Статьи

Однофайловые приложения Symfony? Да, с MicroKernelTrait!

Эта статья была рецензирована Юнесом Рафи , Клаудио Рибейру и Хайдаром Кюлекси . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!


Одностраничное приложение (SPA) предлагает пользователям веб-приложения возможность работы на рабочем столе, загружая одну HTML-страницу и динамически обновляя ее по мере необходимости, без перезагрузки. Однако приложение Symfony может иметь сотни классов, и в базовом приложении мы получаем множество файлов, которые нам на самом деле не нужны.

Иллюстрация программиста, держащего слона

Последние версии Symfony (2.8 и 3.0) знакомят нас с концепцией однофайлового приложения (SFA) — сверхтонкого приложения или микро-фреймворка, реализованного в одном файле.

Для этого вам необходимо иметь работающий веб-сервер и локально запускать веб-приложения. См. Статью Laravel Valet, чтобы узнать, как быстро настроить локальную среду разработки, которая не требует настройки веб-сервера, виртуальных хостов и работы с файлом hosts. Другой вариант — наша верная улучшенная усадьба , готовая к работе.

Шаг 1: Установите Barebones Symfony

Мы собираемся установить Symfony с Composer, поскольку он позволяет нам устанавливать только основной пакет. Создайте папку, в которой обычно находятся ваши веб-приложения, и назовем ее sfa . У меня есть мой под ~/Sites/sfa . В нем мы устанавливаем Symfony:

 composer require symfony/symfony 

Теперь создайте 2 папки внутри sfa и назовите их app и web .

Шаг 2: Фронт-контроллер

Внутри sfa/web мы разместим наш фронт-контроллер — файл, который получает все запросы к приложению, передает его в нужное место для обработки и возвращает ответ клиенту, который сделал запрос.

Вы можете вызывать этот файл как угодно, но вам нужно убедиться, что ваш веб-сервер настроен так, чтобы найти его в правильном месте. Laravel имеет public/index.php , Drupal 8 имеет index.php , а Symfony имеет web/app_dev.php (во время разработки) и web/app.php (во время производства). Поскольку это приложение Symfony, давайте назовем наш app_dev.php :

 <?php use Symfony\Component\HttpFoundation\Request; require __DIR__.'/../vendor/autoload.php'; require __DIR__ . '/../app/SfaKernel.php'; $kernel = new SfaKernel('dev', true); $request = Request::createFromGlobals(); $response = $kernel->handle($request); $response->send(); $kernel->terminate($request, $response); 

Два небольших различия между этим файлом и ванильной установкой Symfony 3.

Во-первых, наш класс ядра будет находиться в app/SfaKernel.php . Ничто не мешает нам называть это Kernel.php , но мы хотим что-то другое. Во-вторых, мы решили не вызывать метод loadClassCache() . Наше приложение тонкое, без большого количества классов из стандартной установки, поэтому мы можем пока оставить этот метод.

Даже если мы говорим о приложении с одним файлом, вы заметите, что это не один файл — у нас есть фронт-контроллер и мини-ядро, которое выполняет всю тяжелую работу. Это в дополнение ко всем другим классам, загружаемым от vendor . Однако для всех целей запуск и запуск приложения Symfony из одного файла ядра можно рассматривать как одно файловое приложение.

Шаг 3: Класс ядра

Создайте app/SfaKernel.php и добавьте это:

 <?php use Symfony\Component\HttpKernel\Kernel; class SfaKernel extends Kernel { } 

Наш класс должен наследоваться от класса Kernel от ядра Symfony.

Поскольку класс Kernel является абстрактным классом, наш конкретный класс должен реализовывать метод registerContainerConfiguration() . Кстати, если вы Symfony\Component\HttpKernel\Kernel.php файл Symfony\Component\HttpKernel\Kernel.php , вы не найдете метод registerContainerConfiguration() — он находится в Symfony\Component\HttpKernel\KernelInterface.php который реализует само Kernel.

Здесь нас интересует новая функция Symfony 3 (также доступная с MicroKernelTrait 2.8), которая позволяет нам создавать микро-фреймворк, и она была названа MicroKernelTrait . Внутри класса используйте эту черту:

 <?php use Symfony\Component\HttpKernel\Kernel; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; class SfaKernel extends Kernel { use MicroKernelTrait; } 

