PHP Mentors — Practical Symfony #8: Symfonyのカーネルの役割とバンドルシステム

1.5M ratings
277k ratings

See, that’s what the app is perfect for.

Sounds perfect Wahhhh, I don’t wanna

Practical Symfony #8: Symfonyのカーネルの役割とバンドルシステム

今回はSymfonyのさまざまな特徴の根幹をなしているカーネルについて紹介します。

カーネル(Kernel)は、HttpKernelコンポーネント内の主に2つのインターフェイス、2つのクラスで構成されています。

  • HttpKernelInterface
  • KernelInterface
  • Kernel
  • HttpKernel

image

図ではKernelInterfaceやKernelクラスのメソッドは省略しています。

アプリケーションの処理を抽象化したHttpKernelInterface

HttpKernelInterfaceはとても単純で、handle()メソッドのみが宣言されています。handle()メソッドは「何か入力を受け取り、それを処理して出力を返す」という、アプリケーションの処理そのものを抽象化したインターフェイスといえるでしょう。

image

このように単純にhandle()メソッドで入力を処理し、出力を返すという仕組みを持ったものがカーネルの「処理の抽象化」部分です。コードは以下のようになっています。

vendor/symfony/src/Symfony/Component/HttpKernel/HttpKernelInterface.php

<?php
// snip

/**
 * HttpKernelInterface handles a Request to convert it to a Response.
 *
 * @author Fabien Potencier 
 *
 * @api
 */
interface HttpKernelInterface
{
    const MASTER_REQUEST = 1;
    const SUB_REQUEST = 2;

    /**
     * Handles a Request to convert it to a Response.
     *
     * When $catch is true, the implementation must catch all exceptions
     * and do its best to convert them to a Response instance.
     *
     * @param  Request $request A Request instance
     * @param  integer $type    The type of the request
     *                          (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST)
     * @param  Boolean $catch   Whether to catch exceptions or not
     *
     * @return Response A Response instance
     *
     * @throws \Exception When an Exception occurs during processing
     *
     * @api
     */
    function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true);
}

なお、githubにあるsymfony/HttpKernelのリポジトリにあるREADMEでは、HttpKernelInterfaceは次のように説明されています。

HttpKernelInterface is the core interface of the Symfony2 full-stack framework (訳:HttpkernelInterfaceは、Symfony2フルスタックフレームワークのコアインターフェイスである)

このような抽象度の高いインターフェイスに「Http」が付いていることは、筆者には少し違和感があります。Webの世界とは切り離した一般的なアプリケーションとしてのインターフェイスである方が望ましいのではないかと考えています。

Symfonyの世界を抽象化したKernelInterface

HttpKernelInterfaceと比べてKernelInterfaceには多くのメソッドが宣言されています。Kernelの起動や終了に関わるinit()やboot(), shutdown(), バンドルの処理に関連するregisterBundles(), コンテナを取得するためのgetContainer()等です。KernelInterfaceの先頭のメソッドとしてregisterBundles()が置かれていることからも、バンドルシステムを扱うことを主目的としている意図がうかがえます。主な機能は、サービスコンテナを生成することと、バンドルの取りまとめを行うことです。

image

vendor/symfony/src/Symfony/Component/HttpKernel/KernelInterface.php

<?php
// snip

/**
 * The Kernel is the heart of the Symfony system.
 *
 * It manages an environment made of bundles.
 *
 * @author Fabien Potencier 
 *
 * @api
 */
interface KernelInterface extends HttpKernelInterface, \Serializable
{
    /**
     * Returns an array of bundles to registers.
     *
     * @return array An array of bundle instances.
     *
     * @api
     */
    function registerBundles();

    /**
     * Loads the container configuration
     *
     * @param LoaderInterface $loader A LoaderInterface instance
     *
     * @api
     */
    function registerContainerConfiguration(LoaderInterface $loader);

    /**
     * Boots the current kernel.
     *
     * @api
     */
    function boot();

    /**
     * Shutdowns the kernel.
     *
     * This method is mainly useful when doing functional testing.
     *
     * @api
     */
    function shutdown();

    /**
     * Gets the registered bundle instances.
     *
     * @return array An array of registered bundle instances
     *
     * @api
     */
    function getBundles();

// snip

