Elao / PhpEnums

:nut_and_bolt: Extended PHP 8.1+ enums features & specific integrations with frameworks and libraries

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

🚧 V2: Rewrite for PHP 8.1 Enum

ogizanagi opened this issue Β· comments

First step was made in #165 to rewrite the lib with the new native enum types & adding some integrations based on it and our specificities (readables, flags, …).

πŸ“š See current V2 documentation here

🚧 More work to come. Help welcome to integrate what could be useful, from 1.x features or whole new ones.

Will probable create a 2.0.0-alpha1 tag in the next weeks.


V2 Features

Core features

From V1, core features extending the PHP 8.1 native enum capabilities

  • Readable enums ➜ Done in #165.
  • Flag enums ➜ Done in #165.
    It requires a new FlagBag object to manipulate flags, since PHP native enums are restricted to their cases and cannot dynamically declare combinations.

New features

Whole new features or integration, leveraging new enum capabilities

(Pending) official integrations

From V1, likely to be dropped in the future, or already having an official integration

Extra integrations

From V1, might still be relevant/adapted

Extra tasks

  • Write an UPGRADE-2.x.md guide, with most basic hints for upgrading your application code from 1.x to 2.x

https://github.com/symfony/symfony/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+enum+label%3A%22Help+wanted%22

Likely to be dropped in the future, if any rewrite of this library is done based on the new enum type:

Might still benefits:

Extras:

<?php

#[Attribute(Attribute::TARGET_CLASS_CONSTANT)]
class EnumLabel
{
    public function __construct(public string $label)
    {
    }
}

enum Suit: string
{
    #[\EnumLabel('suit.hearts')]
    case Hearts = 'H';
    
    #[\EnumLabel('suit.diamonds')]
    case Diamonds = 'D';
    
    #[\EnumLabel('suit.diamonds')]
    case Clubs = 'C';
    
    #[\EnumLabel('suit.spades')]
    case Spades = 'S';
}

var_dump(Suit::Hearts);
var_dump($reflector = new \ReflectionEnum(Suit::class));
var_dump($reflector->getCase('Hearts')->getAttributes(\EnumLabel::class)[0]->newInstance());
var_dump((new \ReflectionClassConstant(Suit::class, 'Hearts'))->getAttributes(\EnumLabel::class)[0]->newInstance());

# output:
enum(Suit::Hearts)
object(ReflectionEnum)#2 (1) {
  ["name"]=>
  string(4) "Suit"
}
object(EnumLabel)#3 (1) {
  ["label"]=>
  string(11) "suit.hearts"
}
object(EnumLabel)#3 (1) {
  ["label"]=>
  string(11) "suit.hearts"
}

Tried my hands with porting the ApiPlatform bridge over. Maybe it helps?

<?php

/*
 * This file is part of the "elao/enum" package.
 *
 * Copyright (C) Elao
 *
 * @author Elao <contact@elao.com>
 */

namespace App\ApiPlatform\Core\JsonSchema\Type;

use ApiPlatform\Core\JsonSchema\Schema;
use ApiPlatform\Core\JsonSchema\TypeFactoryInterface;
use Elao\Enum\ReadableEnumInterface;
use Symfony\Component\PropertyInfo\Type;

final class ElaoEnumType implements TypeFactoryInterface
{
    /** @var TypeFactoryInterface */
    private $decorated;

    public function __construct(TypeFactoryInterface $decorated)
    {
        $this->decorated = $decorated;
    }

    /**
     * {@inheritdoc}
     */
    public function getType(Type $type, string $format = 'json', ?bool $readableLink = null, ?array $serializerContext = null, Schema $schema = null): array
    {
        if (!is_a($enum = $type->getClassName(), ReadableEnumInterface::class, true)) {
            return $this->decorated->getType($type, $format, $readableLink, $serializerContext, $schema);
        }

        $ref = new \ReflectionEnum($type->getClassName());
        $docType = '';
        switch ($ref->getBackingType()) {
            case 'string': $docType = 'string'; break;
            case 'int': $docType = 'integer'; break;
            case null:
            default:
                $docType = 'string';
                break;
        }
        $schema = [];
        $cases = $enum::cases();
        if ($type->isNullable() && !$type->isCollection()) {
            $cases[] = null;
        }
        $readableCases = [];
        foreach ($cases as $case) {
            //$readableCases[$case->value] = $case->getReadable();
            $readableCases[$case->value] = $case->value . '=' . $case->name;
        }
        $enumSchema = [
            'type' => $docType,
            'enum' => $readableCases,
            'example' => $cases[0]->value,
        ];

        if ($type->isCollection()) {
            $schema['type'] = 'array';
            $schema['items'] = $enumSchema;
        } else {
            $schema = $enumSchema;
        }

        if ($type->isNullable()) {
            $schema['nullable'] = true;
        }

        return $schema;
    }
}

Hi @jensstalder . Thank you very much for looking at this.

Could you please explain me a bit more what would be the purpose of such a bridge now with native PHP enum?
I see the $readableCases used as $enumSchema.enum in your sample, but is that correct?
An API would rely on the backed value (as in the previous version of this library), not the readable one. But is there some places where the readable label could be relevant?

You might have more clues than me on what could be relevant to provide in such a bridge regarding the features that are specific to this lib v2 extending native enums capabilities. So, your help is appreciated :)

@ogizanagi Good question. The only benefit I see in relation to JsonSchema is that it shows up in the open API docs. My original goal was to extend the graphql implementation so that the types also get interpreted as enum. I have not found a solution for that yet. But thinking about it, it's true that API platform core should instead simply handle native enums better for both cases (JsonSchema and GraphQL), and it might not be necessary to do this within this package. But this might be a bit to opinionated from the view of the platform? As far as I can tell though, API platform currently only considers the backing type as if it were a simple string or int field? And Graphql endpoint hides the enum typed field completely (but that could be another issue).

For Doctrine there is one issue affecting this lib

  • ENUMs cannot be used as IDs (I made a PR doctrine/orm#9629 which hopefully will be merged soon)

Please add it on the list

EDIT: merged

@ogizanagi the doctrine/orm#9629 has been merged in doctrine/orm 2.12.x , so you can remove the known issue from the list