使用 MicroKernelTrait 构建您自己的框架
Symfony 应用中包含的默认 Kernel
类使用 MicroKernelTrait 在同一个类中配置程序包、路由和服务容器。
这种微内核方法非常灵活,允许您控制应用程序的结构和功能。
单文件 Symfony 应用
从一个完全空的目录开始,并通过 Composer 安装这些 Symfony 组件
1
$ composer require symfony/framework-bundle symfony/runtime
接下来,创建一个 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
// 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']);
};
就是这样!要测试它,启动 Symfony 本地 Web 服务器
1
$ symfony server:start
然后在浏览器中查看 JSON 响应:https://127.0.0.1:8000/random/10
提示
如果您的内核只定义一个控制器,则可以使用可调用方法
1 2 3 4 5 6 7 8 9 10 11 12
class Kernel extends BaseKernel
{
use MicroKernelTrait;
// ...
#[Route('/random/{limit}', name: 'random_number')]
public function __invoke(int $limit): JsonResponse
{
// ...
}
}
“微”内核的方法
当您使用 MicroKernelTrait
时,您的内核需要正好有三个方法来定义您的程序包、服务和路由
registerBundles()
-
这与您在普通内核中看到的
registerBundles()
相同。默认情况下,微内核仅注册FrameworkBundle
。如果您需要注册更多程序包,请覆盖此方法1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
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)
- 此方法构建和配置容器。在实践中,您将使用
extension()
来配置不同的程序包(这等同于您在普通的config/packages/*
文件中看到的内容)。您也可以直接在 PHP 中注册服务或加载外部配置文件(如下所示)。 configureRoutes(RoutingConfigurator $routes)
-
在此方法中,您可以使用
RoutingConfigurator
对象在您的应用程序中定义路由,并将它们与在同一文件中定义的控制器关联起来。但是,使用 PHP 属性定义控制器路由更方便,如上所示。这就是为什么此方法通常仅用于加载外部路由文件(例如,来自程序包)的原因,如下所示。
向“微”内核添加接口
当使用 MicroKernelTrait
时,您还可以实现 CompilerPassInterface
以自动将内核本身注册为编译器传递,如专用 编译器传递部分 中所述。如果在使用 MicroKernelTrait
时实现了 ExtensionInterface,则内核将自动注册为扩展。您可以在关于 使用扩展管理配置 的专用部分中了解更多信息。
也可以实现 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、注解和 Web 调试工具栏
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
// 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 FrameworkBundle();
yield TwigBundle();
if ('dev' === $this->getEnvironment()) {
yield 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.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';
}
}
在继续之前,运行此命令以添加对新依赖项的支持
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);
}
}
}
与之前的内核不同,这会加载一个外部 config/framework.yaml
文件,因为配置开始变得更大
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);
就是这样!这个 /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
和以前一样,您可以使用 Symfony 本地 Web 服务器
1
$ symfony server:start
然后访问您浏览器中的页面:https://127.0.0.1:8000/random/10