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.
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:
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:
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:
1 |
$ symfony server:start
|
Then see the JSON response in your browser: 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:
- 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 normalconfig/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.
It is also possible to implement the EventSubscriberInterface
to handle
events directly from the kernel, again it will be registered automatically:
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.
First, you'll probably want to put your PHP classes in an src/
directory. Configure
your composer.json
file to load from there:
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.
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:
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:
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
The AbstractExtension
class was introduced in Symfony 6.1.
Unlike the previous kernel, this loads an external config/framework.yaml
file,
because the configuration started to get bigger:
-
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 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
:
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
:
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:
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:
1 |
$ symfony server:start
|
Then visit the page in your browser: http://localhost:8000/random/10