Building your own Framework with the MicroKernelTrait

The default Kernel class included in Symfony applications uses a MicroKernelTrait to configure the bundles, the routes and the service container in the same class.

Symfony アプリケーションに含まれるデフォルトのカーネル クラスは、MicroKernelTrait を使用して、バンドル、ルート、およびサービス コンテナーを同じクラスに構成します。

This micro-kernel approach is flexible, allowing you to control your application structure and features.

このマイクロカーネル アプローチは柔軟で、アプリケーションの構造と機能を制御できます。

A Single-File Symfony Application

Start with a completely empty directory and install these Symfony components via Composer:

完全に空のディレクトリから始めて、Composer 経由でこれらの Symfony コンポーネントをインストールします。
1
2
3
$ composer require symfony/config symfony/http-kernel \
  symfony/http-foundation symfony/routing \
  symfony/dependency-injection symfony/framework-bundle

Next, create an index.php file that defines the kernel class and runs it:

次に、カーネル クラスを定義して実行する index.php ファイルを作成します。
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
43
44
45
46
47
// index.php
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;

require __DIR__.'/vendor/autoload.php';

class Kernel extends BaseKernel
{
    use MicroKernelTrait;

    public function registerBundles(): array
    {
        return [
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
        ];
    }

    protected function configureContainer(ContainerConfigurator $c): void
    {
        // PHP equivalent of config/packages/framework.yaml
        $c->extension('framework', [
            'secret' => 'S0ME_SECRET'
        ]);
    }

    protected function configureRoutes(RoutingConfigurator $routes): void
    {
        $routes->add('random_number', '/random/{limit}')->controller([$this, 'randomNumber']);
    }

    public function randomNumber(int $limit): JsonResponse
    {
        return new JsonResponse([
            'number' => random_int(0, $limit),
        ]);
    }
}

$kernel = new Kernel('dev', true);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

That's it! To test it, start the Symfony Local Web Server:

それでおしまい!テストするには、Symfony ローカル Web サーバーを起動します。
1
$ symfony server:start

Then see the JSON response in your browser: http://localhost:8000/random/10

次に、ブラウザで JSON 応答を確認します: http://localhost:8000/random/10

The Methods of a "Micro" Kernel

When you use the MicroKernelTrait, your kernel needs to have exactly three methods that define your bundles, your services and your routes:

MicroKernelTrait を使用する場合、カーネルには、バンドル、サービス、およびルートを定義する 3 つのメソッドが必要です。
registerBundles()
This is the same registerBundles() that you see in a normal kernel.
これは、通常のカーネルで見られるのと同じ registerBundles() です。
configureContainer(ContainerConfigurator $c)
This method builds and configures the container. In practice, you will use extension() to configure different bundles (this is the equivalent of what you see in a normal config/packages/* file). You can also register services directly in PHP or load external configuration files (shown below).
このメソッドは、コンテナーをビルドして構成します。実際には、extension() を使用してさまざまなバンドルを構成します (これは、通常の config/packages/* ファイルに表示されるものと同等です)。サービスを PHP に直接登録するか、外部構成ファイルをロードすることもできます (以下を参照)。
configureRoutes(RoutingConfigurator $routes)
Your job in this method is to add routes to the application. The RoutingConfigurator has methods that make adding routes in PHP more fun. You can also load external routing files (shown below).
このメソッドでの仕事は、アプリケーションにルートを追加することです。 TheRoutingConfigurator には、PHP でのルートの追加をより楽しくするメソッドがあります。外部ルーティング ファイルをロードすることもできます (以下を参照)。

Adding Interfaces to "Micro" Kernel

When using the MicroKernelTrait, you can also implement the CompilerPassInterface to automatically register the kernel itself as a compiler pass as explained in the dedicated compiler pass section.

MicroKernelTrait を使用する場合、専用のコンパイラ パス セクションで説明されているように、コンパイラ パスとしてカーネル自体を自動的に登録するために、CompilerPassInterface を実装することもできます。

It is also possible to implement the EventSubscriberInterface to handle events directly from the kernel, again it will be registered automatically:

EventSubscriberInterface を実装して、カーネルから直接イベントを処理することもできます。これも自動的に登録されます。
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
// ...
use App\Exception\Danger;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class Kernel extends BaseKernel implements EventSubscriberInterface
{
    use MicroKernelTrait;

    // ...

    public function onKernelException(ExceptionEvent $event): void
    {
        if ($event->getException() instanceof Danger) {
            $event->setResponse(new Response('It\'s dangerous to go alone. Take this ⚔'));
        }
    }

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::EXCEPTION => 'onKernelException',
        ];
    }
}

Advanced Example: Twig, Annotations and the Web Debug Toolbar

The purpose of the MicroKernelTrait is not to have a single-file application. Instead, its goal is to give you the power to choose your bundles and structure.

MicroKernelTrait の目的は、アプリケーションを単一ファイルにすることではなく、バンドルと構造を選択できるようにすることです。

First, you'll probably want to put your PHP classes in an src/ directory. Configure your composer.json file to load from there:

まず、おそらく PHP クラスを src/ ディレクトリに置きたいと思うでしょう。そこからロードするように composer.json ファイルを構成します。
1
2
3
4
5
6
7
8
9
10
{
    "require": {
        "...": "..."
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

Then, run composer dump-autoload to dump your new autoload config.

次に、 composer dump-autoload を実行して、新しい autoload 構成をダンプします。

Now, suppose you want to define a custom configuration for your app, use Twig and load routes via annotations. Instead of putting everything in index.php, create a new src/Kernel.php to hold the kernel. Now it looks like this:

ここで、アプリのカスタム構成を定義したいとします。Twig を使用し、アノテーションを介してルートをロードします。すべてを index.php に入れる代わりに、新しい src/Kernel.php を作成してカーネルを保持します。これで次のようになります。
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// src/Kernel.php
namespace App;

use App\DependencyInjection\AppExtension;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;

class Kernel extends BaseKernel
{
    use MicroKernelTrait;

    public function registerBundles(): array
    {
        $bundles = [
            new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            new \Symfony\Bundle\TwigBundle\TwigBundle(),
        ];

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

        return $bundles;
    }

    protected function build(ContainerBuilder $container)
    {
        $container->registerExtension(new AppExtension());
    }

    protected function configureContainer(ContainerConfigurator $c): void
    {
        $c->import(__DIR__.'/../config/framework.yaml');

        // register all classes in /src/ as service
        $c->services()
            ->load('App\\', __DIR__.'/*')
            ->autowire()
            ->autoconfigure()
        ;

        // configure WebProfilerBundle only if the bundle is enabled
        if (isset($this->bundles['WebProfilerBundle'])) {
            $c->extension('web_profiler', [
                'toolbar' => true,
                'intercept_redirects' => false,
            ]);
        }
    }

    protected function configureRoutes(RoutingConfigurator $routes): void
    {
        // import the WebProfilerRoutes, only if the bundle is enabled
        if (isset($this->bundles['WebProfilerBundle'])) {
            $routes->import('@WebProfilerBundle/Resources/config/routing/wdt.xml')->prefix('/_wdt');
            $routes->import('@WebProfilerBundle/Resources/config/routing/profiler.xml')->prefix('/_profiler');
        }

        // load the routes defined as PHP attributes
        // (use 'annotation' as the second argument if you define routes as annotations)
        $routes->import(__DIR__.'/Controller/', 'attribute');
    }

    // optional, to use the standard Symfony cache directory
    public function getCacheDir(): string
    {
        return __DIR__.'/../var/cache/'.$this->getEnvironment();
    }

    // optional, to use the standard Symfony logs directory
    public function getLogDir(): string
    {
        return __DIR__.'/../var/log';
    }
}

Before continuing, run this command to add support for the new dependencies:

続行する前に、次のコマンドを実行して、新しい依存関係のサポートを追加します。
1
$ composer require symfony/yaml symfony/twig-bundle symfony/web-profiler-bundle doctrine/annotations

Next, create a new extension class that defines your app configuration and add a service conditionally based on the foo value:

次に、アプリの構成を定義する新しい拡張クラスを作成し、foo 値に基づいて条件付きでサービスを追加します。
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
// src/DependencyInjection/AppExtension.php
namespace App\DependencyInjection;

use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\AbstractExtension;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

class AppExtension extends AbstractExtension
{
    public function configure(DefinitionConfigurator $definition): void
    {
        $definition->rootNode()
            ->children()
                ->booleanNode('foo')->defaultTrue()->end()
            ->end();
    }

    public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
    {
        if ($config['foo']) {
            $builder->register('foo_service', \stdClass::class);
        }
    }
}

6.1

6.1

The AbstractExtension class was introduced in Symfony 6.1.

AbstractExtension クラスは Symfony 6.1 で導入されました。

Unlike the previous kernel, this loads an external config/framework.yaml file, because the configuration started to get bigger:

以前のカーネルとは異なり、構成が大きくなり始めたため、これは外部の config/framework.yaml ファイルをロードします。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
1
2
3
4
# config/framework.yaml
framework:
    secret: S0ME_SECRET
    profiler: { only_exceptions: false }

This also loads annotation routes from an src/Controller/ directory, which has one file in it:

これはまた、1 つのファイルを含む src/Controller/ ディレクトリから注釈ルートをロードします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Controller/MicroController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class MicroController extends AbstractController
{
    #[Route('/random/{limit}')]
    public function randomNumber(int $limit): Response
    {
        $number = random_int(0, $limit);

        return $this->render('micro/random.html.twig', [
            'number' => $number,
        ]);
    }
}

Template files should live in the templates/ directory at the root of your project. This template lives at templates/micro/random.html.twig:

テンプレート ファイルは、プロジェクトのルートにある templates/ ディレクトリに存在する必要があります。このテンプレートは、templates/micro/random.html.twig にあります。
1
2
3
4
5
6
7
8
9
10
<!-- templates/micro/random.html.twig -->
<!DOCTYPE html>
<html>
    <head>
        <title>Random action</title>
    </head>
    <body>
        <p>{{ number }}</p>
    </body>
</html>

Finally, you need a front controller to boot and run the application. Create a public/index.php:

最後に、アプリケーションを起動して実行するには、フロント コントローラーが必要です。 apublic/index.php を作成します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// public/index.php
use App\Kernel;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Symfony\Component\HttpFoundation\Request;

$loader = require __DIR__.'/../vendor/autoload.php';
// auto-load annotations
AnnotationRegistry::registerLoader([$loader, 'loadClass']);

$kernel = new Kernel('dev', true);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

That's it! This /random/10 URL will work, Twig will render, and you'll even get the web debug toolbar to show up at the bottom. The final structure looks like this:

それでおしまい!この /random/10 URL が機能し、Twig がレンダリングされ、Web デバッグ ツールバーが下部に表示されるようになります。最終的な構造は次のようになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
your-project/
├─ config/
│  └─ framework.yaml
├─ public/
|  └─ index.php
├─ src/
|  ├─ Controller
|  |  └─ MicroController.php
|  └─ Kernel.php
├─ templates/
|  └─ micro/
|     └─ random.html.twig
├─ var/
|  ├─ cache/
│  └─ log/
├─ vendor/
│  └─ ...
├─ composer.json
└─ composer.lock

As before you can use the Symfony Local Web Server:

前と同じように、Symfony ローカル Web サーバーを使用できます。
1
$ symfony server:start

Then visit the page in your browser: http://localhost:8000/random/10

次に、ブラウザで次のページにアクセスします: http://localhost:8000/random/10