怠い RSSフィード

自動書記モードです twitter
 | 

2010-12-04

Symfony2で強制的にhello worldと表示するバンドルを作った話 09:02

この記事は、Symfony アドベントカレンダー 2010 に参加しています。

概要

ごく最近にプレビューリリース4が出たSymfony2ですが、最近になってSymfony2勉強会等に参加したりid:Fivestarから話を聞いたりしてしてようやくSymfony2を触るようになりました。個人的にはSymfony2の最も大きな特徴はDIコンテナバンドルという概念が導入されていることではないかと思います。 symfony1系からあるEventDispatcherに加え、Symfony2でDIコンテナが追加されたことにより、Symfony2のフレームワークとしての柔軟性、アプリケーションの設計のしやすさはより上がっています。また、バンドルという概念が導入されたことによりSymfony2ユーザコミュニティによって再利用可能なものが多くなるという期待も持てます。

この記事では、HelloWorldバンドルというごく単純なバンドルを解説していきます。 単純なバンドルですが、Symfony2フレームワークのルーティング処理などを吹き飛ばして強制的にhello worldと表示するというものです。 このHelloWorldバンドルがどのようにしてフレームワークのコアの処理を改変しているのかを実際のコードと共に説明していきたいと思います。

バンドル

まず、バンドルの説明をします。

Symfony2でDIコンテナと共に新しく導入されたバンドルは、いわばsymfony1のプラグインのように他のアプリケーションに組み込まれることを前提としたモジュールのようなものです。一度作成されたバンドルは、他のアプリケーションから利用することができます。ただし、プラグインと違う点としてあるのは、アプリケーションを書く際にもバンドルを書く必要があるということです。というのもSymfony2では何もかもをバンドルとして実装する仕組みになっているからです。

バンドルは、コンソールで使うコマンド、DIコンテナ設定、コントローラ、ルーティング設定、テンプレート、ライブラリなど様々なものを持つことができます。Symfony2フレームワークでアプリケーションを開発する際には、アプリケーションに必要なバンドルを組み合わせつつアプリケーション自身のバンドルを開発することになります。

Symfony2では、フレームワークのコア機能郡ですらもSymfony\Bundle\FrameworkBundleとして提供されています。

何もかもがバンドルとして実装することで、再利用しやすいコードを増やしていくのがSymfony2の戦略のようです。 実際にDiscover 1140 bundles for Symfony2 | KnpBundlesではすでにバンドルがいくつも公開されており、それらを利用することができます。

HelloWorldバンドル

それではHelloWorldバンドルを説明していきます。とても小さなバンドルなのでソースコードも全て載せていきます。

予めバンドルの流れを説明すると以下のようになります。

  1. バンドルとしてインポートする
  2. DIコンテナに利用するクラスを登録する
  3. ルーティング処理を横取りして強制的にコントローラを決定する
  4. コントローラがhello worldを返す

フレームワークのルーティング処理を横取りしてhello worldと表示するだけのバンドルです。

単純ですが、フレームワークのコアのコードをいじらずにフレームワークのコアの振る舞いを変えています。

環境

Symfony2の実行環境としてPR4のサンドボックスが公開されていますのでこの記事ではそれを利用します。

レイアウト

HelloWorldバンドルのディレクトリのレイアウトを示します。

サンドボックスのsrcディレクトリに以下のようなレイアウトが展開されます。

