Core: Support for Enums

Created on 14 Oct 2018  路  6Comments  路  Source: api-platform/core

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:

  1. 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);
        }
    }
    
  2. 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
    
  3. 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 }
    
  4. 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..?

enhancement help wanted

Most helpful comment

See also: https://github.com/elao/PhpEnums
It gives you all you want for API Platform:

  • a Doctrine type,
  • a validator,
  • a normalizer,
  • a Faker provider for Alice.

All 6 comments

Not sure if anything can be simplify for a custom class considered as an enum.

At least, it may add support for an enum library like this wonderful one? https://github.com/greg0ire/enum :-)

The Schema Generator component already supports https://github.com/myclabs/php-enum, if we add support for an enum lib in core, it would be better to start with the same one for consistency.

Note: I've nothing against greg0ire's one!

Totally think we should be using a standard package like myclabs/php-enum, I happened to not in my app for other reasons.

You can also use DoctrineEnumType for the part on Doctrine

@dunglas I didn't know about already enable support. So it makes sense to use the same library, indeed.

See also: https://github.com/elao/PhpEnums
It gives you all you want for API Platform:

  • a Doctrine type,
  • a validator,
  • a normalizer,
  • a Faker provider for Alice.
Was this page helpful?
0 / 5 - 0 ratings

Related issues

desmax picture desmax  路  3Comments

rockyweng picture rockyweng  路  3Comments

stipic picture stipic  路  3Comments

theshaunwalker picture theshaunwalker  路  3Comments

DenisVorobyov picture DenisVorobyov  路  3Comments