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

Can't save the Enum in the database with Doctrine #90

Closed Growiel closed 4 years ago

Growiel commented 4 years ago

Hi there,

I'm using the project as part of a Symfony 5 project, but when I try to save a form using a ReadableEnum, I get the following error:

Call to a member function getValue() on string

The error comes from Elao\Enum\Bridge\Doctrine\DBAL\Types\AbstractEnumType in the convertToDatabaseValue() method line 57.

The method tries to return $value->getValue() but, at least in my case, $value is a simple string containing the value of my enum.

I tried with both as_values true and false, with similar results. $value is never an instance of Enum.

Here's my enum:

<?php

namespace App\Enum;

use Elao\Enum\ReadableEnum;

class EventType extends ReadableEnum
{
    const TRAINING = 'training';
    const COMPETITION = 'competition';
    const MAIN_EVENT = 'main_event';

    public static function values(): array
    {
        return [
            self::TRAINING,
            self::COMPETITION,
            self::MAIN_EVENT,
        ];
    }

    public static function readables(): array
    {
        return [
            self::TRAINING => 'event_type.training',
            self::COMPETITION => 'event_type.competition',
            self::MAIN_EVENT => 'event_type.main_event',
        ];
    }
}

Here's the form that implements it:

<?php

namespace App\Form;

use App\Entity\Event;
use Elao\Enum\Bridge\Symfony\Form\Type\EnumType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class EventType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title')
            ->add('type', EnumType::class, [
                'enum_class' => \App\Enum\EventType::class,
                'as_value' => false,
                'expanded' => true,
                'multiple' => false,
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Event::class,
        ]);
    }
}

The relevant part of the config/packages/elao_enum.yaml file:

elao_enum:
    # Provide the enum classes for which you want to automatically generate & register Doctrine DBAL Types:
    # https://github.com/Elao/PhpEnums#doctrine
    doctrine:
        types:
            App\Enum\EventType: { name: "App\\Enum\\EventType" }

And finally, the entity where the enum is mapped:

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use App\Enum\EventType;
use App\Repository\EventRepository;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;

/**
 * @ApiResource()
 * @ORM\Entity(repositoryClass=EventRepository::class)
 */
class Event
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $title;

    /**
     * @ORM\Column(type=EventType::class)
     */
    private $type;

    public function setId(int $id): self
    {
        $this->id = $id;

        return $this;
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTitle(): ?string
    {
        return $this->title;
    }

    public function setTitle(string $title): self
    {
        $this->title = $title;

        return $this;
    }

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

    public function setType(?string $type): self
    {
        $this->type = $type;

        return $this;
    }
}

What am I missing ?

Thanks !

pluk77 commented 4 years ago

If you use a different name for the Enum and the doctrine type, it is easier to see the difference in where to use them.

Hope this helps you.

Our config/packages/elao_enum.yaml would read something like this:

elao_enum:
    doctrine:
        types:
            App\Enum\EventTypeEnum: eventType

The entity:

    /**
     * @ORM\Column(type=eventType)
     */
    private $type;

If you want to set a default for the type, do it in the constructor:

    public function __construct()
    {
        $this->type = EventTypeEnum::get(EventTypeEnum::TRAINING);
    }

Your getters and setters need to use the enum class as well:

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

    public function setType(?EventTypeEnum $type): self
    {
        $this->type = $type;

        return $this;
    }
ogizanagi commented 4 years ago

I'm closing as there is no new feedback here after @pluk77 's answer. Please reopen if we missed something.