The DependencyInjection Component

In the previous chapter, we emptied the Simplex\Framework class by extending the HttpKernel class from the eponymous component. Seeing this empty class, you might be tempted to move some code from the front controller to it:

前の章では、同名のコンポーネントから HttpKernel クラスを拡張して、Simplex\Framework クラスを空にしました。この空のクラスを見て、いくつかのコードをフロント コントローラーからそれに移動したくなるかもしれません。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// example.com/src/Simplex/Framework.php
namespace Simplex;

use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel;
use Symfony\Component\Routing;

class Framework extends HttpKernel\HttpKernel
{
    public function __construct($routes)
    {
        $context = new Routing\RequestContext();
        $matcher = new Routing\Matcher\UrlMatcher($routes, $context);
        $requestStack = new RequestStack();

        $controllerResolver = new HttpKernel\Controller\ControllerResolver();
        $argumentResolver = new HttpKernel\Controller\ArgumentResolver();

        $dispatcher = new EventDispatcher();
        $dispatcher->addSubscriber(new HttpKernel\EventListener\ErrorListener(
            'Calendar\Controller\ErrorController::exception'
        ));
        $dispatcher->addSubscriber(new HttpKernel\EventListener\RouterListener($matcher, $requestStack));
        $dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8'));
        $dispatcher->addSubscriber(new StringResponseListener());

        parent::__construct($dispatcher, $controllerResolver, $requestStack, $argumentResolver);
    }
}

The front controller code would become more concise:

フロント コントローラーのコードはより簡潔になります。
1
2
3
4
5
6
7
8
9
10
11
// example.com/web/front.php
require_once __DIR__.'/../vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;

$request = Request::createFromGlobals();
$routes = include __DIR__.'/../src/app.php';

$framework = new Simplex\Framework($routes);

$framework->handle($request)->send();

Having a concise front controller allows you to have several front controllers for a single application. Why would it be useful? To allow having different configuration for the development environment and the production one for instance. In the development environment, you might want to have error reporting turned on and errors displayed in the browser to ease debugging:

簡潔なフロント コントローラーを使用すると、1 つのアプリケーションに対して複数のフロント コントローラーを使用できます。なぜ役に立つのでしょうか?たとえば、開発環境と本番環境で異なる構成を使用できるようにします。開発環境では、デバッグを容易にするために、エラー報告を有効にし、ブラウザーにエラーを表示することができます。
1
2
ini_set('display_errors', 1);
error_reporting(-1);

... but you certainly won't want that same configuration on the production environment. Having two different front controllers gives you the opportunity to have a slightly different configuration for each of them.

...しかし、本番環境で同じ構成が必要になることは確かにありません。 2 つの異なるフロント コントローラーを使用すると、それぞれにわずかに異なる構成を設定できます。

So, moving code from the front controller to the framework class makes our framework more configurable, but at the same time, it introduces a lot of issues:

したがって、コードをフロント コントローラーからフレームワーク クラスに移動すると、フレームワークがより構成しやすくなりますが、同時に多くの問題が発生します。
  • We are not able to register custom listeners anymore as the dispatcher is not available outside the Framework class (a workaround could be the adding of a Framework::getEventDispatcher() method);
    ディスパッチャーは Framework クラスの外では使用できないため、カスタム リスナーを登録することはできません (回避策として、Framework::getEventDispatcher() メソッドを追加することができます)。
  • We have lost the flexibility we had before; you cannot change the implementation of the UrlMatcher or of the ControllerResolver anymore;
    以前の柔軟性を失いました。 UrlMatcher または ControllerResolver の実装を変更することはできません。
  • Related to the previous point, we cannot test our framework without much effort anymore as it's impossible to mock internal objects;
    前のポイントに関連して、内部オブジェクトをモックすることは不可能であるため、フレームワークをテストするのに多大な労力を費やすことはできません。
  • We cannot change the charset passed to ResponseListener anymore (a workaround could be to pass it as a constructor argument).
    ResponseListener に渡された文字セットを変更することはできなくなりました (回避策として、コンストラクターの引数として渡すことができます)。

The previous code did not exhibit the same issues because we used dependency injection; all dependencies of our objects were injected into their constructors (for instance, the event dispatchers were injected into the framework so that we had total control of its creation and configuration).

