Last active
July 12, 2021 11:53
-
-
Save gnoesiboe/e030850c2fa03aeb529c5332ef184654 to your computer and use it in GitHub Desktop.
[Enum] #utility #valueobject
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\Domain\ValueObject; | |
use DomainException; | |
use ReflectionClass; | |
use ReflectionException; | |
use UnexpectedValueException; | |
abstract class Enum | |
{ | |
private const UNIQUE_IDENTIFIER_SEPARATOR = ':'; | |
protected string $value; | |
/** | |
* @var array<class-string<Enum>, string[]> | |
*/ | |
private static array $constants = []; | |
/** | |
* @throws ReflectionException | |
*/ | |
public function __construct(string $value) | |
{ | |
$this->setValue($value); | |
} | |
public static function fromUniqueIdentifier(string $identifier): Enum | |
{ | |
[$enumClass, $enumValue] = explode(self::UNIQUE_IDENTIFIER_SEPARATOR, $identifier); | |
if (!$enumClass || !$enumValue) { | |
throw new UnexpectedValueException('Expecting decoded values to have class and value properties set'); | |
} | |
return new $enumClass($enumValue); | |
} | |
public function toUniqueIdentifier(): string | |
{ | |
return get_class($this) . self::UNIQUE_IDENTIFIER_SEPARATOR . $this->value; | |
} | |
/** | |
* @param string $value | |
* | |
* @throws ReflectionException | |
*/ | |
private function setValue(string $value): void | |
{ | |
$this->validateValue($value); | |
$this->value = $value; | |
} | |
/** | |
* @throws ReflectionException | |
*/ | |
protected function validateValue(string $value): void | |
{ | |
/** @var string[] $supported */ | |
$supported = array_values( | |
self::extractConstants(get_called_class()) | |
); | |
if (in_array($value, $supported, true) === false) { | |
$exception = $this->createInvalidException($value, $supported); | |
if (!$exception instanceof DomainException) { | |
throw new UnexpectedValueException('Expecting to throw an instance of: ' . DomainException::class); | |
} | |
throw $exception; | |
} | |
} | |
/** | |
* @param string $supplied | |
* @param string[] $supported | |
* | |
* @return DomainException | |
*/ | |
protected function createInvalidException(string $supplied, array $supported): DomainException | |
{ | |
$className = get_class($this); | |
return new DomainException("$className value '$supplied' not supported. Supported are: " . implode(', ', $supported)); | |
} | |
/** | |
* @return string[] | |
* | |
* @throws ReflectionException | |
*/ | |
public static function getSupported(): array | |
{ | |
return self::extractConstants(get_called_class()); | |
} | |
/** | |
* @param class-string<Enum> $class | |
* | |
* @return string[] | |
* | |
* @throws ReflectionException | |
*/ | |
private static function extractConstants(string $class): array | |
{ | |
if (array_key_exists($class, self::$constants) === true) { | |
return self::$constants[$class]; | |
} | |
$reflection = new ReflectionClass($class); | |
/** @var string[] $constants */ | |
$constants = $reflection->getConstants(); | |
self::validateClassConstantValuesAreUnique($constants); | |
// This is required to make sure that constants of base classes will be the first to be checked | |
// and then that if it's children | |
while (($reflection = $reflection->getParentClass()) && $reflection instanceof ReflectionClass && $reflection->name !== __CLASS__) { | |
$constants = $reflection->getConstants() + $constants; | |
} | |
self::$constants[$class] = $constants; | |
return $constants; | |
} | |
/** | |
* @param string[] $constants | |
* | |
* @throws UnexpectedValueException | |
*/ | |
private static function validateClassConstantValuesAreUnique(array $constants): void | |
{ | |
// values needs to be unique | |
$ambiguous = []; | |
foreach ($constants as $constantValue) { | |
$nameOccurrenceForValue = array_keys($constants, $constantValue, true); | |
if (count($nameOccurrenceForValue) > 1) { | |
$ambiguous[var_export($constantValue, true)] = $nameOccurrenceForValue; | |
} | |
} | |
if (count($ambiguous) > 0) { | |
throw new UnexpectedValueException( | |
'The constant values for an Enum need to be unique to be able to extinguish hem' | |
); | |
} | |
} | |
public function getValue(): string | |
{ | |
return $this->value; | |
} | |
public function isValue(string $value): bool | |
{ | |
return $this->value === $value; | |
} | |
/** | |
* @param string[] $values | |
* | |
* @return bool | |
*/ | |
public function isOneOfValues(array $values): bool | |
{ | |
foreach ($values as $value) { | |
if ($this->isValue($value)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
public function __toString(): string | |
{ | |
return $this->getValue(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\Infrastructure\Doctrine\Type; | |
use App\Domain\ValueObject\Enum; | |
use Doctrine\DBAL\Platforms\AbstractPlatform; | |
use Doctrine\DBAL\Types\Type; | |
use UnexpectedValueException; | |
/** | |
* To be able to persist to and retrieve from database using Doctrine. | |
*/ | |
final class EnumType extends Type | |
{ | |
private const NAME = 'domain_enum'; | |
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string | |
{ | |
return 'VARCHAR(255)'; | |
} | |
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string | |
{ | |
if ($value === null) { | |
return null; | |
} | |
if (!$value instanceof Enum) { | |
throw new UnexpectedValueException('Expecting value to either be null or an instance of: ' . Enum::class); | |
} | |
return $value->toUniqueIdentifier(); | |
} | |
public function convertToPHPValue($value, AbstractPlatform $platform): ?Enum | |
{ | |
if (!is_string($value) || $value === '') { | |
return null; | |
} | |
return Enum::fromUniqueIdentifier($value); | |
} | |
public function getName(): string | |
{ | |
return self::NAME; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\Domain\Entity\Employer; | |
use App\Domain\ValueObject\AbstractEnum; | |
final class Status extends AbstractEnum | |
{ | |
public const OPEN = 'open'; | |
public const IN_PROGRESS = 'in_progress'; | |
public const CLOSED = 'closed'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment