Статьи

Дискотека с шаблонами дизайна: новый взгляд на инъекцию зависимостей

Внедрение зависимостей — это многократное использование кода. Это шаблон проектирования, направленный на то, чтобы сделать повторно используемый код высокого уровня путем отделения создания / конфигурации объекта от использования.

Иллюстрация очертаний людей, танцующих на дискотеке

Рассмотрим следующий код:

<?php class Test { protected $dbh; public function __construct(\PDO $dbh) { $this->dbh = $dbh; } } $dbh = new PDO('mysql:host=localhost;dbname=test', 'username', 'password'); $test = new Test($dbh) 

Как видите, вместо создания объекта PDO внутри класса, мы создаем его вне класса и передаем как зависимость — через метод конструктора. Таким образом, мы можем использовать драйвер по своему выбору вместо необходимости использовать драйвер, определенный внутри класса.

Наш собственный Алехандро Жервасио фантастически объяснил концепцию DI , и Фабьен Потенциер также осветил ее в серии .

Однако у этого шаблона есть один недостаток: когда число зависимостей растет, многие объекты необходимо создавать / настраивать, прежде чем передавать в зависимые объекты. Мы можем получить кучу шаблонного кода и длинную очередь параметров в наших методах конструктора. Введите Dependency Injection контейнеры!

Контейнер Dependency Injection — или просто DI-контейнер — это объект, который точно знает, как создать сервис и обработать его зависимости.

В этой статье мы продемонстрируем концепцию дальше с новичком в этой области: диско .

Для получения дополнительной информации о контейнерах внедрения зависимостей, смотрите другие наши посты на эту тему здесь .

Поскольку фреймворки являются отличными примерами развертывания DI-контейнеров, мы закончим статью, создав базовую фреймворк на основе HTTP с помощью Disco и некоторых компонентов Symfony .

Установка

Для установки Disco мы используем Composer как обычно:

 composer require bitexpert/disco 

Чтобы протестировать код, мы будем использовать встроенный веб-сервер PHP:

 php -S localhost:8000 -t web 

В результате приложение будет доступно по http://localhost:8000 из браузера. Последний параметр -t определяет корень документа — где находится файл index.php .

Начиная

Disco — это контейнер DI, совместимый с container_interop. Несколько спорно, диско является аннотацией на основе ДИ контейнера.

Обратите внимание, что пакет container_interop состоит из набора интерфейсов для стандартизации функций объектов контейнера. Чтобы узнать больше о том, как это работает, см. Учебник, в котором мы создаем наш собственный контейнер инъекции зависимостей SitePoint, также основанный на взаимодействии контейнеров.

