Построение собственного фреймворка с MicroKernelTrait

Традиционное приложение Symfony содержит благоразумную структуру каталогов, различные файлы конфигурации и AppKernel с некоторыми уже зарегистрированными пакетами. Это полностью функциональное приложение, готовое к применению.

Но зналили вы, что вы можете создать полностью функционирующее приложение Symfony всего в одном файле? Это возможно благодаря новому MicroKernelTrait. Он позволяет вам начать с малюсенького приложения а потом добавлять функции и структуру по необходимости.

Приложение Symfony в одном файле

Начните с абсолютно пустого каталога. Получите symfony/symfony через Composer в качестве зависимости:

1
$ composer require symfony/symfony

Далее, создайте файл 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
44
45
46
47
48
49
50
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Routing\RouteCollectionBuilder;

// требует автозагрузчик Composer'а
require __DIR__.'/vendor/autoload.php';

class AppKernel extends Kernel
{
    use MicroKernelTrait;

    public function registerBundles()
    {
        return array(
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle()
        );
    }

    protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
    {
        // PHP-эквивалент config.yml
        $c->loadFromExtension('framework', array(
            'secret' => 'S0ME_SECRET'
        ));
    }

    protected function configureRoutes(RouteCollectionBuilder $routes)
    {
        // ядро - это сервис, указывающий на этот класс
        // необязательный 3ий аргумент - имя маршрута
        $routes->add('/random/{limit}', 'kernel:randomAction');
    }

    public function randomAction($limit)
    {
        return new JsonResponse(array(
            'number' => rand(0, $limit)
        ));
    }
}

$kernel = new AppKernel('dev', true);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

Вот и всё! Чтобы протестировать, вы можете запустить встроенный веб-сервер:

1
$ php -S localhost:8000

Потом посмотрите JSON-ответ в вашем браузере:

Методы "микро" ядра

Когда вы используете MicroKernelTrait, ваше ядро требует ровно три метода, определяющих ваши пакеты, сервисы и маршруты:

registerBundles()
Тот же registerBundles(), что вы видите в обычном ядре.
configureContainer(ContainerBuilder $c, LoaderInterface $loader)
Этот метод строит и конфигурирует контейнер. На практике, вы будете использовать loadFromExtension, чтобы сконфигурировать разные пакеты (эквивалент того, что вы видите в обычном файле config.yml). Вы можете также зарегистрировать сервисы напрямую в PHP или загрузить внешние файлы конфигурации (показано ниже).
configureRoutes(RouteCollectionBuilder $routes)
Ваша задача в этом методе - добавить маршруты в приложение. RouteCollectionBuilder имеет методы, которые делают добавление маршрутов в PHP веселее. Вы также можете агрузить внешние файлы конфигурации (показано ниже).

Продвинутый пример: Twig, аннотации и панель инструментов веб-отладки

Цель MicroKernelTrait не в том, чтобы иметь приложение из одного файла. Она в том, чтобы дать вам возможность выбирать ваши пакеты и структуру.

Вначале, вы наверное захотите определить ваши PHP-классы в каталог src/. Сконфигурируйте ваш файл composer.json так, чтобы он загружал оттуда:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
    "require": {
        "...": "..."
    },
    "autoload": {
        "psr-4": {
            "": "src/"
        }
    }
}

Теперь, представьте, что вы хотите использовать Twig и загружать маршруты через аннотации. Для маршрутизации аннотаций, вам понадобится SensioFrameworkExtraBundle. Он поставляется с обычным проектом Symfony. Но в этом случае, вам нужно будет его скачать:

1
$ composer require sensio/framework-extra-bundle

Вместо того, чтобы помещать всё в index.php, создайте новый app/AppKernel.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
// app/AppKernel.php

use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
use Doctrine\Common\Annotations\AnnotationRegistry;

// требует автозагрузчик Composer'а
$loader = require __DIR__.'/../vendor/autoload.php';
// автозагрузка аннотаций
AnnotationRegistry::registerLoader(array($loader, 'loadClass'));

class AppKernel extends Kernel
{
    use MicroKernelTrait;

