Как использовать инъекцию зависимостей в Zend Framework?

В настоящее время я пытаюсь изучить Zend Framework, и поэтому я купил книгу «Zend Framework в действии».

В главе 3 вводится базовая модель и контроллер вместе с модульными тестами для обоих. Основной контроллер выглядит следующим образом:

class IndexController extends Zend_Controller_Action { public function indexAction() { $this->view->title = 'Welcome'; $placesFinder = new Places(); $this->view->places = $placesFinder->fetchLatest(); } } 

Places – это класс модели, который извлекает последние места из базы данных. Что меня здесь IndexController : как я IndexController изолировать IndexController ? Поскольку ссылка на класс Places «жестко закодирована», я не могу вводить какие-либо заглушки или IndexController в IndexController .

То, что я хотел бы иметь, это что-то вроде этого:

 class IndexController extends Zend_Controller_Action { private $placesFinder; // Here I can inject anything: mock, stub, the real instance public function setPlacesFinder($places) { $this->placesFinder = $places; } public function indexAction() { $this->view->title = 'Welcome'; $this->view->places = $this->placesFinder->fetchLatest(); } } 

Первый образец кода, который я опубликовал, наиболее определенно не является модульным тестированием, так как IndexController не может быть протестирован изолированно. Второй – намного лучше. Теперь мне просто нужно каким-то образом ввести экземпляры модели в объекты контроллера.

Я знаю, что Zend Framework сама по себе не имеет компонентов для инъекций зависимостей. Но есть ли какие-то хорошие рамки для PHP, можно ли использовать их вместе с Zend Framework? Или есть ли другой способ сделать это в Zend Framework?

Логика для моделей

Прежде всего, стоит упомянуть, что контроллерам нужны только функциональные тесты, хотя вся логика принадлежит моделям.

Моя реализация

Вот выдержка из моей реализации Action Controller, которая решает следующие проблемы:

  • позволяет вводить любую зависимость от действий
  • проверяет параметры действия, например, вы не можете передавать массив в $_GET когда ожидается целое число

Мой полный код позволяет также генерировать канонический URL (для SEO или уникальный хэш страницы для статистики) на основе или требуемых или обрабатываемых параметров действия. Для этого я использую этот абстрактный Action Controller и пользовательский объект Request, но это не тот случай, который мы обсуждаем здесь.

Очевидно, я использую Reflections для автоматического определения параметров действия и объектов зависимостей.

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

Аннотации DocBlock становятся довольно известным отраслевым стандартом, и анализ его для целей оценки становится более популярным (например, Doctrine 2). Я использовал этот метод для многих приложений, и он работал хорошо.

Написав этот класс, я был вдохновлен действиями, теперь с параметрами! и в блоге Джанни Хартикайнен .

Итак, вот код:

 <?php /** * Enchanced action controller * * Map request parameters to action method * * Important: * When you declare optional arguments with default parameters, * they may not be perceded by optional arguments, * eg * @example * indexAction($username = 'tom', $pageid); // wrong * indexAction($pageid, $username = 'tom'); // OK * * Each argument must have @param DocBlock * Order of @param DocBlocks *is* important * * Allows to inject object dependency on actions: * @example * * @param int $pageid * * @param Default_Form_Test $form * public function indexAction($pageid, Default_Form_Test $form = null) * */ abstract class Your_Controller_Action extends Zend_Controller_Action { /** * * @var array */ protected $_basicTypes = array( 'int', 'integer', 'bool', 'boolean', 'string', 'array', 'object', 'double', 'float' ); /** * Detect whether dispatched action exists * * @param string $action * @return bool */ protected function _hasAction($action) { if ($this->getInvokeArg('useCaseSensitiveActions')) { trigger_error( 'Using case sensitive actions without word separators' . 'is deprecated; please do not rely on this "feature"' ); return true; } if (method_exists($this, $action)) { return true; } return false; } /** * * @param string $action * @return array of Zend_Reflection_Parameter objects */ protected function _actionReflectionParams($action) { $reflMethod = new Zend_Reflection_Method($this, $action); $parameters = $reflMethod->getParameters(); return $parameters; } /** * * @param Zend_Reflection_Parameter $parameter * @return string * @throws Your_Controller_Action_Exception when required @param is missing */ protected function _getParameterType(Zend_Reflection_Parameter $parameter) { // get parameter type $reflClass = $parameter->getClass(); if ($reflClass instanceof Zend_Reflection_Class) { $type = $reflClass->getName(); } else if ($parameter->isArray()) { $type = 'array'; } else { $type = $parameter->getType(); } if (null === $type) { throw new Your_Controller_Action_Exception( sprintf( "Required @param DocBlock not found for '%s'", $parameter->getName() ) ); } return $type; } /** * * @param Zend_Reflection_Parameter $parameter * @return mixed * @throws Your_Controller_Action_Exception when required argument is missing */ protected function _getParameterValue(Zend_Reflection_Parameter $parameter) { $name = $parameter->getName(); $requestValue = $this->getRequest()->getParam($name); if (null !== $requestValue) { $value = $requestValue; } else if ($parameter->isDefaultValueAvailable()) { $value = $parameter->getDefaultValue(); } else { if (!$parameter->isOptional()) { throw new Your_Controller_Action_Exception( sprintf("Missing required value for argument: '%s'", $name)); } $value = null; } return $value; } /** * * @param mixed $value */ protected function _fixValueType($value, $type) { if (in_array($type, $this->_basicTypes)) { settype($value, $type); } return $value; } /** * Dispatch the requested action * * @param string $action Method name of action * @return void */ public function dispatch($action) { $request = $this->getRequest(); // Notify helpers of action preDispatch state $this->_helper->notifyPreDispatch(); $this->preDispatch(); if ($request->isDispatched()) { // preDispatch() didn't change the action, so we can continue if ($this->_hasAction($action)) { $requestArgs = array(); $dependencyObjects = array(); $requiredArgs = array(); foreach ($this->_actionReflectionParams($action) as $parameter) { $type = $this->_getParameterType($parameter); $name = $parameter->getName(); $value = $this->_getParameterValue($parameter); if (!in_array($type, $this->_basicTypes)) { if (!is_object($value)) { $value = new $type($value); } $dependencyObjects[$name] = $value; } else { $value = $this->_fixValueType($value, $type); $requestArgs[$name] = $value; } if (!$parameter->isOptional()) { $requiredArgs[$name] = $value; } } // handle canonical URLs here $allArgs = array_merge($requestArgs, $dependencyObjects); // dispatch the action with arguments call_user_func_array(array($this, $action), $allArgs); } else { $this->__call($action, array()); } $this->postDispatch(); } $this->_helper->notifyPostDispatch(); } } 

Чтобы использовать это, просто:

 Your_FineController extends Your_Controller_Action {} 

и предоставлять аннотации к действиям, как обычно (по крайней мере, вы уже должны;).

например

 /** * @param int $id Mandatory parameter * @param string $sorting Not required parameter * @param Your_Model_Name $model Optional dependency object */ public function indexAction($id, $sorting = null, Your_Model_Name $model = null) { // model has been already automatically instantiated if null $entry = $model->getOneById($id, $sorting); } 

(Требуется DocBlock, однако я использую Netbeans IDE, поэтому DocBlock автоматически создается на основе аргументов действия)

Хорошо, вот как я это сделал:

Как IoC Framework я использовал этот компонент структуры symfony (но я не загружал последнюю версию, я использовал более старый, который я использовал в проектах, прежде чем … помните об этом!). Я добавил свои классы в /library/ioc/lib/ .

Я добавил эту функцию init в свой Bootstrap.php , чтобы зарегистрировать автозагрузчик структуры IoC:

 protected function _initIocFrameworkAutoloader() { require_once(APPLICATION_PATH . '/../library/Ioc/lib/sfServiceContainerAutoloader.php'); sfServiceContainerAutoloader::register(); } 

Затем я сделал некоторые настройки в application.ini которые устанавливают путь к проводке xml и позволяют отключить автоматическую инъекцию зависимостей, например, в модульных тестах:

 ioc.controllers.wiringXml = APPLICATION_PATH "/objectconfiguration/controllers.xml" ioc.controllers.enableIoc = 1 

Затем я создал собственный класс строителя, который расширяет sfServiceContainerBuilder и помещает его под /library/MyStuff/Ioc/Builder.php . В этом тестовом проекте я держу все свои классы под /library/MyStuff/ .

 class MyStuff_Ioc_Builder extends sfServiceContainerBuilder { public function initializeServiceInstance($service) { $serviceClass = get_class($service); $definition = $this->getServiceDefinition($serviceClass); foreach ($definition->getMethodCalls() as $call) { call_user_func_array(array($service, $call[0]), $this->resolveServices($this->resolveValue($call[1]))); } if ($callable = $definition->getConfigurator()) { if (is_array($callable) && is_object($callable[0]) && $callable[0] instanceof sfServiceReference) { $callable[0] = $this->getService((string) $callable[0]); } elseif (is_array($callable)) { $callable[0] = $this->resolveValue($callable[0]); } if (!is_callable($callable)) { throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service))); } call_user_func($callable, $service); } } } 

Наконец, я создал собственный класс контроллера в /library/MyStuff/Controller.php который /library/MyStuff/Controller.php все мои контроллеры:

 class MyStuff_Controller extends Zend_Controller_Action { /** * @override */ public function dispatch($action) { // NOTE: the application settings have to be saved // in the registry with key "config" $config = Zend_Registry::get('config'); if($config['ioc']['controllers']['enableIoc']) { $sc = new MyStuff_Ioc_Builder(); $loader = new sfServiceContainerLoaderFileXml($sc); $loader->load($config['ioc']['controllers']['wiringXml']); $sc->initializeServiceInstance($this); } parent::dispatch($action); } } 

В основном это использование IoC Framework для инициализации уже созданного экземпляра контроллера ( $this ). Простые тесты, которые я делал, как будто делали то, что я хочу … давайте посмотрим, как это происходит в реальных жизненных ситуациях. 😉

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

В настоящее время я работаю над одним и тем же вопросом, и после глубоких исследований я решил использовать компонент Symfony Dependency Injection. Вы можете получить хорошую информацию с официального сайта http://symfony.com/doc/current/book/service_container.html .

Я создаю собственный метод getContainer () в bootstrap, который возвращает контейнер обслуживания, и его просто можно использовать в таких контроллерах, как

 public function init() { $sc = $this->getInvokeArg('bootstrap')->getContainer(); $this->placesService = $sc->get('PlacesService'); } 

Здесь вы можете найти, как это сделать: http://blog.starreveld.com/2009/11/using-symfony-di-container-with.html . Но я изменил ContainerFactory из-за использования компонента Symfony2 вместо первой версии.

Вы также можете использовать мост PHP-DI ZF: http://php-di.org/doc/frameworks/zf1.html

Я знаю, что этот вопрос действительно старый, но он довольно быстро появляется в поисковых системах при поиске DI в ZF1, поэтому я решил добавить решение, которое не требует, чтобы вы все это сами записали.

С Диспетчером обслуживания в Zend Framework 3.

Официальная документация:

https://zendframework.github.io/zend-servicemanager/

  1. Зависимости на вашем контроллере обычно вводят инжектором DI Constructor.
  2. Я мог бы привести один пример, который вводит фабрику, ответственную за создание экземпляра ViewModel в контроллере.

Пример:

Контроллер `

 class JsonController extends AbstractActionController { private $_jsonFactory; private $_smsRepository; public function __construct(JsonFactory $jsonFactory, SmsRepository $smsRepository) { $this->_jsonFactory = $jsonFactory; $this->_smsRepository = $smsRepository; } ... } 

Creates the Controller

 class JsonControllerFactory implements FactoryInterface { /** * @param ContainerInterface $serviceManager * @param string $requestedName * @param array|null $options * @return JsonController */ public function __invoke(ContainerInterface $serviceManager, $requestedName, array $options = null) { //improve using get method and callable $jsonModelFactory = new JsonFactory(); $smsRepositoryClass = $serviceManager->get(SmsRepository::class); return new JsonController($jsonModelFactory, $smsRepositoryClass); } } 

`Полный пример в https://github.com/fmacias/SMSDispatcher

Я надеюсь, что это поможет кому-то