mirror of
https://github.com/symfony/symfony-docs.git
synced 2026-03-24 00:32:14 +01:00
481 lines
16 KiB
ReStructuredText
481 lines
16 KiB
ReStructuredText
Building your own Framework with the MicroKernelTrait
|
|
=====================================================
|
|
|
|
The default ``Kernel`` class included in Symfony applications uses a
|
|
:class:`Symfony\\Bundle\\FrameworkBundle\\Kernel\\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:
|
|
|
|
.. code-block:: terminal
|
|
|
|
$ composer require symfony/framework-bundle symfony/runtime
|
|
|
|
Next, create an ``index.php`` file that defines the kernel class and runs it:
|
|
|
|
.. configuration-block::
|
|
|
|
.. code-block:: php-attributes
|
|
|
|
// 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\Attribute\Route;
|
|
|
|
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
|
|
|
class Kernel extends BaseKernel
|
|
{
|
|
use MicroKernelTrait;
|
|
|
|
protected function configureContainer(ContainerConfigurator $container): void
|
|
{
|
|
// PHP equivalent of config/packages/framework.yaml
|
|
$container->extension('framework', [
|
|
'secret' => 'S0ME_SECRET'
|
|
]);
|
|
}
|
|
|
|
#[Route('/random/{limit}', name: 'random_number')]
|
|
public function randomNumber(int $limit): JsonResponse
|
|
{
|
|
return new JsonResponse([
|
|
'number' => random_int(0, $limit),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return static function (array $context) {
|
|
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
|
};
|
|
|
|
.. code-block:: php
|
|
|
|
// 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_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
|
|
|
class Kernel extends BaseKernel
|
|
{
|
|
use MicroKernelTrait;
|
|
|
|
protected function configureContainer(ContainerConfigurator $container): void
|
|
{
|
|
// PHP equivalent of config/packages/framework.yaml
|
|
$container->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),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return static function (array $context) {
|
|
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
|
};
|
|
|
|
That's it! To test it, start the :ref:`Symfony local web server <symfony-cli-server>`:
|
|
|
|
.. code-block:: terminal
|
|
|
|
$ symfony server:start
|
|
|
|
Then see the JSON response in your browser: http://localhost:8000/random/10
|
|
|
|
.. tip::
|
|
|
|
If your kernel only defines a single controller, you can use an invokable method::
|
|
|
|
class Kernel extends BaseKernel
|
|
{
|
|
use MicroKernelTrait;
|
|
|
|
// ...
|
|
|
|
#[Route('/random/{limit}', name: 'random_number')]
|
|
public function __invoke(int $limit): JsonResponse
|
|
{
|
|
// ...
|
|
}
|
|
}
|
|
|
|
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. By
|
|
default, the micro kernel only registers the ``FrameworkBundle``. If you need
|
|
to register more bundles, override this method::
|
|
|
|
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
|
use Symfony\Bundle\TwigBundle\TwigBundle;
|
|
// ...
|
|
|
|
class Kernel extends BaseKernel
|
|
{
|
|
use MicroKernelTrait;
|
|
|
|
// ...
|
|
|
|
public function registerBundles(): array
|
|
{
|
|
yield new FrameworkBundle();
|
|
yield new TwigBundle();
|
|
}
|
|
}
|
|
|
|
**configureContainer(ContainerConfigurator $container)**
|
|
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).
|
|
|
|
**configureRoutes(RoutingConfigurator $routes)**
|
|
In this method, you can use the ``RoutingConfigurator`` object to define routes
|
|
in your application and associate them to the controllers defined in this very
|
|
same file.
|
|
|
|
However, it's more convenient to define the controller routes using PHP attributes,
|
|
as shown above. That's why this method is commonly used only to load external
|
|
routing files (e.g. from bundles) as shown below.
|
|
|
|
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
|
|
:ref:`compiler pass section <kernel-as-compiler-pass>`. If the
|
|
:class:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface`
|
|
is implemented when using the ``MicroKernelTrait``, then the kernel will
|
|
be automatically registered as an extension. You can learn more about it in
|
|
the dedicated section about
|
|
:ref:`managing configuration with extensions <components-dependency-injection-extension>`.
|
|
|
|
It is also possible to implement the ``EventSubscriberInterface`` to handle
|
|
events directly from the kernel, again it will be registered automatically::
|
|
|
|
// ...
|
|
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->getThrowable() 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:
|
|
|
|
Advanced Example: Twig, Attributes 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:
|
|
|
|
.. code-block:: json
|
|
|
|
{
|
|
"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 attributes. Instead of putting *everything*
|
|
in ``index.php``, create a new ``src/Kernel.php`` to hold the kernel.
|
|
Now it looks like this::
|
|
|
|
// src/Kernel.php
|
|
namespace App;
|
|
|
|
use App\DependencyInjection\AppExtension;
|
|
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
|
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
|
use Symfony\Bundle\TwigBundle\TwigBundle;
|
|
use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle;
|
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
|
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(): iterable
|
|
{
|
|
yield new FrameworkBundle();
|
|
yield new TwigBundle();
|
|
|
|
if ('dev' === $this->getEnvironment()) {
|
|
yield new WebProfilerBundle();
|
|
}
|
|
}
|
|
|
|
protected function build(ContainerBuilder $containerBuilder): void
|
|
{
|
|
$containerBuilder->registerExtension(new AppExtension());
|
|
}
|
|
|
|
protected function configureContainer(ContainerConfigurator $container): void
|
|
{
|
|
$container->import(__DIR__.'/../config/framework.yaml');
|
|
|
|
// register all classes in /src/ as service
|
|
$container->services()
|
|
->load('App\\', __DIR__.'/*')
|
|
->autowire()
|
|
->autoconfigure()
|
|
;
|
|
|
|
// configure WebProfilerBundle only if the bundle is enabled
|
|
if (isset($this->bundles['WebProfilerBundle'])) {
|
|
$container->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.php', 'php')->prefix('/_wdt');
|
|
$routes->import('@WebProfilerBundle/Resources/config/routing/profiler.php', 'php')->prefix('/_profiler');
|
|
}
|
|
|
|
// load the routes defined as PHP attributes
|
|
$routes->import(__DIR__.'/Controller/', 'attribute');
|
|
}
|
|
|
|
// optionally, you can define the getCacheDir() and getLogDir() methods
|
|
// to override the default locations for these directories
|
|
}
|
|
|
|
|
|
.. versionadded:: 7.3
|
|
|
|
The ``wdt.php`` and ``profiler.php`` files were introduced in Symfony 7.3.
|
|
Previously, you had to import ``wdt.xml`` and ``profiler.xml``
|
|
|
|
Before continuing, run this command to add support for the new dependencies:
|
|
|
|
.. code-block:: terminal
|
|
|
|
$ composer require symfony/yaml symfony/twig-bundle symfony/web-profiler-bundle
|
|
|
|
Next, create a new extension class that defines your app configuration and
|
|
add a service conditionally based on the ``foo`` value::
|
|
|
|
// 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 $containerConfigurator, ContainerBuilder $containerBuilder): void
|
|
{
|
|
if ($config['foo']) {
|
|
$containerBuilder->register('foo_service', \stdClass::class);
|
|
}
|
|
}
|
|
}
|
|
|
|
Unlike the previous kernel, this loads an external ``config/framework.yaml`` file,
|
|
because the configuration started to get bigger:
|
|
|
|
.. configuration-block::
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/framework.yaml
|
|
framework:
|
|
secret: S0ME_SECRET
|
|
profiler: { only_exceptions: false }
|
|
|
|
.. code-block:: xml
|
|
|
|
<!-- config/framework.xml -->
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<container xmlns="http://symfony.com/schema/dic/services"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:framework="http://symfony.com/schema/dic/symfony"
|
|
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
|
|
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
|
|
|
|
<framework:config secret="S0ME_SECRET">
|
|
<framework:profiler only-exceptions="false"/>
|
|
</framework:config>
|
|
</container>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/framework.php
|
|
use Symfony\Config\FrameworkConfig;
|
|
|
|
return static function (FrameworkConfig $framework): void {
|
|
$framework
|
|
->secret('SOME_SECRET')
|
|
->profiler()
|
|
->onlyExceptions(false)
|
|
;
|
|
};
|
|
|
|
This also loads attribute routes from an ``src/Controller/`` directory, which
|
|
has one file in it::
|
|
|
|
// src/Controller/MicroController.php
|
|
namespace App\Controller;
|
|
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\Routing\Attribute\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``:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<!-- 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``::
|
|
|
|
// public/index.php
|
|
use App\Kernel;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
|
|
require __DIR__.'/../vendor/autoload.php';
|
|
|
|
$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:
|
|
|
|
.. code-block:: text
|
|
|
|
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 :ref:`Symfony local web server <symfony-cli-server>`:
|
|
|
|
.. code-block:: terminal
|
|
|
|
$ symfony server:start
|
|
|
|
Then visit the page in your browser: http://localhost:8000/random/10
|