Построение собственного фреймворка с MicroKernelTrait
Дата обновления перевода 2024-07-16
Построение собственного фреймворка с MicroKernelTrait
Класс Kernel
по умолчанию включённый в приложения Symfony использует
MicroKernelTrait, чтобы
конфигурировать пакеты, маршруты и сервис-контейнер в одном классе.
Этот подход микро-ядра настолько гибок, что позволяет вам контролировать структуру вашего приложения и функции достаточно легко.
Приложение Symfony в одном файле
Начните с абсолютно пустого каталога. И установите эти компоненты Symfony через Composer:
1 2 3
$ composer require symfony/config symfony/http-kernel \
symfony/http-foundation symfony/routing \
symfony/dependency-injection symfony/framework-bundle
Далее, создайте файл 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
// 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 __DIR__.'/vendor/autoload.php';
class Kernel extends BaseKernel
{
use MicroKernelTrait;
public function registerBundles(): array
{
return [
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
];
}
protected function configureContainer(ContainerConfigurator $container): void
{
// PHP-эквивалент 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),
]);
}
}
$kernel = new Kernel('dev', true);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
Вот и всё! Чтобы протестировать, вы можете запустить локальный веб-сервер Symfony:
1
$ symfony server:start
Потом посмотрите JSON-ответ в вашем браузере: http://localhost:8000/random/10
Методы "микро" ядра
Когда вы используете MicroKernelTrait
, ваше ядро требует ровно три метода,
определяющих ваши пакеты, сервисы и маршруты:
- registerBundles()
-
Тот же
registerBundles()
, что вы видите в обычном ядре. - configureContainer(ContainerConfigurator $container)
-
Этот метод строит и конфигурирует контейнер. На практике, вы будете использовать
loadFromExtension
, чтобы сконфигурировать разные пакеты (эквивалент того, что вы видите в обычном файлеconfig/packages/*
). Вы можете также зарегистрировать сервисы напрямую в PHP или загрузить внешние файлы конфигурации (показано ниже). - configureRoutes(RouteCollectionBuilder $routes)
-
Ваша задача в этом методе - добавить маршруты в приложение.
RouteCollectionBuilder
имеет методы, которые делают добавление маршрутов в PHP веселее. Вы также можете агрузить внешние файлы конфигурации (показано ниже).
Добавление интерфейсов к "микро" ядру
При использовании MicroKernelTrait
, вы также можете реализовать CompilerPassInterface
для автоматической регистрации самого ядра как передачи комиплятора, что объясняется в
разделе, посвящённом передаче компилятора . Если
ExtensionInterface
реализуется при использовании MicroKernelTrait
, то ядро будет автоматически зарегистрировано
как расширение. Подробнее об этом вы можете узнать в специальном разделе об
управлении конфигурацией с помощью расширений .
Также возможно реализовать 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->getThrowable() instanceof Danger) {
$event->setResponse(new Response('It\'s dangerous to go alone. Take this ⚔'));
}
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::EXCEPTION => 'onKernelException',
];
}
}
Продвинутый пример: Twig, аннотации и панель инструментов веб-отладки
Цель MicroKernelTrait
не в том, чтобы иметь приложение из одного файла. Она в том,
чтобы дать вам возможность выбирать ваши пакеты и структуру.
Вначале, вы наверное захотите определить ваши PHP-классы в каталог src/
. Сконфигурируйте
ваш файл composer.json
так, чтобы он загружал оттуда:
1 2 3 4 5 6 7 8 9 10
{
"require": {
"...": "..."
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
Затем, выполните composer dump-autoload
, чтобы сбросить вашу новую конфигурацию
автозагрузки.
Теперь, представьте, что вы хотите использовать 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 78
// src/Kernel.php
namespace App;
use App\DependencyInjection\AppExtension;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
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(): 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 $containerBuilder): void
{
$containerBuilder->registerExtension(new AppExtension());
}
protected function configureContainer(ContainerConfigurator $container): void
{
$container->import(__DIR__.'/../config/framework.yaml');
// зарегистрировать все классы в /src/ как сервис
$container->services()
->load('App\\', __DIR__.'/*')
->autowire()
->autoconfigure()
;
// сконфигурировать WebProfilerBundle только, если пакет подключен
if (isset($this->bundles['WebProfilerBundle'])) {
$container->extension('web_profiler', [
'toolbar' => true,
'intercept_redirects' => false,
]);
}
}
protected function configureRoutes(RoutingConfigurator $routes): void
{
// импортировать WebProfilerRoutes только, если пакет подключен
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');
}
// загрузить маршруты, определённые как PHP-атрибуты
// (используйте 'annotation' как второй аргумент, если вы определяете маршруты как аннотации)
$routes->import(__DIR__.'/Controller/', 'attribute');
}
// опционально, чтобы использовать стандартный каталог кеша Symfony
public function getCacheDir(): string
{
return __DIR__.'/../var/cache/'.$this->getEnvironment();
}
// опционально, чтобы использовать каталог логов Symfony
public function getLogDir(): string
{
return __DIR__.'/../var/log';
}
}
Перед тем, как продолжать, выполните эту команду, чтобы добавить поддержку для новых зависимостей:
1
$ composer require symfony/yaml symfony/twig-bundle symfony/web-profiler-bundle
Далее, создайте новый класс расширения, который определяет вашу конфигурацию приложения
и добавьте сервис, условно основанный на значении 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 $containerConfigurator, ContainerBuilder $containerBuilder): void
{
if ($config['foo']) {
$containerBuilder->register('foo_service', \stdClass::class);
}
}
}
В отличие от предыдущего ядра, это загружает внешний файл app/config/config.yml
, так
как конфигурация становится больше:
1 2 3 4
# config/framework.yaml
framework:
secret: S0ME_SECRET
profiler: { only_exceptions: false }
Это также загружает маршруты атрибутов из каталога 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\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,
]);
}
}
Файлы шаблонов должны жить в каталоге 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>
Наконец, вам нужен фронт-контроллер для загрузки и запуска приложения. Создайте
public/index.php
:
1 2 3 4 5 6 7 8 9 10 11
// 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);
Вот и всё! Этот URL /random/10
будет работать, Twig будет отображать, и вы даже увидите
внизу панель инструментов веб-отладки. Итоговая структура выглядит так:
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
Как и раньше, вы можете использовать локальный веб-сервер Symfony:
1
$ symfony server:start
Просмотрите страницу в браузере: http://localhost:8000/random/10