This post comes from the first version of this blog.
Please send me an email if anything needs an update. Thanks!

Jakiś czas temu chciałem poeksperymentować trochę z bazą danych jednego z projektów FLOSS w Symfony2.

Importując dane z MySQLa poprzez komendę

1
php app/console doctrine:mapping:import ThunderSomethingBundle yml --force

zostałem przywitany przez Doctrine2 wyjątkiem:

[Doctrine\DBAL\DBALException] Unknown database type enum requested, Doctrine\DBAL\Platforms\MySqlPlatform may not support it.

Zgodnie z dokumentacją okazuje się, że ENUM nie jest typem danych wspieranych domyślnie przez ten ORM. Akapit:

When using Enums with a non-tweaked Doctrine 2 application you will get errors from the Schema-Tool commands due to the unknown database type “enum”. By default Doctrine does not map the MySQL enum type to a Doctrine type. This is because Enums contain state (their allowed values) and Doctrine types don’t.

powinien wyjaśnić cel, którym kierowali się programiści biblioteki. Trochę do dziwne, bo według mnie można było podczas generowania klasy encji w Symfony2 wykryć, że dane pole jest wyliczeniem (enumeracją - ENUMem), pobrać listę poprawnych wartości, zapisać je jako publiczną statyczną listę stałych albo chronioną statyczną tablicę asocjacyjną (string na int) i umieścić odpowiednie warunki (prosty array_key_exists() byłby wystarczający) w setterach:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

class SampleEntity
{
    protected static $enumValues = array(
        'opened' => 0,
        'closed' => 1,
        'protected' => 2,
    );

    const ENUMFIELD_OPENED = 0;
    const ENUMFIELD_CLOSED = 1;
    const ENUMFIELD_PROTECTED = 2;

    private $enumField;

    public function setEnumField($newValue)
    {
        if(!array_key_exists($newValue, self::$enumValues)) {
            $msg = sprintf('Invalid value %s!', $newValue);
            throw new \InvalidArgumentException($msg);
        }
        
        $this->enumField = self::$enumValues[$newValue];
    }
}

Istnieje na to jednak rada - nie musimy zmieniać kodu Doctrine ani kombinować z różnego rodzaju obejściami. Na szczęście twórcy umieścili w konfiguracji biblioteki możliwość rejestrowania alternatywnych mapowań typów pól. Jeśli uda nam się dobrać do obiektu EntityManagera, wystarczy, że wywołamy na nim (pośrednio) metodę registerDoctrineTypeMapping. Aby umożliwić korzystanie z typu wyliczeniowego w kontrolerze Symfony2, należy umieścić w nim kod:

1
2
3
4
5
6
7
<?php

$em = $this->getDoctrine()->getManager();
$em
    ->getConnection()
    ->getDatabasePlatform()
    ->registerDoctrineTypeMapping('enum', 'string');

W przypadku poleceń konsolowych, takich jak doctrine:mapping:import właśnie, trzeba jednak udać się do pliku config.yml naszego projektu i stworzyć w drzewie doctrine.dbal wpis mapping_types:

1
2
3
4
doctrine:
  dbal:
    mapping_types:
      enum: string

W przypadku środowiska dev framework sam “załapie” zmiany, w prod trzeba wyczyścić cache za pomocą prostego cache:clear lub jeszcze prostszego rm -rf app/cache/*. :)