Maërlyn

Enum mező Doctrine2-ben

2013. 09. 16.

A Doctrine2 nagyon szép és jó, viszont a platformfüggetlenség jegyében (helyesen) nem támogatja beépítve a csak MySQL-ben létező enum mezőt. Számunkra viszont ez elég hasznos volna, lássuk hát, hogyan tudjuk belevarázsolni Symfony2 keretek közt.

Az osztályok felépítését úgy oldottam meg, hogy van egy ős, amiben le van írva az összes művelet, valamint igény szerinti számban ennek a gyerekei egyedi névvel és a hozzájuk tartozó értékekkel.

Új típust úgy tudunk létrehozni, hogy a Doctrine\DBAL\Types\Type osztályból származtatunk. Íme az ősünk:

<?php

namespace Foo\BarBundle\DBAL;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;

abstract class EnumType extends Type
{
    protected $name;
    protected static $values;

    public function getSqlDeclaration(array $fieldDeclaration,
        AbstractPlatform $platform)
    {
        return
           $platform->getVarcharTypeDeclarationSQL($fieldDeclaration);
    }

    public function convertToPHPValue($value,
        AbstractPlatform $platform)
    {
        return $value;
    }

    public function convertToDatabaseValue($value,
        AbstractPlatform $platform)
    {
        if (!in_array($value, static::$values)) {
            throw new \InvalidArgumentException(
                "Invalid '".$this->name."' value."
            );
        }

        return $value;
    }

    public function getName()
    {
        return $this->name;
    }

    public static function getValues()
    {
        return static::$values;
    }

    public function requiresSQLCommentHint(AbstractPlatform $platform)
    {
        return true;
    }
}

(A kódba plusz töréseket raktam, hogy kiférjen.)

Részletesen, hogy mi mit csinál:

Az utolsó függvényről picit bővebben szeretnék írni. Amikor migrálást csinálunk, akkor a Doctrine az adatbázis állapota alapján és a kódunk alapján készít egy-egy sémát, aztán ezeket hasonlítja össze. Az enum mezőnk az elsőben varchar, a másodikban saját típus lesz, ezért minden alkalommal módosítaná, mert nem tudja megkülönböztetni. E függvényünk igaz visszatérési értéke miatt viszont a mezőhöz hozzácsap egy “(DC2Type:valamilyenenum)” kommentet, ami alapján később pontosan tudja, milyen típusúnak is kell lennie.

Konkrét típust két lépésben tudunk létrehozni: származtatunk kell az előző osztályból, valamint regisztrálni kell azt. A gyerekre egy példa:

<?php

namespace Foo\BarBundle\DBAL;

class FejlecLablecEnumType extends EnumType
{
    protected $name = "fejleclablecenum";
    protected static $values = array(
        "fejléc"    =>  "fejléc",
        "lábléc"    =>  "lábléc",
    );
}

A regisztráció pedig csak ennyi:

# app/config/config.yml
doctrine:
    dbal:
        types:
            fejleclablecenum: Foo\BarBundle\DBAL\FejlecLablecEnumType

És máris tudjuk használni az entity-jeink leírásához:

/**
 * @ORM\Column(type="fejleclablecenum")
 */
protected $tipus;

Illetve a formjainkban:

$tipusok = FejlecLablecEnumType::getValues();
$builder->add("tipus", "choice", array(
    "label"         =>  "Típus",
    "choices"       =>  $tipusok,
    "constraints"   =>  array(
        new Assert\NotBlank(),
        new Assert\Choice(array("choices" => array_keys($tipusok))),
    ),
));

Ez utóbbi megoldás nem a legszebb, de még nem találtam rá jobbat.