Elao / PhpEnums

:nut_and_bolt: Extended PHP 8.1+ enums features & specific integrations with frameworks and libraries
MIT License
326 stars 27 forks source link

Symfony bridge: Use PHP Attributes for Doctrine configuration #236

Open michnovka opened 2 weeks ago

michnovka commented 2 weeks ago

I have a proposal: right now we require to list manually all types that we want to integrate with doctrine inside elao_enum.yaml file.

Let's introduce a new attribute Elao\Attribute\DBALType.

#[DBALType]
enum Status: string implements ReadableEnumInterface
{
    use ReadableEnumTrait;

    case DRAFT = 'draft';
    case PUBLISHED = 'published';
    case ARCHIVED = 'archived';
}

We can take advantage of autoconfiguration:

namespace Elao\Attribute;

use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;

#[\Attribute(\Attribute::TARGET_CLASS)]
class DBALType extends AutoconfigureTag
{
    public function __construct(
        public ?string $name = null, // if null, use enum FQCN as name
        public string $type = 'single',
        public \BackedEnum|int|string|null $defaultOnNull = null,
    )
    {
        parent::__construct('elao.dbal_type');
    }
}

then its easy to find inside CompilerPass

     public function process(ContainerBuilder $container)
    {
        // Find all services tagged with 'your_bundle.dbal_type'
        $taggedServices = $container->findTaggedServiceIds('elao.dbal_type');

        $enums = [];

        foreach ($taggedServices as $id => $tags) {
            $definition = $container->getDefinition($id);
            $class = $definition->getClass();

            if (class_exists($class)) {
                $reflection = new \ReflectionClass($class);

                if ($reflection->isEnum()) {
                    $enums[] = $class;
                }
            }
        }

        // Process $enums as needed
        $this->registerDBALTypes($enums);
    }

I just think this is more flexible approach than always remembering to add a config type to yaml config.

And, as an added benefit, because of the reflection used in PHP code, this allows us to use TypedFieldMapper to map all such enums to their types. So we can then use

    #[ORM\Column]
    private Status $status;

instead of

    #[ORM\Column(type: Status::class)]
    private Status $status;

WDYT?

We have to agree what to do with the yaml config, especially if we have conflicts. Will one take precedence? Will we throw?

ogizanagi commented 1 week ago

Sounds interesting indeed. TIL about the type field mappers, thanks 👌🏻

We have to agree what to do with the yaml config, especially if we have conflicts. Will one take precedence? Will we throw?

Usually, YAML config wins over PHP attributes. It is meant to allow overriding the config per Symfony env easily. Despite I don't see much use-case for it, I guess we should stick to the convention here.