Symfony2とDoctrine(データベースの扱い方)

Symfony2でデータベースを扱うときは、データベースを単独で扱うということは滅多にないと思いますが、単独で扱うならPHPとかSymfony2とかはいらないです。ということはつまりはデータベースと様々なその他のシステムが連動するということになるのが通常ということで、Symfony2ではその「連動」の一貫したシステムをより容易に構築してくれるというわけです。
その連動とはちょっとややこしいですが、こういうことになります。

  • データベース(Symfony2ではデフォルトがMySQL)
  • データベースの永続化(エンティティーとオブジェクト)

コントローラではデータベースから呼び出したデータそのものを扱うことはありません。(扱ってもよいのですが、それは野暮な話です。)コントローラでは常にデータベースから取り出したデータをオブジェクト化したもの(永続化したもの)を扱います。つまりはコントローラはデータベースを意識することがなくなるというわけです。
データベースの様々なデータをオブジェクト化する役目を果たすのがDoctrineということになります。
http://symfony.com/doc/current/reference/configuration/doctrine.html

データベースの設定をする

いわゆるあれです。DBユーザーとDBパスワードとDB名とDBホストを設定するあれです。指定があればポートの設定とかもします。
app/config/parameters.yml
yamlで設定します。(古い情報だとparameters.iniとかになっているかもですが、symfony2.6.1ではparameters.ymlになってました。)ここでは送信メールサーバーの情報も同時に設定するファイルなので見てわかる人は一緒に設定しちゃうのがよいでしょう。
parameters:
database_driver: pdo_mysql
database_host: localhost
database_port: null
database_name: dbname
database_user: dbuser
database_password: dbpassword

設定情報が正しければこれで問題なく接続準備ができていることになります。
また逆にコマンドユーザーにデータベース作成権限があるのなら、このままこの情報を使ってデータベースの作成ができます。
php app/console doctrine:database:create
これもまた楽ちんです。が、私はあんまり使いません。
しかし、結構深いところで設定するんですね。私としてはバンドル単位で設定してもいいんじゃないかと思うのですが、そういう設定の方法があったら誰か教えてください。

1. エンティティーにアノテーションを作成

データベースをPHPに取り込んでオブジェクト化するときエンティティに指定したプロパティーとデータベースカラムを結びつけようとします。つまりエンティティープロパティーはデータベースレコードのそれぞれのカラムとイコールになるので、コントローラからはこのエンティティーを見ればよいというわけです。
このエンティティのプロパティには、アノテーションと呼ばれるデータのメタ情報を付加します。こうすることでエンティティーはデータベースそのものになりすますことができたり、エンティティからデータベーステーブルと正しく作成できたりします。
http://docs.symfony.gr.jp/symfony2/book/doctrine.html#book-doctrine-field-types
まず最初にエンティティを作成しておきます。それからDoctrineのORMのマッピングコンポーネントをuseしておきます。
// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
......

データベースとエンティティのマッピングはDoctrine\ORM\Mappingにまかせておけばいいのでとても楽ちんです。しかしこれを今まで手作業でやっていたと思うとうんざりというぐらいとても便利なモジュールです。さて、as ORM;としてuseしておりますが、これはマッピングする際の記号なので何でもいいです。慣例として大文字数文字程度といったところです。