    /**
     * Gets the current container.
     *
     * @return ContainerInterface A ContainerInterface instance
     *
     * @api
     */
    function getContainer();

// snip
}

バンドルシステムが後付けの拡張方式ではなく、Symfonyの世界で一級市民として扱われていることは、Symfonyを理解する上で重要なポイントとなります。凝集度の高いコンポーネントとしてのバンドルを複数集めてアプリケーションの機能を構成し、サービスコンテナを使ってバンドル間の機能を横断的に結びつけているというのが、Symfonyの根幹をなす構造です。

アプリケーション実行時のカーネルとバンドル

アプリケーションの実行時には、カーネルに登録されたバンドル群とサービスコンテナが活躍します。カーネル処理の本体ではEventDispatcher機構が用意され、Webアプリケーションを処理するためのイベントフローが実行されます。カーネル側は決められたわずかなイベントを特定の流れで呼び出す程度の処理しか行いません。イベントを監視して実際の処理を行うのは、バンドル側の責務となっています。サービスコンテナを準備してあるため、イベントの処理時に必要となるさまざまなツールを準備するのは、カーネルの責務ではありません。例えば多くのコントローラの処理では、データベースを扱うためにDoctrineの機能が必要になりますが、サービスコンテナ経由でDoctrineのサービスオブジェクトを取得できるようになっているため、カーネル側でDoctrineサービスオブジェクトの構成を準備するといったことはありません。

image

実際にはKernelオブジェクトがHTTPの処理用にHttpKernelを生成し、Requestの処理をHttpKernelへ引き渡します。HttpKernelのhandle()およびその実体であるhandleRaw()内に、イベントフローがあります。ルーティングによるコントローラの決定、コントローラの実行等、フレームワークとしての処理の骨格が、ごくシンプルなコードで記述されていることが分かります。

vendor/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php

<?php
// snip
/**
 * Handles a request to convert it to a response.
 *
 * Exceptions are not caught.
 *
 * @param Request $request A Request instance
 * @param integer $type    The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST)
 *
 * @return Response A Response instance
 *
 * @throws \LogicException If one of the listener does not behave as expected
 * @throws NotFoundHttpException When controller cannot be found
 */
private function handleRaw(Request $request, $type = self::MASTER_REQUEST)
{
    // request
    $event = new GetResponseEvent($this, $request, $type);
    $this->dispatcher->dispatch(KernelEvents::REQUEST, $event);

    if ($event->hasResponse()) {
        return $this->filterResponse($event->getResponse(), $request, $type);
    }

    // load controller
    if (false === $controller = $this->resolver->getController($request)) {
        throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". Maybe you forgot to add the matching route in your routing configuration?', $request->getPathInfo()));
    }

    $event = new FilterControllerEvent($this, $controller, $request, $type);
    $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event);
    $controller = $event->getController();

    // controller arguments
    $arguments = $this->resolver->getArguments($request, $controller);

    // call controller
    $response = call_user_func_array($controller, $arguments);

    // view
    if (!$response instanceof Response) {
        $event = new GetResponseForControllerResultEvent($this, $request, $type, $response);
        $this->dispatcher->dispatch(KernelEvents::VIEW, $event);

        if ($event->hasResponse()) {
            $response = $event->getResponse();
        }

        if (!$response instanceof Response) {
            $msg = sprintf('The controller must return a response (%s given).', $this->varToString($response));

            // the user may have forgotten to return something
            if (null === $response) {
                $msg .= ' Did you forget to add a return statement somewhere in your controller?';
            }
            throw new \LogicException($msg);
        }
    }

    return $this->filterResponse($response, $request, $type);
}

まとめ

Symfonyのカーネルとバンドルシステムについて、それぞれの責務の概要を紹介しました。このカーネルとバンドルシステムによって、カーネルの責務がコンパクトに抑えられ、個別の関心事をバンドルに分散することに成功しています。開発者の手にある制御領域が大きく、柔軟な開発が可能なフレームワークとなっていることがわかるでしょう。

参考

symfony practical.symfony

See more posts like this on Tumblr

#symfony #practical.symfony