依存性注入を使用したため、前のコードでは同じ問題は発生しませんでした。オブジェクトのすべての依存関係はコンストラクターに注入されました (たとえば、イベント ディスパッチャーはフレームワークに注入されたので、その作成と構成を完全に制御できました)。

Does it mean that we have to make a choice between flexibility, customization, ease of testing and not to copy and paste the same code into each application front controller? As you might expect, there is a solution. We can solve all these issues and some more by using the Symfony dependency injection container:

柔軟性、カスタマイズ、テストの容易さ、および同じコードを各アプリケーションフロントコントローラーにコピーアンドペーストしないことのいずれかを選択する必要があるということですか?ご想像のとおり、解決策があります。 Symfony の依存性注入コンテナーを使用することで、これらすべての問題とその他の問題を解決できます。
1
$ composer require symfony/dependency-injection

Create a new file to host the dependency injection container configuration:

依存性注入コンテナー構成をホストする新しいファイルを作成します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// example.com/src/container.php
use Simplex\Framework;
use Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher;
use Symfony\Component\HttpFoundation;
use Symfony\Component\HttpKernel;
use Symfony\Component\Routing;

$containerBuilder = new DependencyInjection\ContainerBuilder();
$containerBuilder->register('context', Routing\RequestContext::class);
$containerBuilder->register('matcher', Routing\Matcher\UrlMatcher::class)
    ->setArguments([$routes, new Reference('context')])
;
$containerBuilder->register('request_stack', HttpFoundation\RequestStack::class);
$containerBuilder->register('controller_resolver', HttpKernel\Controller\ControllerResolver::class);
$containerBuilder->register('argument_resolver', HttpKernel\Controller\ArgumentResolver::class);

$containerBuilder->register('listener.router', HttpKernel\EventListener\RouterListener::class)
    ->setArguments([new Reference('matcher'), new Reference('request_stack')])
;
$containerBuilder->register('listener.response', HttpKernel\EventListener\ResponseListener::class)
    ->setArguments(['UTF-8'])
;
$containerBuilder->register('listener.exception', HttpKernel\EventListener\ErrorListener::class)
    ->setArguments(['Calendar\Controller\ErrorController::exception'])
;
$containerBuilder->register('dispatcher', EventDispatcher\EventDispatcher::class)
    ->addMethodCall('addSubscriber', [new Reference('listener.router')])
    ->addMethodCall('addSubscriber', [new Reference('listener.response')])
    ->addMethodCall('addSubscriber', [new Reference('listener.exception')])
;
$containerBuilder->register('framework', Framework::class)
    ->setArguments([
        new Reference('dispatcher'),
        new Reference('controller_resolver'),
        new Reference('request_stack'),
        new Reference('argument_resolver'),
    ])
;

return $containerBuilder;

The goal of this file is to configure your objects and their dependencies. Nothing is instantiated during this configuration step. This is purely a static description of the objects you need to manipulate and how to create them. Objects will be created on-demand when you access them from the container or when the container needs them to create other objects.

このファイルの目的は、オブジェクトとその依存関係を構成することです。この構成ステップでは何もインスタンス化されません。これは、操作する必要があるオブジェクトとその作成方法の純粋に静的な説明です。オブジェクトは、コンテナからオブジェクトにアクセスするとき、またはコンテナが他のオブジェクトを作成するためにオブジェクトを必要とするときに、オンデマンドで作成されます。

For instance, to create the router listener, we tell Symfony that its class name is Symfony\Component\HttpKernel\EventListener\RouterListener and that its constructor takes a matcher object (new Reference('matcher')). As you can see, each object is referenced by a name, a string that uniquely identifies each object. The name allows us to get an object and to reference it in other object definitions.

たとえば、ルーターリスナーを作成するには、そのクラス名が Symfony\Component\HttpKernel\EventListener\RouterListener であること、およびそのコンストラクターがマッチャーオブジェクト (new Reference('matcher')) を取ることを Symfony に伝えます。ご覧のとおり、各オブジェクトは、各オブジェクトを一意に識別する文字列である名前によって参照されます。この名前により、オブジェクトを取得し、他のオブジェクト定義で参照することができます。

Note

ノート

By default, every time you get an object from the container, it returns the exact same instance. That's because a container manages your "global" objects.

デフォルトでは、コンテナからオブジェクトを取得するたびに、まったく同じインスタンスが返されます。これは、コンテナーが「グローバル」オブジェクトを管理するためです。

The front controller is now only about wiring everything together:

フロントコントローラーは、すべてを一緒に配線するだけです。
1
2
3
4
5
6
7
8
9
10
11
12
13
// example.com/web/front.php
require_once __DIR__.'/../vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;

$routes = include __DIR__.'/../src/app.php';
$container = include __DIR__.'/../src/container.php';

$request = Request::createFromGlobals();

$response = $container->get('framework')->handle($request);

$response->send();

As all the objects are now created in the dependency injection container, the framework code should be the previous simple version:

すべてのオブジェクトが依存性注入コンテナーで作成されるようになったため、フレームワーク コードは以前の単純なバージョンである必要があります。
1
2
3
4
5
6
7
8
// example.com/src/Simplex/Framework.php
namespace Simplex;

use Symfony\Component\HttpKernel\HttpKernel;

class Framework extends HttpKernel
{
}

Note

ノート

If you want a light alternative for your container, consider Pimple, a simple dependency injection container in about 60 lines of PHP code.

コンテナーの軽量な代替手段が必要な場合は、約 60 行の PHP コードで構成される単純な依存性注入コンテナーである Pimple を検討してください。

Now, here is how you can register a custom listener in the front controller:

次に、カスタム リスナーをフロント コントローラーに登録する方法を示します。
1
2
3
4
5
6
7
// ...
use Simplex\StringResponseListener;

$container->register('listener.string_response', StringResponseListener::class);
$container->getDefinition('dispatcher')
    ->addMethodCall('addSubscriber', [new Reference('listener.string_response')])
;

Besides describing your objects, the dependency injection container can also be configured via parameters. Let's create one that defines if we are in debug mode or not:

オブジェクトを記述するだけでなく、依存性注入コンテナーはパラメーターを介して構成することもできます。デバッグモードかどうかを定義するものを作成しましょう:
1
2
3
$container->setParameter('debug', true);

echo $container->getParameter('debug');

These parameters can be used when defining object definitions. Let's make the charset configurable:

これらのパラメーターは、オブジェクト定義を定義するときに使用できます。文字セットを設定可能にしましょう:
1
2
3
4
// ...
$container->register('listener.response', HttpKernel\EventListener\ResponseListener::class)
    ->setArguments(['%charset%'])
;

After this change, you must set the charset before using the response listener object:

この変更後、応答リスナー オブジェクトを使用する前に文字セットを設定する必要があります。
1
$container->setParameter('charset', 'UTF-8');

Instead of relying on the convention that the routes are defined by the $routes variables, let's use a parameter again:

ルートが $routes 変数によって定義されるという規則に依存する代わりに、パラメーターを再度使用しましょう。
1
2
3
4
// ...
$container->register('matcher', Routing\Matcher\UrlMatcher::class)
    ->setArguments(['%routes%', new Reference('context')])
;

And the related change in the front controller:

そして、フロントコントローラーの関連する変更:
1
$container->setParameter('routes', include __DIR__.'/../src/app.php');

We have barely scratched the surface of what you can do with the container: from class names as parameters, to overriding existing object definitions, from shared service support to dumping a container to a plain PHP class, and much more. The Symfony dependency injection container is really powerful and is able to manage any kind of PHP class.

コンテナでできることの表面をかじっただけです。パラメータとしてのクラス名から、既存のオブジェクト定義のオーバーライド、共有サービスのサポート、プレーンな PHP クラスへのコンテナのダンプなど、さまざまです。 Symfony 依存性注入コンテナーは非常に強力で、あらゆる種類の PHP クラスを管理できます。

Don't yell at me if you don't want to use a dependency injection container in your framework. If you don't like it, don't use it. It's your framework, not mine.

フレームワークで依存性注入コンテナーを使用したくない場合でも、怒鳴らないでください。気に入らない場合は使用しないでください。それは私のフレームワークではなく、あなたのフレームワークです。

This is (already) the last chapter of this book on creating a framework on top of the Symfony components. I'm aware that many topics have not been covered in great details, but hopefully it gives you enough information to get started on your own and to better understand how the Symfony framework works internally.

これは、Symfony コンポーネントの上にフレームワークを作成する本書の (すでに) 最後の章です。多くのトピックが詳細にカバーされていないことは承知していますが、自分で始めて、Symfony フレームワークが内部でどのように機能するかをよりよく理解するのに十分な情報が得られることを願っています.

Have fun!

楽しむ!