使用 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://: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://:8000/random/10