Начиная с версии 2.7.0 из Зенд-MVC ServiceLocatorAwareInterface
ограничен, так $this->serviceLocator->get()
звонки внутри контроллеров.
Вот почему несколько дней назад я провел огромный рефакторинг всех своих модулей, чтобы внедрить необходимые сервисы / объекты через конструкторы, использующие фабрики в основном для всего.
Конечно, я понимаю, почему это лучший / чище способ сделать что-то, потому что зависимости теперь гораздо более заметны. Но с другой стороны:
Это приводит к большим накладным расходам и гораздо большему количеству никогда не используемых экземпляров классов, не так ли?
Давайте посмотрим на пример:
Поскольку все мои контроллеры имеют зависимости, я создал фабрики для всех них.
CustomerControllerFactory.php
namespace Admin\Factory\Controller;
class CustomerControllerFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $controllerManager) {
$serviceLocator = $controllerManager->getServiceLocator();
$customerService = $serviceLocator->get('Admin\Service\CustomerService');
$restSyncService = $serviceLocator->get('Admin\Service\SyncRestClientService');
return new \Admin\Controller\CustomerController($customerService, $restSyncService);
}
}
CustomerController.php
namespace Admin\Controller;
class CustomerController extends AbstractRestfulController {
public function __construct($customerService, $restSyncService) {
$this->customerService = $customerService;
$this->restSyncService = $restSyncService;
}
}
module.config.php
'controllers' => [
'factories' => [
'Admin\Controller\CustomerController' => 'Admin\Factory\Controller\CustomerControllerFactory',
]
],
'service_manager' => [
'factories' => [
'Admin\Service\SyncRestClientService' => 'Admin\Factory\SyncRestClientServiceFactory',
]
]
SyncRestClientServiceFactory.php
namespace Admin\Factory;
class SyncRestClientServiceFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $serviceLocator) {
$entityManager = $serviceLocator->get('doctrine.entitymanager.orm_default');
$x1 = $serviceLocator->get(...);
$x2 = $serviceLocator->get(...);
$x3 = $serviceLocator->get(...);
// ...
return new \Admin\Service\SyncRestClientService($entityManager, $x1, $x2, $x3, ...);
}
}
SyncRestService — это сложный класс обслуживания, который запрашивает некоторый внутренний сервер нашей системы. Он имеет много зависимостей и всегда создается, если запрос поступает в CustomerController. Но этот сервис синхронизации только используется внутри syncAction()
CustomerController! Прежде чем я использовал просто $this->serviceLocator->get('Admin\Service\SyncRestClientService')
внутри syncAction()
так что только тогда это было воплощено.
В целом, похоже, что при каждом запросе создается множество экземпляров через фабрики, но большинство зависимостей не используются. Является ли это проблемой из-за моего дизайна или это обычное побочное действие «внедрения зависимости через конструкторы»?
На мой взгляд, это нормальный эффект внедрения зависимостей через конструкторы.
Я думаю, что теперь у вас есть два варианта (не взаимоисключающие), чтобы улучшить работу вашего приложения:
Разделите ваши контроллеры, так что зависимости создаются только при необходимости. Это, безусловно, приведет к большему количеству классов, большему количеству фабрик и так далее, но ваш код достигнет большего по принципу единой ответственности
Вы могли бы использовать Ленивые Услуги, так что, даже если некоторые службы являются зависимостями всего контроллера, они будут фактически созданы только при первом вызове (поэтому никогда не выполняются действия, когда они не вызваны!)
Если вы используете только SyncRestClientService
Внутри контроллера вы должны рассмотреть вопрос о замене его с сервиса на плагин контроллера (или сделать плагин контроллера, в который вы добавляете свой SyncRestClientService
).
Например, вы все равно можете получить его в своем контроллере. syncAction
метод очень похож на то, что вы делали раньше. Это как раз и является целью плагинов контроллера ZF2.
Сначала вам нужно создать класс плагина контроллера (расширяющий Zend\Mvc\Controller\Plugin\AbstractPlugin
):
<?php
namespace Application\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class SyncPlugin extends AbstractPlugin{
protected $syncRestClientService;
public function __constuct(SyncRestClientService $syncRestClientService){
$this->syncRestClientService = $syncRestClientService
}
public function sync(){
// do your syncing using the service that was injected
}
}
Затем фабрика вводит свой сервис в классе:
<?php
namespace Application\Controller\Plugin\Factory;
use Application\Controller\Plugin\SyncPlugin;
class SyncPluginFactory implements FactoryInterface
{
/**
* @param ServiceLocatorInterface $serviceController
* @return SyncPlugin
*/
public function createService(ServiceLocatorInterface $serviceController)
{
$serviceManager = $serviceController->getServiceLocator();
$syncRestClientService = $serviceManager>get('Admin\Service\SyncRestClientService');
return new SyncPlugin($syncRestClientService);
}
}
Тогда вам нужно зарегистрировать свой плагин в вашем module.config.php
:
<?php
return array(
//...
'controller_plugins' => array(
'factories' => array(
'SyncPlugin' => 'Application\Controller\Plugin\Factory\SyncPluginFactory',
)
),
// ...
);
Теперь вы можете использовать его внутри действия контроллера следующим образом:
protected function syncAction(){
$plugin = $this->plugin('SyncPlugin');
//now you can call your sync logic using the plugin
$plugin->sync();
}
Подробнее о плагинах контроллера здесь в документации
Может быть, вам нужна только одна зависимость для внедрения в конструктор контроллера (экземпляр ServiceManager). Я не вижу копов вокруг …
namespace Admin\Factory\Controller;
class CustomerControllerFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $controllerManager)
{
$serviceLocator = $controllerManager->getServiceLocator();
return new \Admin\Controller\CustomerController($serviceLocator);
}
}
Лично я получаю имя действия на фабрике контроллеров для внедрения сервисов для каждого действия.
Взгляните на мой сайт контроллера.
namespace Admin\Controller\Service;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Admin\Controller\SitesController;
use Admin\Model\Sites as Models;
class SitesControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$actionName = $serviceLocator->getServiceLocator()->get('Application')->getMvcEvent()->getRouteMatch()->getParam('action');
$controller = new SitesController();
switch ($actionName) {
case 'list':
$controller->setModel($serviceLocator->getServiceLocator()->get(Models\ListSitesModel::class));
break;
case 'view':
$controller->setModel($serviceLocator->getServiceLocator()->get(Models\ViewSiteModel::class));
break;
case 'add':
$controller->setModel($serviceLocator->getServiceLocator()->get(Models\AddSiteModel::class));
break;
case 'edit':
$controller->setModel($serviceLocator->getServiceLocator()->get(Models\EditSiteModel::class));
break;
}
return $controller;
}
}
Как видите, я использую $serviceLocator->getServiceLocator()->get('Application')->getMvcEvent()->getRouteMatch()->getParam('action');
чтобы получить имя действия и использовать инструкцию switch для внедрения зависимостей, когда это необходимо.
Я не знаю, является ли это лучшим решением, но оно работает для меня.
Надеюсь это поможет.