Skip to content

Commit

Permalink
Merge pull request #96 from smartemailing/removed-consistance
Browse files Browse the repository at this point in the history
Removed consistance
  • Loading branch information
slischka committed May 25, 2021
2 parents 2dc2b2d + a52097d commit 9fa524d
Show file tree
Hide file tree
Showing 4 changed files with 345 additions and 63 deletions.
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
],
"require": {
"php": "^7.2|^8.0",
"consistence/consistence": "^1.0 || ^2.0",
"nette/utils": "^3.0 || ^2.4",
"nette/http": "^3.0 || ^2.4",
"jschaedl/iban-validation": "^1.0",
Expand Down
265 changes: 259 additions & 6 deletions src/Enum.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,276 @@

namespace SmartEmailing\Types;

use Consistence\Enum\InvalidEnumValueException;
use ReflectionClass;
use ReflectionClassConstant;

abstract class Enum extends \Consistence\Enum\Enum
abstract class Enum
{

/**
* @var mixed
*/
private $value;

/**
* @var array<static> indexed by enum and value
*/
private static $instances = [];

/**
* @var array<string, mixed>
*/
private static $availableValues = [];

/**
* @param mixed $value
*/
final private function __construct(
$value
)
{
static::checkValue($value);
$this->value = $value;
}

/**
* @param mixed $value
* @return static
*/
final public static function get(
$value
): self
{
$index = \sprintf('%s::%s', static::class, self::getValueIndex($value));

if (!isset(self::$instances[$index])) {
self::$instances[$index] = new static($value);
}

return self::$instances[$index];
}

/**
* @return mixed
*/
public function getValue()
{
return $this->value;
}

public function equals(
self $that
): bool
{
$this->checkSameEnum($that);

return $this === $that;
}

/**
* @param mixed $value
* @return bool
*/
public function equalsValue(
$value
): bool
{
return $this->getValue() === $value;
}

/**
* @param mixed $value
* @throws \Exception
*/
public static function checkValue(
$value
): void
{
try {
parent::checkValue($value);
} catch (InvalidEnumValueException $e) {
throw new InvalidTypeException($e->getMessage());
if (!\is_subclass_of(static::class, self::class)) {
throw new \Exception(\sprintf(
'"%s" is not a subclass of "%s"',
static::class,
self::class
));
}

if (!static::isValidValue($value)) {
$availableValues = static::getAvailableValues();

throw new InvalidTypeException(
\sprintf(
'%s [%s] is not a valid value for %s, accepted values: %s',
$value,
\gettype($value),
static::class,
\implode(', ', $availableValues)
)
);
}
}

/**
* @return array<mixed>
*/
public static function getAvailableValues(): array
{
$index = static::class;

if (!isset(self::$availableValues[$index])) {
$availableValues = self::getEnumConstants();
static::checkAvailableValues($availableValues);
self::$availableValues[$index] = $availableValues;
}

return self::$availableValues[$index];
}

/**
* @return array<static>
*/
public static function getAvailableEnums(): array
{
$values = static::getAvailableValues();
$out = [];

foreach ($values as $value) {
$out[$value] = static::get($value);
}

return $out;
}

/**
* @param mixed $value
* @return bool
*/
public static function isValidValue(
$value
): bool
{
return \in_array($value, self::getAvailableValues(), true);
}

protected function checkSameEnum(
self $that
): void
{
if (\get_class($that) !== static::class) {
throw new InvalidTypeException(\sprintf('Operation supported only for enum of same class: %s given, %s expected', \get_class($that), static::class));
}
}

/**
* @param array<mixed> $availableValues
*/
protected static function checkAvailableValues(
array $availableValues
): void
{
$index = [];

foreach ($availableValues as $value) {
self::checkType($value);
$key = self::getValueIndex($value);

if (isset($index[$key])) {
throw new InvalidTypeException(
\sprintf(
'Value %s [%s] is specified in %s\'s available values multiple times',
$value,
\gettype($value),
static::class
)
);
}

$index[$key] = true;
}
}

/**
* @param mixed $value
*/
private static function checkType(
$value
): void
{
if (\is_scalar($value) || $value === null) {
return;
}

$valueType = \gettype($value);
$printableValue = $value;

if (\is_object($value)) {
$valueType = \get_class($value);
$printableValue = \get_class($value);
}

if (\is_array($value)) {
$valueType = '';
}

throw new InvalidTypeException(
\sprintf('%s expected, %s [%s] given', 'int|string|float|bool|null', $printableValue, $valueType)
);
}

/**
* @param \ReflectionClass<static> $classReflection
* @return array<int, \ReflectionClassConstant>
*/
private static function getDeclaredConstants(
ReflectionClass $classReflection
): array
{
$constants = $classReflection->getReflectionConstants();
$className = $classReflection->getName();

return \array_filter(
$constants,
static function (ReflectionClassConstant $constant) use ($className): bool {
return $constant->getDeclaringClass()->getName() === $className;
}
);
}

/**
* @param mixed $value
* @return string
*/
private static function getValueIndex(
$value
): string
{
$type = \gettype($value);

return $value . \sprintf('[%s]', $type);
}

/**
* @return array<string, mixed>
*/
private static function getEnumConstants(): array
{
$classReflection = new ReflectionClass(static::class);
$declaredConstants = self::getDeclaredConstants($classReflection);
$declaredPublicConstants = \array_filter(
$declaredConstants,
static function (ReflectionClassConstant $constant): bool {
return $constant->isPublic();
}
);

$out = [];

foreach ($declaredPublicConstants as $publicConstant) {
\assert($publicConstant instanceof \ReflectionClassConstant);

$out[$publicConstant->getName()] = $publicConstant->getValue();
}

return $out;
}

}
43 changes: 27 additions & 16 deletions tests/CountryCodeTest.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,39 @@ declare(strict_types = 1);
namespace SmartEmailing\Types;

use Tester\Assert;
use Tester\TestCase;

require_once __DIR__ . '/bootstrap.php';

$countrySK = CountryCode::from('SK');
Assert::equal('SK', $countrySK->getValue());
class CountryCodeTest extends TestCase
{

$countryGB = CountryCode::extractOrNull(['currency_code' => 'GB'], 'currency_code');
Assert::equal('GB', $countryGB->getValue());
public function testDefaults(): void
{
$countrySK = CountryCode::from('SK');
Assert::equal('SK', $countrySK->getValue());

$countryPL = CountryCode::extract(['currency_code' => 'PL'], 'currency_code');
Assert::equal('PL', $countryPL->getValue());
Assert::equal('PL', (string) $countryPL);
$countryGB = CountryCode::extractOrNull(['currency_code' => 'GB'], 'currency_code');
Assert::equal('GB', $countryGB->getValue());

Assert::true($countryPL->equalsValue(CountryCode::PL));
Assert::false($countryPL->equals($countryGB));
$countryPL = CountryCode::extract(['currency_code' => 'PL'], 'currency_code');
Assert::equal('PL', $countryPL->getValue());
Assert::equal('PL', (string) $countryPL);

$enums = CountryCode::getAvailableEnums();
Assert::type('array', $enums);
Assert::true($countryPL->equalsValue(CountryCode::PL));
Assert::false($countryPL->equals($countryGB));

$values = CountryCode::getAvailableValues();
Assert::type('array', $values);
$enums = CountryCode::getAvailableEnums();
Assert::type('array', $enums);

$country = CountryCode::from('CZ');
Assert::type(CountryCode::class, $country);
Assert::type(CountryCode::class, $country);
$values = CountryCode::getAvailableValues();
Assert::type('array', $values);

$country = CountryCode::from('CZ');
Assert::type(CountryCode::class, $country);
Assert::type(CountryCode::class, $country);
}

}

(new CountryCodeTest())->run();
Loading

0 comments on commit 9fa524d

Please sign in to comment.