    public function registerBundles()
    {
        $bundles = array(
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            new Symfony\Bundle\TwigBundle\TwigBundle(),
            new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle()
        );

        if ($this->getEnvironment() == 'dev') {
            $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
        }

        return $bundles;
    }

    protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
    {
        $loader->load(__DIR__.'/config/config.yml');

        // сконфигурировать WebProfilerBundle, только если пакет подключен
        if (isset($this->bundles['WebProfilerBundle'])) {
            $c->loadFromExtension('web_profiler', array(
                'toolbar' => true,
                'intercept_redirects' => false,
            ));
        }
    }

    protected function configureRoutes(RouteCollectionBuilder $routes)
    {
        // импортировать WebProfilerRoutes только, если пакет подключен
        if (isset($this->bundles['WebProfilerBundle'])) {
            $routes->import('@WebProfilerBundle/Resources/config/routing/wdt.xml', '/_wdt');
            $routes->import('@WebProfilerBundle/Resources/config/routing/profiler.xml', '/_profiler');
        }

        // загрузить маршруты аннотаций
        $routes->import(__DIR__.'/../src/App/Controller/', '/', 'annotation');
    }

    // необязательно, использовать стандартный каталог кеша Symfony
    public function getCacheDir()
    {
        return __DIR__.'/../var/cache/'.$this->getEnvironment();
    }

    // необязательно, использовать стандартный каталог логов Symfony
    public function getLogDir()
    {
        return __DIR__.'/../var/logs';
    }
}

В отличие от предыдущего ядра, это загружает внешний файл app/config/config.yml, так как конфигурация становится больше:

  • YAML
    1
    2
    3
    4
    5
    6
    # app/config/config.yml
    framework:
        secret: S0ME_SECRET
        templating:
            engines: ['twig']
        profiler: { only_exceptions: false }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <!-- app/config/config.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 http://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    
        <framework:config secret="S0ME_SECRET">
            <framework:templating>
                <framework:engine>twig</framework:engine>
            </framework:templating>
            <framework:profiler only-exceptions="false" />
        </framework:config>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // app/config/config.php
    $container->loadFromExtension('framework', array(
        'secret' => 'S0ME_SECRET',
        'templating' => array(
            'engines' => array('twig'),
        ),
        'profiler' => array(
            'only_exceptions' => false,
        ),
    ));
    

Также загружает маршруты аннотации из каталога src/App/Controller/, который имеет в себе один файл:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// src/App/Controller/MicroController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class MicroController extends Controller
{
    /**
     * @Route("/random/{limit}")
     */
    public function randomAction($limit)
    {
        $number = rand(0, $limit);

        return $this->render('micro/random.html.twig', array(
            'number' => $number
        ));
    }
}

Файлы шаблонов должны жить в каталоге Resources/views того же каталога, в котором находится ваше ядро. Так как AppKernel живёт в app/, то этот шаблон находится в app/Resources/views/micro/random.html.twig:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!-- app/Resources/views/micro/random.html.twig -->
<!DOCTYPE html>
<html>
    <head>
        <title>Random action</title>
    </head>
    <body>
        <p>{{ number }}</p>
    </body>
</html>

Наконец, вам нужен фронт-контроллер для загрузки и запуска приложения. Создайте web/index.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// web/index.php

use Symfony\Component\HttpFoundation\Request;

require __DIR__.'/../app/AppKernel.php';

$kernel = new AppKernel('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
20
21
your-project/
├─ app/
|  ├─ AppKernel.php
│  ├─ config/
│  └─ Resources
|     └─ views
|        └─ micro
|           └─ random.html.twig
├─ src/
│  └─ App
|     └─ Controller
|        └─ MicroController.php
├─ var/
|  ├─ cache/
│  └─ logs/
├─ vendor/
│  └─ ...
├─ web/
|  └─ index.php
├─ composer.json
└─ composer.lock

Как и раньше, вы можете использовать встроенный PHP-сервер:

1
2
cd web/
$ php -S localhost:8000

Просмотрите страницу в браузере:

Эй, это же выглядит очень похоже на традиционное приложение Symfony! Вы правы: MicroKernelTrait всё ещё является Symfony: но вы можете достаточно легко контролировать вашу структуру и функции.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.