somemo's diary

プログラマ、雑記、プログラミング関係はLinkから、数式はこっちでまとめていることが多い

【Symfony2】モデルの作成とテーブルの作成

Blogの記事に相当するモデルの作成を行います。

モデルの作成

下記コマンドを実行します。

php app/console generate:doctrine:entity --entity=MyBlogBundle:Post --format=annotation --fields="title:string(255) body:text createdAt:datetime updatedAt:datetime"

コマンド実行中に以下の5つの質問をされました。

  1. The Entity shortcut name [MyBlogBundle:Post]:
  2. Configuration format (yml, xml, php, or annotation) [annotation]:
  3. Available types: array, object, boolean, integer, smallint,bigint, string, text, datetime, datetimetz, date, time, decimal, float.
    New field name (press <return> to stop adding fields):
  4. Do you want to generate an empty repository class [no]?
  5. You are going to generate a "MyBlogBundle:Post" Doctrine2 entity using the "annotation" format.
    Do you confirm generation [yes]?

質問内容は、Entity名と設定の形式、フィールドを追加するかどうか、からのリポジトリクラスを作成するかどうか、最後は作成の確認です。

Entity作成後には、BlogBundleディレクトリにEntityディレクトリ、その中にPost.phpが作成されていました。

設定の形式

モデルの作成コマンドを--format=yml, xml, phpで実行してみました。BlogBundleのResources/configディレクトリにdoctrineディレクトリが作成され、それぞれのフォーマットにあった設定ファイルが作成されていました。

  • Post.orm.yml
  • Post.orm.xml
  • Post.orm.php
My\BlogBundle\Entity\Post:
  type: entity
  table: null
  fields:
    id:
      type: integer
      id: true
      generator:
        strategy: AUTO
    title:
      type: string
      length: '255'
    body:
      type: text
      length: null
    createdAt:
      type: datetime
      length: null
    updatedAt:
      type: datetime
      length: null
  lifecycleCallbacks: {  }

<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
  <entity name="My\BlogBundle\Entity\Post">
    <change-tracking-policy>DEFERRED_IMPLICIT</change-tracking-policy>
    <id name="id" type="integer" column="id">
      <generator strategy="AUTO"/>
    </id>
    <field name="title" type="string" column="title" length="255"/>
    <field name="body" type="text" column="body"/>
    <field name="createdAt" type="datetime" column="createdAt"/>
    <field name="updatedAt" type="datetime" column="updatedAt"/>
    <lifecycle-callbacks/>
  </entity>
</doctrine-mapping>

<?php

use Doctrine\ORM\Mapping\ClassMetadataInfo;

$metadata->setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_NONE);
$metadata->setChangeTrackingPolicy(ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT);
$metadata->mapField(array(
   'fieldName' => 'id',
   'type' => 'integer',
   'id' => true,
   'columnName' => 'id',
  ));
$metadata->mapField(array(
   'fieldName' => 'title',
   'type' => 'string',
   'length' => '255',
   'columnName' => 'title',
  ));
$metadata->mapField(array(
   'fieldName' => 'body',
   'type' => 'text',
   'length' => NULL,
   'columnName' => 'body',
  ));
$metadata->mapField(array(
   'fieldName' => 'createdAt',
   'type' => 'datetime',
   'length' => NULL,
   'columnName' => 'createdAt',
  ));
$metadata->mapField(array(
   'fieldName' => 'updatedAt',
   'type' => 'datetime',
   'length' => NULL,
   'columnName' => 'updatedAt',
  ));
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);

Post.php

--format=annotationで作成した場合、プロパティにannotationが記載されています。それ以外のコードは、他のフォーマットと変わりありません。--format=yml, xml, phpの場合、プロパティに関する設定がファイルとして出力されているためです。

<?php

namespace My\BlogBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * My\BlogBundle\Entity\Post
 *
 * @ORM\Table()
 * @ORM\Entity
 */
class Post
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string $title
     *
     * @ORM\Column(name="title", type="string", length=255)
     */
    private $title;

    /**
     * @var text $body
     *
     * @ORM\Column(name="body", type="text")
     */
    private $body;

    /**
     * @var datetime $createdAt
     *
     * @ORM\Column(name="createdAt", type="datetime")
     */
    private $createdAt;

    /**
     * @var datetime $updatedAt
     *
     * @ORM\Column(name="updatedAt", type="datetime")
     */
    private $updatedAt;

Postクラスは、phpで書かれた単なるクラスです。このクラスにはプロパティと、それに対するセッター/ゲッターが定義されています。annotationの場合には、TableでEntityであることが記載されています。また、各プロパティはカラムであり、それぞれに対する設定が記載されています。