Чтобы добавить сервисы в контейнер, нам нужно создать класс конфигурации . Этот класс должен быть помечен аннотацией @Configuration :

 <?php /** * @Configuration */ class Services { // ... } 

Каждый контейнерный сервис должен быть определен как открытый или защищенный метод внутри класса конфигурации. Диско называет каждый сервис Бином , который происходит из культуры Java.

Внутри каждого метода мы определяем, как должен быть создан сервис. Каждый метод должен быть помечен @Bean который подразумевает, что это сервис, и аннотациями @return которые отмечают тип возвращаемого объекта.

Это простой пример класса конфигурации Disco с одним «Bean»:

 <?php /** * @Configuration */ class Configuration { /** * @Bean * @return SampleService */ public function getSampleService() { // Instantiation $service = new SampleService(); // Configuration $service->setParameter('key', 'value'); return $service; } } 

Аннотация @Bean принимает несколько параметров конфигурации для указания характера службы. Должен ли это быть одноэлементный объект, лениво загруженный (если объект является ресурсоемким), или даже его состояние сохраняется в течение времени жизни сеанса, определяется этими параметрами.

По умолчанию все сервисы определены как одноэлементные сервисы.

Например, следующий компонент Bean создает одиночный загруженный сервис с отложенной загрузкой:

 <?php // ... /** * @Bean({"singleton"=true, "lazy"=true}) * @return \Acme\SampleService */ public function getSampleService() { return new SampleService(); } // ... 

Диско использует ProxyManager для ленивой загрузки сервисов. Он также использует его для добавления дополнительных поведений (определенных аннотациями) в методы класса конфигурации.

После того, как мы создадим класс конфигурации, нам нужно создать экземпляр AnnotationBeanFactory , передав ему класс конфигурации. Это будет наш контейнер.

Наконец, мы регистрируем контейнер в BeanFactoryRegistry :

 <?php // ... use \bitExpert\Disco\AnnotationBeanFactory; use \bitExpert\Disco\BeanFactoryRegistry; // ... // Setting up the container $container = new AnnotationBeanFactory(Services::class, $config); BeanFactoryRegistry::register($container); 

Как получить услугу из контейнера

Поскольку Disco совместим с container/interop , мы можем использовать методы get() и has() для объекта контейнера:

 // ... $sampleService = $container->get('sampleService'); $sampleService->callSomeMethod(); 

Сфера обслуживания

HTTP — это протокол без сохранения состояния, то есть при каждом запросе все приложение загружается и все объекты воссоздаются. Однако мы можем влиять на срок службы службы, передавая соответствующие параметры в аннотацию @Bean . Одним из этих параметров является scope . Область действия может быть либо request либо session .

Если область действия — session , состояние службы будет сохраняться в течение времени жизни сеанса. Другими словами, в последующих HTTP-запросах последнее состояние объекта извлекается из сеанса.

Давайте поясним это на примере. Рассмотрим следующий класс:

 <?php class sample { public $counter = 0; public function add() { $this->counter++; return $this; } } 

В приведенном выше классе значение $counter увеличивается каждый раз при вызове метода add() ; Теперь давайте добавим это в контейнер с областью действия, установленной на session :

 // ... /** * @Bean({"scope"="session"}) * @return Sample */ public function getSample() { return new Sample(); } // ... 

И если мы используем это так:

 // ... $sample = $container->get('getSample'); $sample->add() ->add() ->add(); echo $sample->counter; // output: 3 // ... ственного // ... $sample = $container->get('getSample'); $sample->add() ->add() ->add(); echo $sample->counter; // output: 3 // ... 

При первом запуске будет три. Если мы снова запустим скрипт (чтобы сделать еще один запрос ), значение будет шесть (вместо трех). Вот как состояние объекта сохраняется в запросах.

Если для области задано значение request , в последующих HTTP-запросах это значение всегда будет равно трем.

Параметры контейнера

Контейнеры обычно принимают параметры из внешнего мира. С помощью Disco мы можем передать параметры в контейнер в виде ассоциативного массива:

 // ... $parameters = [ // Database configuration 'database' => [ 'dbms' => 'mysql', 'host' => 'localhost', 'user' => 'username', 'pass' => 'password', ], ]; // Setting up the container $container = new AnnotationBeanFactory(Services::class, $parameters); BeanFactoryRegistry::register($container); 

Чтобы использовать эти значения внутри каждого метода класса конфигурации, мы используем аннотации @Parameters и @parameter :

 <?php // ... /** * @Bean * @Parameters({ * @parameter({"name"= "database"}) * }) * */ public function sampleService($database = null) { // ... } 

Дискотека в действии

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

Для создания ядра нашей платформы мы будем использовать некоторые компоненты Symfony .

HTTP-ядро

Сердце нашего каркаса. Предоставляет основы запроса / ответа.

Http Foundation

Хороший объектно-ориентированный слой вокруг PHP-глобальных суперглобалей.

маршрутизатор

Согласно официальному сайту: сопоставляет HTTP-запрос с набором переменных конфигурации — подробнее об этом ниже.

Диспетчер событий

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

Чтобы установить все эти компоненты:

 composer require symfony/http-foundation symfony/routing symfony/http-kernel symfony/event-dispatcher 

Как правило, мы будем хранить код Framework пространстве имен Framework .

Также зарегистрируем автозагрузчик PSR-4 . Для этого мы добавляем следующее отображение пространства имен в путь под ключом psr-4 в composer.json :

 // ... "autoload": { "psr-4": { "": "src/" } } // ... 

В результате все пространства имен будут искать в каталоге src/ . Теперь мы запускаем composer dump-autoload чтобы это изменение вступило в силу.

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

Ядро

Основой любого фреймворка является его ядро. Здесь запрос обрабатывается в ответ .

Мы не собираемся создавать ядро ​​с нуля. Вместо этого мы расширим класс Kernel только что установленного компонента HttpKernel .

 <?php // src/Framework/Kernel.php namespace Framework; use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\HttpKernel\HttpKernelInterface; class Kernel extends HttpKernel implements HttpKernelInterface { } 

Поскольку базовая реализация прекрасно работает для нас, мы не будем переопределять какие-либо методы, а вместо этого будем просто полагаться на унаследованную реализацию.

Маршрутизация

Объект Route содержит путь и обратный вызов , который вызывается (с помощью контроллера разрешения ) каждый раз, когда сопоставляется маршрут (с помощью URL Matcher ).

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

Объект контекста запроса содержит информацию о текущем запросе.

Вот как маршруты определяются с помощью компонента Routing :

 <?php // ... use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $routes = new RouteCollection(); $routes->add('route_alias', new Route('path/to/match', ['_controller' => function(){ // Do something here... }] )); 

Чтобы создать маршруты, нам нужно создать экземпляр RouteCollection (который также является частью компонента Routing ), а затем добавить к нему наши маршруты.

Чтобы сделать синтаксис маршрутизации более выразительным, мы создадим класс построителя маршрутов вокруг RouteCollection .

 <?php // src/Framework/RouteBuilder.php namespace Framework; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; class RouteBuilder { protected $routes; public function __construct(RouteCollection $routes) { $this->routes = $routes; } public function get($name, $path, $controller) { return $this->add($name, $path, $controller, 'GET'); } public function post($name, $path, $controller) { return $this->add($name, $path, $controller, 'POST'); } public function put($name, $path, $controller) { return $this->add($name, $path, $controller, 'PUT'); } public function delete($name, $path, $controller) { return $this->add($name, $path, $controller, 'DELETE'); } protected function add($name, $path, $controller, $method) { $this->routes->add($name, new Route($path, ['_controller' => $controller], ['_method' => $method])); return $this; } } 

Этот класс содержит экземпляр RouteCollection . В RouteBuilder для каждого HTTP-глагола есть метод, который вызывает add() . Мы сохраним наши определения маршрутов в файле src/routes.php :

 <?php // src/routes.php use Symfony\Component\Routing\RouteCollection; use Framework\RouteBuilder; $routeBuilder = new RouteBuilder(new RouteCollection()); $routeBuilder ->get('home', '/', function() { return new Response('It Works!'); }) ->get('welcome', '/welcome', function() { return new Response('Welcome!'); }); 

Фронт-контроллер

Точкой входа любого современного веб-приложения является его фронт-контроллер. Это файл PHP, обычно называемый index.php . Это где автозагрузчик класса включен, и приложение загружается.

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

 <?php //web/index.php require_once __DIR__ . '/../vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\EventListener\RouterListener; use Symfony\Component\HttpKernel\Controller\ControllerResolver; // Create a request object from PHP's global variables $request = Request::createFromGlobals(); $routes = include __DIR__.'/../src/routes.php'; $UrlMatcher = new Routing\Matcher\UrlMatcher($routes, new Routing\RequestContext()); // Event dispatcher & subscribers $dispatcher = new EventDispatcher(); // Add a subscriber for matching the correct route. We pass UrlMatcher to this class $dispatcher->addSubscriber(new RouterListener($UrlMatcher, new RequestStack())); $kernel = new Framework\Kernel($dispatcher, new ControllerResolver()); $response = $kernel->handle($request); // Sending the response $response->send(); 

В приведенном выше коде мы создаем экземпляр объекта Request на основе глобальных переменных PHP.

 <?php // ... $request = Request::createFromGlobals(); // ... 

Далее мы загружаем файл routes.php в $routes . Определение правильного маршрута является обязанностью класса UrlMatcher , поэтому мы создаем его, передавая коллекцию маршрутов вместе с объектом RequestContext .

 <?php // ... $routes = include __DIR__.'/../src/routes.php'; $UrlMatcher = new Routing\Matcher\UrlMatcher($routes, new Routing\RequestContext()); // ... 

Чтобы использовать экземпляр UrlMatcher , мы передаем его RouteListener событий RouteListener .

 <?php // ... // Event dispatcher & subscribers $dispatcher = new EventDispatcher(); // Add a subscriber for matching the correct route. We pass UrlMatcher to this class $dispatcher->addSubscriber(new RouterListener($UrlMatcher, new RequestStack())); // ... 

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

Наконец, мы создаем экземпляр ядра, передавая Dispatcher и экземпляр Controller Resolver — через его конструктор:

 <?php // ... $kernel = new Framework\Kernel($dispatcher, new ControllerResolver()); $response = $kernel->handle($request); // Sending the response $response->send(); // ... 

Время дискотеки

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

Пришло время позволить Disco соединить все эти части для нас.

Как и прежде, мы устанавливаем его с помощью Composer:

 composer require bitexpert/Disco; 

Затем мы создаем класс конфигурации и определяем службы, которые нам понадобятся во фронт-контроллере:

 <?php // src/Framework/Services.php use bitExpert\Disco\Annotations\Bean; use bitExpert\Disco\Annotations\Configuration; use bitExpert\Disco\Annotations\Parameters; use bitExpert\Disco\Annotations\Parameter; /** * @Configuration */ class Services { /** * @Bean * @return \Symfony\Component\Routing\RequestContext */ public function context() { return new \Symfony\Component\Routing\RequestContext(); } /** * @Bean * * @return \Symfony\Component\Routing\Matcher\UrlMatcher */ public function matcher() { return new \Symfony\Component\Routing\Matcher\UrlMatcher($this->routeCollection(), $this->context()); } /** * @Bean * @return \Symfony\Component\HttpFoundation\RequestStack */ public function requestStack() { return new \Symfony\Component\HttpFoundation\RequestStack(); } /** * @Bean * @return \Symfony\Component\Routing\RouteCollection */ public function routeCollection() { return new \Symfony\Component\Routing\RouteCollection(); } /** * @Bean * @return \Framework\RouteBuilder */ public function routeBuilder() { return new \Framework\RouteBuilder($this->routeCollection()); } /** * @Bean * @return \Symfony\Component\HttpKernel\Controller\ControllerResolver */ public function resolver() { return new \Symfony\Component\HttpKernel\Controller\ControllerResolver(); } /** * @Bean * @return \Symfony\Component\HttpKernel\EventListener\RouterListener */ protected function listenerRouter() { return new \Symfony\Component\HttpKernel\EventListener\RouterListener( $this->matcher(), $this->requestStack() ); } /** * @Bean * @return \Symfony\Component\EventDispatcher\EventDispatcher */ public function dispatcher() { $dispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher(); $dispatcher->addSubscriber($this->listenerRouter()); return $dispatcher; } /** * @Bean * @return Kernel */ public function framework() { return new Kernel($this->dispatcher(), $this->resolver()); } } 

Похоже, много кода; но на самом деле это тот же код, который ранее находился во фронт-контроллере.

Перед использованием класса нам нужно убедиться, что он был загружен автоматически, добавив его в ключ files в нашем файле composer.json :

 // ... "autoload": { "psr-4": { "": "src/" }, "files": [ "src/Services.php" ] } // ... 

А теперь на наш фронт контроллер.

 <?php //web/index.php require_once __DIR__ . '/../vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $container = new \bitExpert\Disco\AnnotationBeanFactory(Services::class); \bitExpert\Disco\BeanFactoryRegistry::register($container); $routes = include __DIR__.'/../src/routes.php'; $kernel = $container->get('framework') $response = $kernel->handle($request); $response->send(); 

Теперь наш фронтальный контроллер может дышать! Все экземпляры выполняются Disco, когда мы запрашиваем услугу.

Но как насчет конфигурации?

Как объяснялось ранее, мы можем передавать параметры в виде ассоциативного массива в класс AnnotationBeanFactory .

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

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

Давайте держать их в каталоге Config :

 // Config/dev.php return [ 'debug' => true; ]; 

И для производства:

 // Config/prod.php return [ 'debug' => false; ]; 

Чтобы обнаружить среду, мы определим среду в специальном текстовом файле , так же как мы определяем переменную среды:

 ENV=dev 

Для разбора файла мы используем PHP dotenv , пакет, который загружает переменные среды из файла (по умолчанию имя файла .env ) в .env PHP $_ENV . Это означает, что мы можем получить значения с помощью PHP-функции getenv () .

Чтобы установить пакет:

 composer require vlucas/phpdotenv 

Затем мы создаем наш файл .env каталоге Config/ .

Config / .env

 ENV=dev 

Во фронт-контроллере мы загружаем переменные окружения, используя PHP dotenv:

 <?php //web/index.php // ... // Loading environment variables stored .env into $_ENV $dotenv = new Dotenv\Dotenv(__DIR__ . '/../Config'); $dotenv->load(); // Load the proper configuration file based on the environment $parameters = require __DIR__ . '/../config/' . getenv('ENV') . '.php'; $container = new \bitExpert\Disco\AnnotationBeanFactory(Services::class, $parameters); \bitExpert\Disco\BeanFactoryRegistry::register($container); // ... 

В предыдущем коде мы сначала указываем каталог, в котором находится наш файл .env , затем вызываем load() для загрузки переменных среды в $_ENV . Наконец, мы используем getenv() чтобы получить правильное имя файла конфигурации.

Создание Контейнерного Строителя

Есть еще одна проблема с кодом в его текущем состоянии: всякий раз, когда мы хотим создать новое приложение, мы должны создать экземпляр AnnotationBeanFactory в нашем фронт-контроллере ( index.php ). В качестве решения мы можем создать фабрику, которая создает контейнер при необходимости.

 <?php // src/Factory.php namespace Framework; class Factory { /** * Create an instance of Disco container * * @param array $parameters * @return \bitExpert\Disco\AnnotationBeanFactory */ public static function buildContainer($parameters = []) { $container = new \bitExpert\Disco\AnnotationBeanFactory(Services::class, $parameters); \bitExpert\Disco\BeanFactoryRegistry::register($container); return $container; } } 

Эта фабрика имеет статический метод buildContainer() , который создает и регистрирует контейнер Disco.

Вот как это улучшает наш фронт-контроллер:

 <?php //web/index.php require_once __DIR__ . '/../vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; // Getting the environment $dotenv = new Dotenv\Dotenv(__DIR__ . '/../config'); $dotenv->load(); // Load the proper configuration file based on the environment $parameters = require __DIR__ . '/../config/' . getenv('ENV') . '.php'; $request = Request::createFromGlobals(); $container = Framework\Factory::buildContainer($parameters); $routes = include __DIR__.'/../src/routes.php'; $kernel = $container->get('framework') $response = $kernel->handle($request); $response->send(); 

Это выглядит намного аккуратнее, не так ли?

Класс приложения

Мы можем сделать шаг вперед с точки зрения удобства использования и абстрагировать оставшиеся операции (во фронт-контроллере) в другой класс. Давайте назовем этот класс Application :

 <?php namespace Framework; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpFoundation\Request; class Application { protected $kernel; public function __construct(HttpKernelInterface $kernel) { $this->kernel = $kernel; } public function run() { $request = Request::createFromGlobals(); $response = $this->kernel->handle($request); $response->send(); } } 

Application зависит от ядра и работает как оболочка вокруг него. Мы создаем метод с именем run() , который заполняет объект запроса и передает его ядру для получения ответа.

Чтобы сделать его еще круче, давайте добавим и этот класс в контейнер:

 <?php // src/Framework/Services.php // ... /** * @Bean * @return \Framework\Application */ public function application() { return new \Framework\Application($this->kernel()); } // ... 

И это новый вид нашего фронт-контроллера:

 <?php require_once __DIR__ . '/../vendor/autoload.php'; // Getting the environment $dotenv = new Dotenv\Dotenv(__DIR__ . '/../config'); $dotenv->load(); // Load the proper configuration file based on the environment $parameters = require __DIR__ . '/../config/' . getenv('ENV') . '.php'; // Build a Disco container using the Factory class $container = Framework\Factory::buildContainer($parameters); // Including the routes require __DIR__ . '/../src/routes.php'; // Running the application to handle the response $app = $container->get('application') ->run(); 

Создание слушателя ответа

Мы можем использовать эту платформу сейчас, но все еще есть возможности для улучшения. В настоящее время мы должны возвращать экземпляр Response в каждом контроллере, в противном случае ядро ​​создает исключение:

 <?php // ... $routeBuilder ->get('home', '/', function() { return new Response('It Works!'); }); ->get('welcome', '/welcome', function() { return new Response('Welcome!'); }); // ... 

Тем не менее, мы можем сделать его необязательным и разрешить отправлять обратно чистые строки. Для этого мы создаем специальный класс подписчика, который автоматически создает объект Response если возвращаемое значение является строкой.

Подписчики должны реализовать интерфейс Symfony\Component\EventDispatcher\EventSubscriberInterface . Они должны реализовать метод getSubscribedMethods() в котором мы определяем события, на которые мы заинтересованы подписаться, и их прослушиватели событий.

В нашем случае нас интересует событие KernelEvents::VIEW . Событие происходит, когда ответ должен быть возвращен.

Вот наш класс подписчика:

 <?php // src/Framework/StringResponseListener namespace Framework; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\HttpKernel\KernelEvents; class StringResponseListener implements EventSubscriberInterface { public function onView(GetResponseForControllerResultEvent $event) { $response = $event->getControllerResult(); if (is_string($response)) { $event->setResponse(new Response($response)); } } public static function getSubscribedEvents() { return array(KernelEvents::VIEW => 'onView'); } } 

Внутри метода слушателя onView мы сначала проверяем, является ли ответ строкой (а не объектом- Response ), а затем создаем объект-ответ, если требуется.

Чтобы использовать подписчика, нам нужно добавить его в контейнер как защищенный сервис:

 <?php // ... /** * @Bean * @return \Framework\StringResponseListener */ protected function ListenerStringResponse() { return new \Framework\StringResponseListener(); } // ... 

Затем мы добавляем его в диспетчерскую службу:

 <?php // ... /** * @Bean * @return \Symfony\Component\EventDispatcher\EventDispatcher */ public function dispatcher() { $dispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher(); $dispatcher->addSubscriber($this->listenerRouter()); $dispatcher->addSubscriber($this->ListenerStringResponse()); return $dispatcher; } // ... 

Отныне мы можем просто возвращать строку в наших функциях контроллера:

 <?php // ... $routeBuilder ->get('home', '/', function() { return 'It Works!'; }) ->get('welcome', '/welcome', function() { return 'Welcome!'; }); // ... 

Каркас готов сейчас.

Вывод

Мы создали базовый HTTP-фреймворк с помощью Symfony Components и Disco. Это просто базовая структура запросов / ответов, в которой отсутствуют какие-либо другие концепции MVC, такие как модели и представления, но она позволяет реализовать любые дополнительные архитектурные шаблоны, которые мы можем пожелать.

Полный код доступен на Github .

Disco является новичком в игре DI-контейнера и, по сравнению со старыми, в ней отсутствует исчерпывающая документация. Эта статья была попыткой обеспечить плавное начало для тех, кто может найти этот новый вид DI-контейнера интересным.

Вы склеиваете компоненты своего приложения с DI-контейнерами? Если да, то какие? Вы дали дискотеке попробовать? Дайте нам знать!