fre5h / DoctrineEnumBundle

📦 Provides support of ENUM type for Doctrine in Symfony applications.
https://github.com/fre5h/DoctrineEnumBundle
MIT License
459 stars 75 forks source link

How to use with native PHP enum #236

Open SirMishaa opened 8 months ago

SirMishaa commented 8 months ago

Hello,

According this example :

<?php
namespace App\DBAL\Types;

use Fresh\DoctrineEnumBundle\DBAL\Types\AbstractEnumType;

/**
 * @extends AbstractEnumType<string, string>
 */
final class BasketballPositionType extends AbstractEnumType
{
    public final const POINT_GUARD = 'PG';
    public final const SHOOTING_GUARD = 'SG';
    public final const SMALL_FORWARD = 'SF';
    public final const POWER_FORWARD = 'PF';
    public final const CENTER = 'C';

    protected static array $choices = [
        self::POINT_GUARD => 'Point Guard',
        self::SHOOTING_GUARD => 'Shooting Guard',
        self::SMALL_FORWARD => 'Small Forward',
        self::POWER_FORWARD => 'Power Forward',
        self::CENTER => 'Center'
    ];
}

I wonder why we don't use PHP's native enumerations? I have this enum, and I'd want to use as a type :

<?php

namespace App\Enum;

enum MovieStatus
{
    /**
     * We don't have any information about the movie.
     */
    case UNKNOWN;

    /**
     * The production of the movie has been temporarily postponed.
     */
    case PRODUCTION_POSTPONED;

    /**
     * The movie has been officially announced,
     * and details such as production and release date are in progress.
     */
    case ANNOUNCED;

    /**
     * The movie is in the writing phase (scenario development).
     */
    case WRITING_STARTED;

    /**
     * The movie is currently in the production phase (being filmed, recorded, ...).
     */
    case PRODUCTION_STARTED;

    /**
     * The movie has been produced and edited,
     * but the release date is yet to be confirmed.
     */
    case WAITING_FOR_RELEASE;

    /**
     * The release of the movie has been temporarily postponed.
     */
    case RELEASE_POSTPONED;

    /**
     * The movie has been officially released to the public.
     */
    case RELEASED;

    /**
     * The production of the movie has been canceled.
     */
    case CANCELED;
}

Regards ❤️

fre5h commented 8 months ago

@SirMishaa Hi. Maybe this article could help you https://www.doctrine-project.org/2022/01/11/orm-2.11.html The approach is different, now Doctrine supports native PHP enums out of box

hackzilla commented 4 months ago

The reason we wanted to use PHP enums was better reason about the value in our application logic and the reason to keep using DoctrineEnumBundle was to enforce the value in the database.

We've had success with enums and DoctrineEnumBundle.

Here is our implementation. We have an enum for Review types.

enum ReviewEnum: string
{
    case WRITTEN         = 'written';
    case PHONE_INTERVIEW = 'phone_interview';
}

And we reference the enum in the Type.

final class ReviewType extends AbstractEnumType
{
    protected static array $choices = [
        ReviewEnum::WRITTEN->value         => 'Written',
        ReviewEnum::PHONE_INTERVIEW->value => 'Phone interview',
    ];
}
    #[ORM\Column(type: ReviewType::class, nullable: false, enumType: ReviewEnum::class)]
    #[PhpEnumType(entity: ReviewType::class)]
    private ReviewEnum $type;

    public function getType(): ReviewEnum
    {
        return $this->type;
    }

    public function setType(ReviewEnum|string $type): self
    {
        if (is_string($type)) {
            $type = ReviewEnum::from($type);
        }

        $this->type = $type;

        return $this;
    }

setType() accepts strings to make it easier to load fixtures when testing.

However, the EnumTypeValidator will not work with php enums, so we need to wrap it to allow it to convert the enum into a string.

namespace App\Validator\Constraints;

use Fresh\DoctrineEnumBundle\Exception\RuntimeException;
use Fresh\DoctrineEnumBundle\Validator\Constraints\EnumType;
use Fresh\DoctrineEnumBundle\Validator\Constraints\EnumTypeValidator;
use Symfony\Component\Validator\Constraint;

class PhpEnumTypeValidator extends EnumTypeValidator
{
    /**
     * @throws RuntimeException
     */
    public function validate(mixed $value, Constraint|EnumType $constraint): void
    {
        if (!$constraint instanceof EnumType) {
            throw new RuntimeException(\sprintf('Object of class %s is not instance of %s', \get_class($constraint), EnumType::class));
        }

        $constraint->choices = $constraint->entity::getValues();

        if ($value instanceof \BackedEnum) {
            $value = $value->value;
        }

        parent::validate($value, $constraint);
    }
}

and

namespace App\Validator\Constraints;

use Fresh\DoctrineEnumBundle\Validator\Constraints\EnumType;

/**
 * Required to activate PhpEnumTypeValidator.
 */
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class PhpEnumType extends EnumType
{
}
hackzilla commented 4 months ago

@fre5h If you could incorporate the unwrapping of the enum in the validator, then this bundle would support php enums.

fre5h commented 4 months ago

@hackzilla thanks for advice, I'll consider this 👍