Теперь нам нужно реализовать три метода в этом классе — configureRoutes() , configureContainer() и registerBundles() . Первые 2 являются абстрактными методами из этой черты, в то время как registerBundles() находится в Symfony\Component\HttpKernel\KernelInterface которую Kernel реализует, а мы, в свою очередь, расширяем наше Symfony\Component\HttpKernel\KernelInterface . Если мы внимательно посмотрим на эти методы, мы сможем многому научиться из комментариев.

  1. registerBundles()

    Пакет в Symfony — это набор файлов, которые реализуют функцию. Другие приложения или платформы говорят о плагинах или модулях. Единственное, что нам нужно на данный момент, — это сама структура Symfony. Комментарий гласит: «Возвращает массив пакетов для регистрации» , поэтому наш метод должен выглядеть следующим образом:

     public function registerBundles() { return [ new FrameworkBundle() ]; } 
  2. configureRoutes()

    Здесь мы добавляем или импортируем маршруты для нашего приложения. Маршрутизация смотрит на путь в запросе и определяет, куда направить его, чтобы в конечном итоге получить ответ. Комментарий, как обычно, полезен и говорит нам о двух способах:

     $routes->import('config/routing.yml'); 

    Вы добавляете файл конфигурации, в котором вы определяете свои маршруты, и импортируете его сюда.

     $routes->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); 

    Вы указываете путь ( /admin ), добавляете класс контроллера с помощью метода ( AppBundle:Admin:dashboard ) и дополнительно присваиваете ему имя или псевдоним ( admin_dashboard ).

    Тем не менее, есть третий способ, а не второй способ указания вашего контроллера. Например, 'kernel:home' относится к методу home() в текущем классе ядра. По сути, этот класс SfaKernel удваивается как контроллер. Как мило! Давайте добавим 2 маршрута.

     $routes->add('/', 'kernel:home'); 

    Когда мы перейдем на нашу домашнюю страницу, запрос будет перенаправлен методу home() в этом классе.

     $routes->add('/greet/{who}', 'kernel:greet'); 

    Аналогично, этот маршрут будет сопоставлять все запросы к /greet/{who} с параметром маршрута, представленным {who} и передавать их методу greet с параметром $who .

    Давайте продолжим и реализуем методы. Еще раз, убедитесь, что вы use Symfony\Component\HttpFoundation\Response; на вершине класса.

     public function home() { return new Response( '<p>Home, sweet home</p>' ); } public function greet($who) { return new Response( "<h1>Greeting</h1><p>Hello $who</p>" ); } 

    Помните, что вам нужно вернуть объект Response из методов.

  3. configureContainer()

    Контейнер содержит большое количество классов, и им часто нужны разные параметры или любая другая конфигурация, передаваемая им. Здесь вы регистрируете любые расширения, услуги или параметры, например

     $c->loadFromExtension('framework', array( 'secret' => '%secret%' )); $c->register('halloween', 'FooBundle\HalloweenProvider'); $c->setParameter('halloween', 'lot of fun'); 

    Единственное расширение, которое у нас есть, — FrameworkBundle, у которого есть несколько параметров конфигурации , но требуется только одно — secret . Они предоставляются в виде ассоциативного массива с ключами параметров. Значение нашего secret должно быть уникальным, поэтому наш метод должен выглядеть следующим образом:

     protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) { $c->loadFromExtension('framework', [ 'secret' => 'micr0', ]); } 

Если вы настроили свой веб-сервер, перейдите на домашнюю страницу, и вы должны увидеть: Дом, милый дом . Добавьте /greet/Symfony к URL. Привет Symfony должен отображаться в браузере.

Вы можете зарегистрировать любое количество маршрутов в registerRoutes() и вернуть ответ из этого же класса. У вас есть работающее однофайловое приложение Symfony.

Прежде чем продолжить, давайте вернемся к началу, когда мы впервые расширили класс Kernel. Нам нужно было реализовать только один метод — registerContainerConfiguration() . Когда мы добавили MicroKernelTrait, нам потребовалось 3 метода.

Если вы откроете Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait.php , вы заметите, что метод registerContainerConfiguration() был реализован для нас, чтобы сделать его более гибким и настраиваемым. Давайте пройдем через это.

 public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(function (ContainerBuilder $container) use ($loader) { $container->loadFromExtension('framework', array( 'router' => array( 'resource' => 'kernel:loadRoutes', 'type' => 'service', ), )); $this->configureContainer($container, $loader); $container->addObjectResource($this); }); } 

Внутри замыкания посмотрите на ключ массива resource для router и вы увидите kernel:loadRoutes . Мы сталкивались с чем-то вроде этого, когда смотрели на определение класса контроллера для нашего маршрута. Это та же самая концепция здесь. kernel ссылается на класс, использующий эту черту при расширении класса Kernel, и :loadRoutes будет искать метод в этой черте.

 public function loadRoutes(LoaderInterface $loader) { $routes = new RouteCollectionBuilder($loader); $this->configureRoutes($routes); return $routes->build(); } 

Именно из этого метода вызывается наш configureRoutes() . Чтобы убедиться, что он реализован в любой микро-среде, использующей эту черту, он был определен как абстрактный метод.

Случаи использования

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

Другие предлагают создавать микросервисы из MicrokernelTrait на основе полнофункциональной платформы Symfony. Другой возможностью было бы разделение запросов GET, которые будут обрабатываться гораздо более тонким приложением MicrokernelTrait, тогда как другие запросы для более ресурсоемких процессов управляются более традиционным приложением Symfony.

Вывод

До Symfony 2.8 и 3.0 микро-фреймворки, такие как Silex и Lumen, были доступными вариантами для проектов, у которых были оговорки относительно реализации полной фреймворка. Тем не менее, концепция приложения с одним файлом предложила другую золотую середину.

Это захватывающая вещь для Symfony, и в ближайшие дни можно ожидать, что разработчики выжмут гениальные варианты использования из этой новой функции. Лично я надеюсь, что папка vendor получит дальнейшее изучение. Должна ли установка без компоновки с composer require symfony/symfony действительно включали все эти зависимости?

Пока еще рано, но потенциал есть, и направление, в котором разработчики собираются использовать эту новую функцию, еще предстоит выяснить. Вы используете это еще? Дайте нам знать!