Обычно считается, что контроллеры — наиболее связанные классы в приложении. Как правило, на основании данных запроса они получают или сохраняют данные в базу данных, затем превращают данные или результат сохранения в HTML, который выступает в качестве ответа клиенту, который произвел запрос.
Получается, что контроллеры — повсюду, они соединяют те части приложения, которые обычно достаточно независимы друг от друга. Это сильно повышает связанность контроллеров: среди их зависимостей есть менеджер сущностей Doctrine, шаблонизатор Twig, базовый контроллер из FrameworkBundle, и прочее.
В этой записи я покажу, что этот уровень связанности совершенно не нужен. Я покажу вам, как значительно понизить связанность, предприняв всего несколько простых шагов. В результате мы получим контроллер, который можно будет повторно использовать в разных типах приложений, например, на базе Silex или даже Drupal.
В основном, классы контроллеров, с которыми я сталкиваюсь, наследуют от класса Controller из FrameworkBundle:
Базовый класс контроллера добавляет несколько полезных сокращений, вроде createNotFoundException() и redirect(). А еще он автоматически делает ваш контроллер наследником ContainerAware («осведомленным» о контейнере), в результате чего в контроллер будет автоматически инъектироваться контейнер внедрения зависимостей (Dependency Injection Container, DIC). Из контейнера можно будет получить любой нужный вам сервис.
Всё это, конечно, кажется очень удобным, но если чуть меньше лениться, можно заметно понизить связанность. Нет ничего страшного в том, чтобы выкинуть эти вспомогательные методы: тела большинства из них все равно состоят всего из одной строки (посмотрите в базовый контроллер, чтобы понять, что происходит там на самом деле!). Вы запросто можете заменить вызовы этих методов кодом из них:
Если вы больше не используете вспомогательные методы из родительского класса контроллера, то можно сделать еще один шаг для избавления от этого ненужного на самом деле наследования. Вместо того, чтобы получать зависимости из контейнера, инъектируйте их вручную в конструкторе:
Вам также надо убедиться в том, что новый контроллер-сервис собирается не просто через вызов оператора new, как обычно поступает ControllerResolver, а все зависимости вашего контроллера корректно инъектируются. Для этого вам нужно объявить сервис:
И настройки роутинга тоже надо поправить. Если вы пользуетесь аннотациями:
И если ваш роутинг сохранен в YML-файле конфигурации:
Наконец, больше нет необходимости наследоваться от стандартного симфонийского контроллера, нам также не надо, чтобы контроллер знал что-либо о контейнере DI, поскольку все его зависимости инъектируются в аргументах конструктора. И мы больше не зависим от вспомогательных методов базового класса, что значит, что теперь мы можем окончательно убрать extends Controller вместе с use-выражением из объявления класса нашего контроллера:
Вот таким образом мы и получаем много бонусных очков за несвязанный код!
В следующем посте мы поговорим про аннотации и про то, как наш код станет еще менее связанным, если мы от них избавимся.
Получается, что контроллеры — повсюду, они соединяют те части приложения, которые обычно достаточно независимы друг от друга. Это сильно повышает связанность контроллеров: среди их зависимостей есть менеджер сущностей Doctrine, шаблонизатор Twig, базовый контроллер из FrameworkBundle, и прочее.
В этой записи я покажу, что этот уровень связанности совершенно не нужен. Я покажу вам, как значительно понизить связанность, предприняв всего несколько простых шагов. В результате мы получим контроллер, который можно будет повторно использовать в разных типах приложений, например, на базе Silex или даже Drupal.
Часть I: не используйте стандартные контроллеры
Излишняя связанность: наследование от базового контроллера
В основном, классы контроллеров, с которыми я сталкиваюсь, наследуют от класса Controller из FrameworkBundle:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MyController extends Controller
{
...
}
Базовый класс контроллера добавляет несколько полезных сокращений, вроде createNotFoundException() и redirect(). А еще он автоматически делает ваш контроллер наследником ContainerAware («осведомленным» о контейнере), в результате чего в контроллер будет автоматически инъектироваться контейнер внедрения зависимостей (Dependency Injection Container, DIC). Из контейнера можно будет получить любой нужный вам сервис.
Не используйте вспомогательные методы!
Всё это, конечно, кажется очень удобным, но если чуть меньше лениться, можно заметно понизить связанность. Нет ничего страшного в том, чтобы выкинуть эти вспомогательные методы: тела большинства из них все равно состоят всего из одной строки (посмотрите в базовый контроллер, чтобы понять, что происходит там на самом деле!). Вы запросто можете заменить вызовы этих методов кодом из них:
class MyController extends Controller
{
public function someAction()
{
// было:
throw $this->createNotFoundException($message);
// стало:
throw new NotFoundHttpException($message);
}
}
Используйте внедрение зависимостей
Если вы больше не используете вспомогательные методы из родительского класса контроллера, то можно сделать еще один шаг для избавления от этого ненужного на самом деле наследования. Вместо того, чтобы получать зависимости из контейнера, инъектируйте их вручную в конструкторе:
class MyController extends Controller
{
public function someAction()
{
$this->get('doctrine')->getManager(...)->persist(...);
}
}
// превратится в:
use Doctrine\Common\Persistence\ManagerRegistry;
class MyController extends Controller
{
public function __construct(ManagerRegistry $doctrine)
{
$this->doctrine = $doctrine;
}
public function someAction()
{
$this->doctrine->getManager(...)->persist(...);
}
}
Сделайте ваш контроллер сервисом
Вам также надо убедиться в том, что новый контроллер-сервис собирается не просто через вызов оператора new, как обычно поступает ControllerResolver, а все зависимости вашего контроллера корректно инъектируются. Для этого вам нужно объявить сервис:
services:
my_controller:
class: MyController
arguments:
- @doctrine
И настройки роутинга тоже надо поправить. Если вы пользуетесь аннотациями:
/**
* @Route(service="my_controller")
*/
class MyController extends Controller
{
...
}
И если ваш роутинг сохранен в YML-файле конфигурации:
my_action:
path: /some/path
defaults:
_controller: my_controller:someAction
Наконец, больше нет необходимости наследоваться от стандартного симфонийского контроллера, нам также не надо, чтобы контроллер знал что-либо о контейнере DI, поскольку все его зависимости инъектируются в аргументах конструктора. И мы больше не зависим от вспомогательных методов базового класса, что значит, что теперь мы можем окончательно убрать extends Controller вместе с use-выражением из объявления класса нашего контроллера:
class MyController
{
}
Вот таким образом мы и получаем много бонусных очков за несвязанный код!
В следующем посте мы поговорим про аннотации и про то, как наш код станет еще менее связанным, если мы от них избавимся.