で、エンティティの各プロパティーにアノテーションを書き込んでゆきます。このコメントアウトされたデータがマッピングされる際の重要な情報(アノテーション)になります。
// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="product")
*/
class Product {
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;

/**
* @ORM\Column(type="string", length=64)
*/
public $name;

/**
* @ORM\Column(type="string", length=128)
*/
protected $email;
......

アノテーションはデータベーステーブルのデータ型と直結しています。なのでデータベースの設定をするかのように設定してゆきます。すでに充分なほどの型が揃っているので設定で問題ありというのはまずないでしょう。デシマルとかもうやめて!といった感じです。が、もう面倒臭いから全部textでいいや!という投げやりなEC-CUBEみたいなことにはならないでしょう。
@ORM\Column(type="string", length=64)
ここでは@ORMによってマッピングされたカラム(\Column)に文字列型で64バイトの長さを指定(type="string", length=64)しています。オートインクリメント型のIDなどの指定は、
@ORM\GeneratedValue(strategy="AUTO")
という風に書きます。いろいろな書き方があるのでこれはドキュメントを見た方が早いです。それぞれのプロパティーにアノテーションを作成しましょう。

2. エンティティにゲッターとセッターを自動作成

さて、エンティティの肝はプロパティーがデータベースの値と直結しているというものの他に値をセットする、値を取得するという大切な機能があります。JAVAをやっている人ならいつも使っている概念ですがPHPではあまり馴染みのあるものではありません。ですが、Symfony2ではその方法論をそのままそっくり真似ています。というのも大変便利な機能だからです。
ここでもゲッターとセッターを設定します。
ゲッターとセッターは自力で書くことも可能ですがSymfony2ではプロパティーから自動的にゲッターとセッターを設定するコマンドが用意されているので、それを使わない手はないでしょう。
php app/console doctrine:generate:entities [NAMESPACE][BUNDLENAME]Bundle
という風にコマンドをうちます。
$php app/console doctrine:generate:entities AcmeStoreBundle
Generating entities for bundle "AcmeStoreBundle"
> backing up Product.php to Product.php~
> generating Acme\StoreBundle\Entity\Product

殆どエラーがなく安定して作れるので大変便利です。Product.php~というバックアップファイルが自動的に作られます。もし動きに問題がなかったらこのバックアップファイルは消してしまっても大丈夫です。またこのコマンドは何度実行しても同じことが上書きされるだけなので安全です。
コマンドを実行してから言うのも何ですが、プロパティーがprotectedの時にゲッターとセッターが必要になります。安全にデータを出し入れするために明示的にゲッターとセッターを使うということになります。publicの値は基本的にはそのままオブジェクトから取り出すことができます。時にSymfony2ではこのことに何も言及していませんが、いわゆる個人情報的な会員情報などではprotectedを、通常のWEBページ情報で表示される公開情報などはpublicを、というのが常識といえば常識なんじゃないかというところです。
ちなみにゲッターとセッターは、エンティティクラス内のプロパティ(データベースの値)に対するメソッドになります。なので、
public function getEmail() {
return $this->email;
}
public function setEmail($email) {
$this->email = $email;
}
という非常に簡素なものです。これで値の出し入れは自由になります。

3. エンティティーからテーブルを作成(データベーススキーマの作成)

ここがSymfony2のよくできている部分になります。エンティティの準備が整ったらこれらの情報(プロパティー、アノテーション)からデータベーステーブルを作成します。
テーブル名は、
/**
* @ORM\Entity
* @ORM\Table(name="[TABLENAME]")
*/
としてオプションで設定が可能です。もし省略した場合はエンティティークラス名がそのままテーブル名になります。とてもよくできています。
コマンドはこんなものです。
$php app/console doctrine:schema:update --force
このコマンドはSymfony2内にあるエンティティすべてに対して行われるコマンドです。作りかけのエンティティがある中で実行するとエラーになりますが、完成したエンティティに対しては何度実行してもよいです。というのも、プロパティーを追加した時点で実行することも考慮しているからです。
さて、実際に実行してみると、
[Doctrine\ORM\Mapping\MappingException]
No identifier/primary key specified for Entity "Core\LoginBundle\Entity\Product". Ev
ery Entity must have an identifier/primary key.

というエラーが出ることがあります。その通りでprimary keyがなければならないって言っています。Symfony2のデータベーステーブルは必ずプライマリーキーがないとダメだということです。非常にフレームワーク臭い制約ですが、是非文句を言わず従ってください。たいていのテーブルにはプライマリーキーをつけた方がいいんです。
さて、このコマンドを実行して気がついたことが幾つかあるかと思いますが、実はアノテーション付きのプロパティーしか読み込んでくれません。すなわちアノテーションがついているプロパティーのみのテーブルカラムを作成します。理屈で考えてエンティティ内に存在していてデータベースに存在しない値というのはちょっとおかしなものなので、よっぽどの理由がない限りすべてのプロパティーにアノテーションを付けるというのがよいかと思います。(勿論パスワードなどの確認のための再入力フィールドなどのデータそのものは必要ないですが。)

マッピングの@ORMの部分が間違っていたら以下のようなエラーが出ます。as [XXX]と@XXXが違っていたりすると出ちゃうので見直しましょう。
[Doctrine\Common\Annotations\AnnotationException]
[Semantical Error] The annotation "@ORM\Column" in property Core\LoginBundle\Entity\Product::$name was never imported. Did you maybe forget to add a "use" statement for
this annotation?

しかしながら結果的にはこれでドン!でテーブルが作成できてしまうのでとてもよいです。楽しいです。