Symfony2入門(1) でとりあえずインストールを完了させました。 なので次はCakePHPでもおなじみのCRUDを作ろうと思います。

モデルを作るその前に

Symfony2にはMVCよりも上位の概念となる 「Bundle(バンドル)」 が存在します。 MVCや、その他必要なコンポーネントやアセット(画像やCSS, Javascript等)をひとまとめにした物です。 このBundleの存在によって、一連の処理をプラグインのようにして使う事が可能になります。

アプリケーションバンドルを作る

とりあえずBundleを作らなければ何も始まらないので、アプリケーション用のバンドルを作ります。

$ cd /path/to/symfony-project/
$ php app/console generate:bundle

補足: Symfonyでの開発中は php app/console をCakePHPと比べるとかなり頻繁に使います。 面倒に感じたら php app/console --shell コマンドを1度実行すると、Symfony専用のShellを起動でき、以降は php app/console を省略できますのでオススメです。

# {ベンダー名}/Bundle/{バンドル名}Bundle
Bundle namespace: Quartet/Bundle/BlogBundle [ENTER]

Bundle name [QuartetBlogBundle]: [ENTER]

Target directory [/usr/local/Cellar/httpd/2.2.25/share/apache2/htdocs/symfony-blog-tuts/src]: [ENTER]

# 設定ファイルは基本的にYaml, XML, PHP, Annotation の4種類の中から好きに選べます。
# 今回はファイル間の移動を減らす為にAnnotationを使いますが、基本的に何でもいいです。
Configuration format (yml, xml, php, or annotation): annotation [ENTER]

# 以降エンター連打でOK
To help you get started faster, the command can generate some
code snippets for you.

Do you want to generate the whole directory structure [no]?  [ENTER]

Do you confirm generation [yes]? [ENTER]
Generating the bundle code: OK
Checking that the bundle is autoloaded: OK
Confirm automatic update of your Kernel [yes]? [ENTER]
Enabling the bundle inside the Kernel: OK
Confirm automatic update of the Routing [yes]? [ENTER]
Importing the bundle routing resource: OK

symfony-blog-tuts|master ⇒

これでバンドルのひな形作成は完了です。

モデルを作る

CakePHPではデータベース上に物理テーブルを作ってから、自動的にモデルのソースを生成しましたが、Symfony2(というかDoctrine2)は 真逆 です。

モデルのソースコード上にテーブルスキーマを定義し、物理テーブルのスキーマを自動更新します。

  • CakePHP: テーブルを作る => モデルを生成
  • Symfony: モデルを作る => テーブルを生成

モデルのソースコードもコンソールから生成できるので、やってみます。

$ php app/console doctrine:generate:entity

The Entity shortcut name: QuartetBlogBundle:Post [Enter]

Configuration format (yml, xml, php, or annotation) [annotation]: [Enter]

Available types: array, simple_array, json_array, object,
boolean, integer, smallint, bigint, string, text, datetime, datetimetz,
date, time, decimal, float, blob, guid.

# id は勝手に追加されるのでID以外の項目を定義します。
# 今回は Post モデルに、title, content, created を設定します。
New field name (press <return> to stop adding fields): title [Enter]
Field type [string]: [Enter]
Field length [255]: [Enter]

New field name (press <return> to stop adding fields): content [Enter]
Field type [string]: text [Enter]

New field name (press <return> to stop adding fields): created [Enter]
Field type [string]: datetime [Enter]

New field name (press <return> to stop adding fields): [Enter]

# リポジトリはDAOのような物です。マスターテーブルは大体必要になってくるので、作っておきましょう。
Do you want to generate an empty repository class [no]? yes [Enter]

Do you confirm generation [yes]? [Enter]

これでPostモデルが作成されました。

CRUD を生成する。

$ php app/console doctrine:generate:crud

# {バンドル名}:{モデル名}
You must use the shortcut notation like AcmeBlogBundle:Post [ENTER]

Do you want to generate the "write" actions [no]? yes [ENTER]

Determine the format to use for the generated CRUD.

Configuration format (yml, xml, php, or annotation) [annotation]: [ENTER]

Determine the routes prefix (all the routes will be "mounted" under this
prefix: /prefix/, /prefix/new, ...).

# URLプレフィックスの指定
Routes prefix [/post]: [ENTER]

Do you confirm generation [yes]?  [ENTER]

表示してみましょう

ブラウザで http://localhost/symfony-blog-tuts/web/app_dev.php/post を開きます。

85b2ee6f-d3f9-3b00-47d3-47935031a2a0-1024x893

例外が発生してしまいました。

物理テーブルはまだ作ってないので当たり前ですね。 少し話がそれますが、Symfonyのスタックトレースはとてつもなく親切なので、わざわざソースの行数を追っかけなくてもView上で確認できます。

物理テーブルのスキーマを更新します。

# スキーマ作成時
php app/console doctrine:schema:create

# スキーマ更新時
php app/console doctrine:schema:update

まさかのこれだけです。

実際にどんなSQLが発行されるか確認する際は --dump-sql オプションを付けるとSQLが表示されます。

表示してみましょう(再)

83afbf57-7cfb-46ed-c598-4b46faa5221f-1024x893

CRUD全ての処理が実行できる事が確認できます。

フォームのカスタマイズ

時刻の入力が面倒なので、CRUD生成時のフォームの created を自動入力にします。

eed7e062-ad0f-4aef-cde6-2ac271be616e

Postフォームオブジェクト(PostType)の確認

フォームとエンティティの関係

<?php
// PostController.php
/**
 * 新規投稿
 */
public function newAction()
{
    $entity = new Post();
    // 空のエンティティからフォームを作る
    $form   = $this->createCreateForm($entity);

    return array(
        'entity' => $entity,
        'form'   => $form->createView(),
    );
}
/**
 * @フォームを作る
 */
private function createCreateForm(Post $entity)
{
    // フォームとモデルをバインドする
    $form = $this->createForm(new PostType(), $entity, array(
        'action' => $this->generateUrl('post_create'),
        'method' => 'POST',
    ));

    $form->add('submit', 'submit', array('label' => 'Create'));

    return $form;
}

フォームの定義

<?php
// PostType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('title')
        ->add('content')
  //    ->add('created') # => 削除
    ;
}

フォームから削除した項目の初期値を設定する処理

<?php
// Post.php
class Post
{
    public function __construct()
    {
        // 初期値として現在時刻を設定
        $this->created = new \DateTime();
    }
}

入力画面を表示する(再)

2dd4aaf7-f32c-b26c-01ce-b5d56b9cda10

3f3b95eb-6350-5742-aa73-3568f6421d22

作成日時が自動入力されました。

Doctrine LifeCycleCallbacksを使う方法

更新日時はどうすんの? もちろんこれも定義しておけば自動で実行させる事ができます。 @PreUpdate

<?php
/**
 * @PreUpdate
 */
public function setUpdateDate
{
    $this->updateDate = new \DateTime();
}

Symfony EventDispatcherを使う

SymfonyのServiceContainerはDoctrineのコールバックにサービスをバインドさせる事ができます。