Le blog du pangolin

Accueil > Programmation > PHP > Doctrine > Créer un nouveau générateur d’identifiant

Créer un nouveau générateur d’identifiant

mercredi 20 avril 2011, par Super Pangolin

Si la base de votre client est mal configurée et que vous n’avez ni d’auto-increment ni de séquence et que, justement, c’est la base de votre client, il n’est pas question de modifier sa structure, les générateurs de Doctrine ne sont pas suffisants. Nous allons donc créer un générateur qui va trouver la valeur maximum de votre colonne et l’incrémenter.

Si vos entités sont bien configurées, lors d’une insertion en base, Doctrine connait votre (vos) colonnes qui servent d’identifiant à votre table. Vous avez également renseigné la stratégie de génération e votre identifiant. Tout devrait aller comme sur des roulettes dans le meilleur des mondes. Pour rappel, voici un exemple de configuration :

   /**
    * @var integer $matableId
    *
    * @Column(name="MATABLE_ID", type="integer", nullable=false)
    * @Id
    * @GeneratedValue(strategy="SEQUENCE")
    * @SequenceGenerator(sequenceName="MATABLE_MATABLE_ID", allocationSize="1", initialValue="1")
    */
   private $matableId;

Ceci fonctionne si vous êtes sous oracle avec des séquences.
Dans le cas contraire, sous oracle, on est bloqué : aucune des stratégies proposées par Doctrine ne convient. Soit on choisit la stratégie "NONE" et on assigne des identifiants à la main, soit on écrit une nouvelle stratégie. Faisons le deuxième choix.
Les classes des générateurs sont dans le répertoire « ORM\Id ». Allons y et créons un nouveau fichier appelé « MaxPlusOneGenerator.php » (ou selon votre inspiration). Voici mon code commenté :

<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/

namespace Doctrine\ORM\Id;

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMException;

/**
* For numeric ids, generate a (max(id)+1).
*
* @since   2.0
* @author  Julien Tricard
*/
class MaxPlusOneGenerator extends AbstractIdGenerator
{
   /**
    * Generates the identifier assigned to the given entity.
    *
    * @param object $entity
    * @return mixed
    * @override
    */
   public function generate(EntityManager $em, $entity)
   {
       //--- On charge la classe de l'entité
       $class = $em->getClassMetadata(get_class($entity));
       //--- On liste ses identifiants (normalement, on n'en a qu'un)
       $idFields = $class->getIdentifierFieldNames();
       if(\count($idFields)>1){
           throw new Exception;
       }
       //--- Nom de l'entité (nécessaire pour requêter)
       $entitiName = $class->name;
       //--- Requêtage
       $querySelectMax="select max(e.{$idFields[0]})+1 as newId from {$entitiName} e";
       $q = $em->createQuery($querySelectMax);
       $newId = $q->getResult();
       //--- Assignation de la valeur à la propriété identifiant
       $class->getReflectionProperty($idFields[0])->setValue($entity, $newId);
       //--- Fini! On retourne la valeur
       return $newId[0]["newId"];
   }
}

Tout est commenté, que dire de plus ?
Il faut maintenant dire à Doctrine que ce générateur existe :
Dans le ficher « ORM\Mapping\ClassMetadataInfo.php » cherchez le texte

const GENERATOR_TYPE_NONE = 5;

et insérez dessous :

   /**
    * MAXPLUSONE means the id is generated with a select max(id) +1 query.
    */
   const GENERATOR_TYPE_MAXPLUSONE = 6;

Oui, j’ai mis des commentaires en anglais, l’espoir fait vivre...
Dans le ficher « ORM\Mapping\ClassMetadataFactory.php » cherchez le texte

case ClassMetadata::GENERATOR_TYPE_NONE:

On va rajouter un nouveau case :

           case ClassMetadata::GENERATOR_TYPE_MAXPLUSONE:
               $class->setIdGenerator(new \Doctrine\ORM\Id\MaxPlusOneGenerator());
               break;

Et voila, il n’y a plus qu’à le définir dans nos entités :

   /**
    * @var integer $matableId
    *
    * @Column(name="MATABLE_ID", type="integer", nullable=false)
    * @Id
    * @GeneratedValue(strategy="MAXPLUSONE")
    */
   private $matableId;

Notre générateur d’identifiant est opérationnel. Il est juste dommage d’être obligé de modifier le code de Doctrine.

Un message, un commentaire ?

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.