Description
edit by soyuka
With PHP 8.1 enums coming to PHP we want to implement the following into API Platform:
- Ability to declare enums as a Resource (see PHP 8.1 Backed enums #4349 by @bpolaszek)
- update the schema factory to implement https://schema.org/Enumeration
- update the OpenAPI factory if needed https://swagger.io/docs/specification/data-models/enums/
- make enums work with GraphQL
- add a doctrine enum filter?
end edit
This issue is more for documentation on how to include enums with API Platform and maybe some discussion on how this could potentially be brought into the core.
I've been able to implement enums by doing the following:
-
Create a class which represents the enum, there is a base enum class that looks like this:
abstract class Enum { protected static $allowedValues = []; protected $value; public function __construct($value) { if (!\in_array($value, static::$allowedValues)) { throw new \InvalidArgumentException("Invalid value '${value}' for enum. Allowed values are: " . \implode(', ', static::$allowedValues)); } $this->value = $value; } public static function getAllValues() { return self::$allowedValues; } public function __invoke() { return $this->value; } public function __toString() { return (string) $this->value; } }
where the subclass would look like:
final class Direction extends Enum { const UP = 'up'; const DOWN = 'down'; protected static $allowedValues = [self::UP, self::DOWN]; public static function up(): self { return new self(self::UP); } public static function down(): self { return new self(self::DOWN); } }
-
Create and Register a custom doctrine type for the enum:
abstract class EnumType extends StringType { public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value instanceof Enum) { return (string) $value; } throw new \InvalidArgumentException('Value must be an Enum instance.'); } public function requiresSQLCommentHint(AbstractPlatform $platform) { return false; } } final class DirectionEnumType extends EnumType { public function convertToPHPValue($value, AbstractPlatform $platform) { return new Direction($value); } public function getName() { return 'direction_enum'; } }
In symfony it would look like:
doctrine: dbal: types: channel_enum: class: App\Doctrine\Type\DirectionEnumType
-
Create a Normalizer for the enum and register it after the object normalizer
class EnumNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface { /** * {@inheritdoc} */ public function normalize($object, $format = null, array $context = array()) { /** @var Enum $object */ return $object(); } /** * {@inheritdoc} */ public function supportsNormalization($data, $format = null) { return $data instanceof Enum; } /** * {@inheritdoc} */ public function denormalize($data, $class, $format = null, array $context = array()) { return new $class($data); } /** * {@inheritdoc} */ public function supportsDenormalization($data, $type, $format = null) { return is_subclass_of($type, Enum::class); } public function hasCacheableSupportsMethod(): bool { return __CLASS__ === \get_class($this); // copied from DateTimeNormalizer, not quite sure what this is for. } }
services: App\Serializer\Normalizer\EnumNormalizer: tags: - {name: serializer.normalizer, priority: -916 }
-
Create a trait and class to utilize the enum!
trait WithDirection { /** * @ORM\Column(type="direction_enum") * @ApiProperty(swaggerContext={"enum"={"up", "down"}}) */ private $direction; public function getDirection(): Direction { return $this->direction; } } class AcmeEntity { use WithDirection; public function __construct(Direction $direction) { $this->direction = $direction; } }
With all of those steps in place, you should be able to have strict enum classes to use in PHP that are saved and retrieved from the db properly, documented in the swagger API, and are properly serialized/deserialized from a string to the proper enum class and back when using the API.
I'm not sure of the best way API Platform could simplify this process except for maybe providing the default enum implementation which would allow API Platform to include the enum normalizer and possibly the doctrine mapping types..?