フィールドの追加

フィールドの追加質問の際に、testフィールドを追加してみました。

New field name (press  to stop adding fields): test
Field type [string]:
Field length [255]:

New field name (press  to stop adding fields):

名前を入力後、フィールドのタイプと長さを質問されました。今回はデフォルトのまま進めたので、String:255となっています。決定後は、さらに追加するかを聞かれます。Entity作成後にはtestプロパティが追加され、titleプロパティと同様の設定が記載されていました。

空のリポジトリの作成

質問時にyesを入力して作成してみました。EntityディレクトリにPostRepository.phpというソースが作成されていました。

<?php

namespace My\BlogBundle\Entity;

use Doctrine\ORM\EntityRepository;

/**
 * PostRepository
 *
 * This class was generated by the Doctrine ORM. Add your own custom
 * repository methods below.
 */
class PostRepository extends EntityRepository
{
}

プロンプトに記載されていたとおり、空のリポジトリクラスが作成されていました。このクラスは、EntityRepositoryを継承しています。実装しているObjectRepositoryに定義されているのは、find系メソッドです。

<?php

namespace Doctrine\ORM;

use Doctrine\DBAL\LockMode;
use Doctrine\Common\Persistence\ObjectRepository;

 * An EntityRepository serves as a repository for entities with generic as well as business specific methods for retrieving entities.
class EntityRepository implements ObjectRepository
{
     * @var string
    protected $_entityName;

     * @var EntityManager
    protected $_em;

     * @var \Doctrine\ORM\Mapping\ClassMetadata
    protected $_class;

     * Initializes a new EntityRepository.
    public function __construct($em, Mapping\ClassMetadata $class)

     * Create a new QueryBuilder instance that is prepopulated for this entity name
    public function createQueryBuilder($alias)

     * Create a new Query instance based on a predefined metadata named query.
    public function createNamedQuery($queryName)

     * Clears the repository, causing all managed entities to become detached.
    public function clear()

     * Finds an entity by its primary key / identifier.
    public function find($id, $lockMode = LockMode::NONE, $lockVersion = null)

     * Finds all entities in the repository.
    public function findAll()

     * Finds entities by a set of criteria.
    public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)

     * Finds a single entity by a set of criteria.
    public function findOneBy(array $criteria)

    /**
     * Adds support for magic finders.
     *
     * @return array|object The found entity/entities.
     * @throws BadMethodCallException  If the method called is an invalid find* method
     *                                 or no find* method at all and therefore an invalid
     *                                 method call.
     */
    public function __call($method, $arguments)
    {
        if (substr($method, 0, 6) == 'findBy') {
            $by = substr($method, 6, strlen($method));
            $method = 'findBy';
        } else if (substr($method, 0, 9) == 'findOneBy') {
            $by = substr($method, 9, strlen($method));
            $method = 'findOneBy';
        } else {
            throw new \BadMethodCallException(
                "Undefined method '$method'. The method name must start with ".
                "either findBy or findOneBy!"
            );
        }

        if ( !isset($arguments[0])) {
            // we dont even want to allow null at this point, because we cannot (yet) transform it into IS NULL.
            throw ORMException::findByRequiresParameter($method.$by);
        }

        $fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));

        if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) {
            return $this->$method(array($fieldName => $arguments[0]));
        } else {
            throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by);
        }
    }

     * @return string
    protected function getEntityName()

     * @return EntityManager
    protected function getEntityManager()

     * @return Mapping\ClassMetadata
    protected function getClassMetadata()
}

見た感じですと、データ取得用ですね。マジックメソッド__callによって、取得条件のカラムを変更できるのかなと、cakeの知識を元に想像しています。createQueryBuilderで、SQLの作成もできそうです。

テーブルの作成

今まで作成したモデルをもとに、テーブルを作成できます。ただし、--format=phpで作成した場合は失敗します。--format=(yml, xml, annotation)であれば、問題なく作成できます。

#phpの場合
php app/console doctrine:schema:create
No Metadata Classes to process.
php app/console doctrine:schema:create
ATTENTION: This operation should not be executed in a production environment.

Creating database schema...
Database schema created successfully!

mysql> desc post;
+-----------+--------------+------+-----+---------+----------------+
| Field     | Type         | Null | Key | Default | Extra          |
+-----------+--------------+------+-----+---------+----------------+
| id        | int(11)      | NO   | PRI | NULL    | auto_increment |
| title     | varchar(255) | NO   |     | NULL    |                |
| body      | longtext     | NO   |     | NULL    |                |
| createdAt | datetime     | NO   |     | NULL    |                |
| updatedAt | datetime     | NO   |     | NULL    |                |
+-----------+--------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)