Зачем использовать Service Manager в Zend Framework 2?
Допустим, у меня есть услуга:
namespace Helloworld\Service;
class GreetingService
{
public function getGreeting()
{
if(date("H") <= 11)
return "Good morning, world!";
else if (date("H") > 11 && date("H") < 17)
return "Hello, world!";
else
return "Good evening, world!";
}
}
и я создаю invokable для него
public function getServiceConfig()
{
return array(
'invokables' => array(
'greetingService'
=> 'Helloworld\Service\GreetingService'
)
);
}
тогда в моем контроллере я мог бы сделать:
public function indexAction()
{
$greetingSrv = $this->getServiceLocator()
->get('greetingService');
return new ViewModel(
array('greeting' => $greetingSrv->getGreeting())
);
}
предположительно это делает контроллер зависимым от сервиса (и ServiceManager)
и лучшее решение - создать фабрику для этой службы или вернуть замыкание в ServiceManager и создать установщик в контроллере:
class IndexController extends AbstractActionController
{
private $greetingService;
public function indexAction()
{
return new ViewModel(
array(
'greeting' => $this->greetingService->getGreeting()
)
);
}
public function setGreetingService($service)
{
$this->greetingService = $service;
}
}
а также
'controllers' => array(
'factories' => array(
'Helloworld\Controller\Index' => function($serviceLocator) {
$ctr = new Helloworld\Controller\IndexController();
$ctr->setGreetingService(
$serviceLocator->getServiceLocator()
->get('greetingService')
);
return $ctr;
}
)
)
У меня вопрос почему? Почему второй подход лучше первого? и что это значит, что controller is dependent of the service
?
Спасибо
4 ответа
ServiceManager
по умолчанию вводится в любой контроллер ZF2, так как он расширяет AbstractController
реализация ServiceLocatorAwareInterface
интерфейс.
Второй подход имеет форму " избыточности", потому что, помимо того, что уже имеет доступ к ServiceManager
Например, когда вам нужно разделить ваш сервис между вашими контроллерами, вы должны настроить для каждого из них механизм внедрения. Поскольку ваши контроллеры уже имеют зависимость от ServiceManager, было бы более целесообразно использовать первый подход и зарегистрировать связанные с доменом службы в ServiceManager
централизуя таким образом доступ к Сервисному уровню.
Примечание: следующая часть ответа может выходить за рамки вопроса, но она направлена на то, чтобы предоставить "скрытый" фон оригинала.
Давайте предположим, что мы создаем сложную систему, в которой продвигаются слабая связь, возможность повторного использования и возможности тестирования. Наша система многослойна, и мы построили все до уровня обслуживания. Обратите внимание, что до сих пор мы еще не рассматривали веб-слой "MVC" и даже не выбирали данную платформу.
В рамках нашего сервисного уровня (я буду рассматривать этот уровень, так как он упоминается в вопросе), мы предполагаем, что мы приняли принцип разделения между бизнес-логикой и построением графов объектов (или разрешением зависимостей). Таким образом, у нас есть, вероятно, несколько сложных сервисов, которые создаются через фабрики.
Теперь, когда наш сервисный уровень построен, мы решили "подключить" его выше ZF2. Естественно, наши сервисы должны быть доступны с контроллеров, как показывает ваш вопрос, у нас есть больше, чем способ сделать это. Однако мы хотим избежать избыточности и использовать то, что мы уже создали. Давайте предположим следующую фабрику:
//The following class is a part of our Service layer
public class ComplexServiceFactory{
private dependencyA;
private dependencyB;
public function buildComplexService()
{
$service = new ComplexService();
$service->setDependencyA($this->dependecyA);
$service->setDependencyB($this->dependecyB);
return $service;
}
}
Теперь мы просто настроим (фактически расширим) нашу фабрику, чтобы она стала пригодной для использования ServiceManager
логика. Этот класс можно рассматривать как часть механизма, используемого для "подключения" нашей системы к ZF2 (на самом деле это адаптер)
public class SMComplexServiceFactory extends ComplexServiceFactory implements
Zend\ServiceManager\FactoryInterface
{
public function createService(ServiceLocatorInterface $sm)
{
$this->setDependencyA($sm->get('dependecyA'));
$this->setDependencyB($sm->get('dependecyB'));
return parent::buildComplexService;
}
}
Таким образом, мы не переносим построение объектного графа на уровень MVC (в противном случае это будет нарушением разделения интересов и ненужной межуровневой связью). Service Manager + наши "адаптированные" классы фабрик в некотором смысле являются нашим механизмом разрешения зависимостей. Дело в том, что наша Система по-прежнему переносима, мы можем, например, выбрать другую систему (другую платформу) и иметь низкую зависимость от платформы MVC.
Очень полезный вопрос. Он был поднят неоднократно. Я думаю, что вы можете получить точный ответ здесь
Чтобы добавить что-то к ответу @yechabbis:
Заводской шаблон для ServiceConfiguration
действительно для сложной инфраструктуры или просто не использовать closures
/ callable functions
внутри конфигурации. Это по двум причинам:
- Читабельность / Чистый код
- Спектакль
Настройка заводского шаблона внутри getServiceConfig
это красиво, чисто и быстро. Никакие классы не создаются, но это происходит при вызове сервисного ключа. Однако если вы настраиваете сервисы, используя шаблон замыкания или вызываемую функцию, то эти классы всегда будут создаваться при каждом отдельном запросе!
Это, вероятно, просто пример, но код, который вы показали, также лучше всего использовать как ViewHelper
Я думаю, что второй подход лучше, что делает класс контроллера независимым от GreetingService. Этот подход будет полезен, когда вы хотите использовать другую службу приветствия, которая будет использоваться контроллером. Вам вообще не нужно менять код в классе контроллера, вместо этого вы делаете это путем изменения менеджера сервисов с этим закрытием фабрики.
Я считаю, что это основная идея инверсии управления или внедрения зависимостей, все подключения и конфигурация выполняются вне класса (в данном случае: класс контроллера).
Так что, на мой взгляд, именно поэтому второй подход лучше.