Do You PHP はてブロ

Do You PHPはてなからはてブロに移動しました

Doctrine2.3で導入されたCustomIdGeneratorを試してみた

Doctrine2ではORMにPrimary Keyの値を自動生成する機能(Generator)が用意されています。今回2.3から"独自の値"を生成するGeneratorを定義・利用できるようになったので試してみました。

前置き

2.2まではBasic Mapping - Object Relational Mapper (ORM) - Doctrineにあるオプション

  • AUTO
  • SEQUENCE
  • TABLE
  • IDENTITY
  • NONE

が用意されており、これを変更することで「どのように値を生成するのか?」を変更することができます。
ただし、基本的にはsequenceなどを使った連番(数値)となりますので、

  • 先頭ゼロ埋めのXX桁の文字列にしたい
  • Entity内の何らかの値(特定のカラムの値とか)を使ってIDを生成したい

といった場合は、別途値を生成してEntityにセットしてからpersist、といった処理が必要でした。

環境

  • PHP5.4.6
  • Symfony2.1.0-RC1 (7a233bc7a6f1a34150a358d223b12c76c1ed1674)
  • Doctrine2.3.x-dev (971865f271771a3b06c474dfea9e91f023837cce)

手順

1.Entityクラスにアノテーションを追加する

GeneratedValueアノテーションのstrategyオプションに"CUSTOM"を指定し、実際に値を生成するクラスを2.3から導入されたCustomIdGeneratorアノテーションで指定します。

<?php

namespace Foo\Bar;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

class Item
{
    /**
     * @var string $ItemCode 受注番号
     *
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="CUSTOM")
     * @ORM\CustomIdGenerator(class="Acme\DemoBundle\Entity\Id\BasicGenerator")
     * @ORM\Column(name="item_code", type="string", length=10, nullable=false)
     */
    protected $itemCode;
     :
}
2.Generatorクラスを作成する

CustomIdGeneratorアノテーションで指定したGeneratorクラスを実装します。他のGeneratorクラスと同様、Doctrine\ORM\Id\AbstractIdGeneratorクラスを継承してgenerateメソッドを実装します。
たとえば、ありがちな「先頭ゼロ埋めのXX桁の文字列」の場合、次のような感じです。

<?php

namespace Acme\DemoBundle\Entity\Id;

use Doctrine\ORM\Id\AbstractIdGenerator;
use Doctrine\ORM\EntityManager;

class BasicGenerator extends AbstractIdGenerator
{
    /**
     * {@inheritdoc}
     */
    public function generate(EntityManager $em, $entity)
    {
        $metadata = $em->getClassMetadata(get_class($entity));
        if (!isset($metadata->identifier[0]) || !isset($metadata->fieldMappings[$metadata->identifier[0]]['length'])) {
            return null;
        }

        $query = 'SELECT MAX(a.' . $metadata->identifier[0] . ' ) AS max_id FROM ' . get_class($entity) . ' a ';
        $result = $em->createQuery($query)->getSingleResult();
        $max_id = (is_null($result['max_id']) ? 0 : (int)$result['max_id']);

        return sprintf('%0' . $metadata->fieldMappings[$metadata->identifier[0]]['length'] . 'd', $max_id + 1);
    }

}

第2引数の$entityには、ID生成時点でのEntityオブジェクトが渡されますので、Entityの値を使ってIDを生成することもできそうです。

制限

複合キーには対応していないようで、複合キーを持つEntityに適用した場合、EntityManager::persistをコールした際に次のような例外(Doctrine\ORM\Mapping\MappingException)が発生します。

Entity 'Acme\DemoBundle\Entity\ItemAttribute' has a composite identifier but uses an ID generator other than manually assigning (Identity, Sequence). This is not supported.

まとめ

個人的にはありがたい機能で意外とあっさり実装できてしまいました。
ただ、「一歩進んだ」感はあるんですが、相変わらず複合キーに弱いなぁ。。。