Application/
`-- HelloWorldBundle
    |-- DependencyInjection
    |   `-- HelloWorldExtension.php
    |-- HelloWorld.php
    `-- HelloWorldBundle.php

ファイル自体は三つしかありません。

HelloWorldBundle

Application\HelloWorldBundleクラスは、実行時にアプリケーションにバンドルとして追加するのに必要なインターフェイスを提供します。 とはいえ、このクラスではSymfony\Component\HttpKernel\Bundloe\Bundleクラスを継承しているだけです。

<?php
namespace Application\HelloWorldBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class HelloWorldBundle extends Bundle
{
}

単にバンドルとしてインポートするのに必要なファイルというだけで、このバンドルでは特になにかメソッドをオーバーライドする必要はありませんでした。

HelloWorldExtension

DependencyInjectionディレクトリ以下にはHelloWorldExtension.phpがあります。

このファイルはDIコンテナの設定を行うためのクラスをおさめたファイルで、エクステンションと呼ばれます。

アプリケーションがバンドルとして追加された際に、どんなバンドルでもDependencyInjectionディレクトリ以下の*Extension.phpというファイルはDIコンテナの設定をするものとして自動的に利用されます。

<?php

namespace Application\HelloWorldBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\Extension\ExtensionInterface,
    Symfony\Component\DependencyInjection\Definition,
    Symfony\Component\DependencyInjection\ContainerBuilder;

class HelloWorldExtension implements ExtensionInterface
{
    // 設定ファイルでのプレフィクス
    function getAlias()
    {
        return 'helloworld';
    }

    function load($tag, array $config, ContainerBuilder $builder)
    {
        $def = new Definition('Application\HelloWorldBundle\HelloWorld', array());
        $def->addTag('kernel.listener', array('priority' => 1)); // 'kernel.listener' タグの追加

        return $builder->setDefinition('hook.listener', $def);
    }

    // 今回は不要
    function getNamespace()
    {
        return null;
    }

    // 今回は不要
    function getXsdValidationBasePath()
    {
        return null;
    }
}

この中のloadメソッドでは、Application\HelloWorldBundle\HelloWorldクラスをDIコンテナに登録しています。

DIコンテナの設定には、XML, YAML, INIファイルなどでも行えるのですが、今回は簡単にPHPで直接設定しています。

Symfony2のDIコンテナでは、クラスを登録する際にタグを設定することができます。ここで設定された "kernel.listener" タグは、Symfony2フレームワークのコアのふるまいを司るイベントディスパッチャの設定をするオブジェクトのために用意されています。

Symfony2がリクエストからレスポンスを返す仕組みのすべてはこのイベントディスパッチャを通じて行われるため、このイベントディスパッチャをうまく設定することによってフレームワークのルーティング処理などを先に横取りすることができます。その具体的にな処理は次のHelloWorldクラスで行われます。

HelloWorldクラス
<?php

namespace Application\HelloWorldBundle;

use Symfony\Bundle\FrameworkBundle\EventDispatcher,
    Symfony\Component\EventDispatcher\Event,
    Symfony\Component\HttpFoundation\Response,
    Symfony\Component\HttpKernel\HttpKernelInterface;

class HelloWorld
{
    function register(EventDispatcher $dispatcher, $priority)
    {
        $dispatcher->connect('core.request', array($this, 'handleRequestEvent'), $priority);
    }

    /**
     * @param Symfony\Component\EventDispatcher\Event
     * @return null
     */
    function handleRequestEvent(Event $event)
    {
        if ($event->get('request_type') === HttpKernelInterface::MASTER_REQUEST) {
            $event->get('request')->attributes->set('_controller' , __CLASS__ . '::hello');
            $event->get('request')->attributes->set('hoge' ,'world');
        }
    }

    /**
     * @return Symfony\Component\HttpFoundation\Response
     */
    static function hello($hoge)
    {
        $response = new Response();
        $response->setContent('hello ' . $hoge);
        return $response;
    }

}

このクラスでは、イベントディスパッチャの設定とコントローラの決定、hello worldと返すコントローラという三つの処理を持っています。

registerメソッドは、DIコンテナに 'kernel.lister' というタグをつけて設定することでフレームワーク側から自動的に呼ばれるメソッドです。このメソッドの中では "core.request" というイベントが起きた時にhandleRequestEventというメソッドを呼び出すように設定しています。

handleRequestEventメソッドでは、ルーティング処理をする前に実行するコントローラを勝手に決める処理を記述します。 フレームワークのルーティング処理を横取りしているわけです。コントローラは強制的にhelloメソッドに決定されます。

Symfony2フレームワークは内部でコントローラを呼び出し、それが返すResponseオブジェクトを元にHTTPレスポンスを生成します。

これがルーティング処理を吹っ飛ばしてhello worldと表示するバンドルの正体です。

インポート

最後にアプリケーションで実際にこのHelloWorldバンドルを使う方法を解説します。

先ほどのPR4のサンドボックスのapp/AppKernel.phpにあるAppKernel::registerBundles()メソッドに以下のようにHelloWorldバンドルを追加します。

<?php
    public function registerBundles()
    {
        $bundles = array(
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            new Symfony\Bundle\TwigBundle\TwigBundle(),

            // enable third-party bundles
            new Symfony\Bundle\ZendBundle\ZendBundle(),
            new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
            new Symfony\Bundle\DoctrineBundle\DoctrineBundle(),
            //new Symfony\Bundle\DoctrineMigrationsBundle\DoctrineMigrationsBundle(),
            //new Symfony\Bundle\DoctrineMongoDBBundle\DoctrineMongoDBBundle(),

            // HelloWorldBundleの追加
            new Application\HelloWorldBundle\HelloWorldBundle(),
        );

        if ($this->isDebug()) {
            $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
        }

        return $bundles;
    }

次に app/config/config.yml に、HelloWorldExtensionを読み込むための設定を以下のように記述します。

app.config:
    charset:       UTF-8
    error_handler: null
    csrf_secret:   xxxxxxxxxx
    router:        { resource: "%kernel.root_dir%/config/routing.yml" }
    validation:    { enabled: true, annotations: true }
    templating:    {} #assets_version: SomeVersionScheme
    session:
        default_locale: en
        lifetime:       3600
        auto_start:     true

# Twig Configuration
twig.config:
    debug:            %kernel.debug%
    strict_variables: %kernel.debug%

helloworld.use: ~ # ここに追加

これでバンドルを利用するための設定は終わりです。

ブラウザから サンドボックスのwebディレクトリ以下にアクセスしてください。 ルーティング設定をいくらやっても必ずhello worldが表示されるアプリケーションの完成です。

というわけで単純なHelloWorldバンドルの解説をしました。

Symfony2ではDIコンテナの設定やイベントディスパッチャの設定によってフレームワークのコアの振る舞いも変えることができるということがわかると思います。 また、バンドルとして実装することで簡単に再利用可能なコードを生み出すことができるということもわかったと思います。

まとめ

  • Symfony2ではDIコンテナとイベントディスパッチャによってフレームワークの柔軟性が上がっている
  • Symfony2では何もかもを再利用しやすいバンドルとして実装する

Symfony Advent 2010であなたの記事を公開してみませんか?

Symfony Advent 2010では12月1日から12月24日までを使って日替わりでsymfonyでイイなと思った小さなtipsから内部構造まで迫った解説などをブログ記事にし て公開していくイベントです。

参加についてはATNDで参加表明の上、Google GroupのSymfony Advent 2010に追加リクエストを送信ください。

 |