jeudi 21 avril 2016

Corriger les requêtes jouées en boucle par doctrine:schema:update

La commande doctrine:schema:update de Symfony permet de mettre à jour votre base de données, par rapport à votre mapping.
Dans certains cas, cette commande veut exécuter des requêtes qui n'ont pas lieu d'être.
De même si vous utilisez DoctrineMigrations, qui peut vous générer des migrations avec toujours les mêmes requêtes.

Par exemple, si vous voulez créér un type de champ FooDecimal, qui doit définir la valeur par défaut de scale à 2 au lieu de 0 :
Foo\Entity\Bar: fields: baz: type: foodecimal

Vous allez créer un type de champ Doctrine via cette documentation, avec entre autres ce code pour définir scale à 2 par défaut "dans la requête SQL" (pas au niveau mapping YML, XML ou PHP) :
class FooDecimalType extends DecimalType { public function getName() { return 'foodecimal'; } public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { $fieldDeclaration['scale'] = (empty($fieldDeclaration['scale'])) ? 2 : $fieldDeclaration['scale']; return $platform->getDecimalTypeDeclarationSQL($fieldDeclaration); } /** * Ajoute un commentaire SQL sur le champ (exemple MySQL : COMMENT '(DC2Type:foodecimal)'), avec le type retourné par getName() * Permet de lier le type de champ SQL decimal, au type de champ FooDecimalType * Sans ça, Doctrine se base sur la requête ALTER de création du champ pour "retrouver" le type de champ Doctrine * Le premier trouvé sera DecimalType, fourni avec Doctrine, et pas notre FooDecimalType * Comme le type de champ a changé pour Doctrine, une requête ALTER sera jouée à l'infini, * même si au final elle n'effectue pas de modification en base */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return true; }
Au niveau de votre base de données, la valeur par défaut de scale sera bien 2.

Mais il est impossible de modifier les valeurs par défaut du côté du mapping, cf SchemaTool::gatherColumn(), et YamlDriver::columnToArray() pour le mapping YML par exemple.
Quand vous allez exécuter la commande doctrine:schema:update, SchemaTool::getSchemaFromMetadata() retournera 0 comme valeur pour scale pour votre champ baz du côté du mapping puisqu'il n'y a aucune information dans le fichier YML sur scale, alors que AbstractSchemaManager::createSchema() retournera 2, puisque votre champ en base de données est configuré pour avoir scale à 2.
A ce moment-là, pour Doctrine, le champ en base de données n'est pas le même que ce que le mapping lui indique. Donc, il génère une requête ALTER TABLE, qui modifiera le scale en base de données, pour lui mettre 2 (ce que FooDecimal définit dans la génération du SQL), bien que ce soir déjà le cas. La différence entre le mapping et la base de données est faite dans Doctrine\DBAL\Schema\Comparator::diffColumn().

Pour corriger ce problème de valeurs par défaut non modifiable du côté du mapping, on peut utiliser l'événement postGenerateSchemaTable, qui n'est pas documenté.
services: foo_decimal_default_scale: class: Foo\BarBundle\EventListener\FooDecimalDefaultScaleListener tags: - { name: doctrine.event_listener, event: postGenerateSchemaTable }
<?php namespace Foo\BarBundle\EventListener; use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs; use Foo\BarBundle\Doctrine\Type\FooDecimal; class FooDecimalDefaultScaleListener { public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $event) { foreach ($event->getSchema()->getTables() as $table) { foreach ($table->getColumns() as $column) { if ($column->getType() instanceof FooDecimal && $column->getScale() === 0) { $column->setScale(2); } } } } }

Aucun commentaire:

Publier un commentaire