php zend-framework dependency-injection

php - ¿Cómo utilizar la inyección de dependencias en Zend Framework?



zend-framework dependency-injection (5)

Lógica a modelos.

En primer lugar, vale la pena mencionar que los controladores solo necesitan pruebas funcionales, aunque toda la lógica pertenece a los modelos.

Mi implementacion

Aquí hay un extracto de mi implementación de Action Controller, que resuelve los siguientes problemas:

  • Permite inyectar cualquier dependencia a las acciones.
  • valida los parámetros de acción, por ejemplo, no puede pasar la matriz en $_GET cuando se espera un entero

Mi código completo también permite generar URL canónicas (para SEO o hash de página único para estadísticas) basadas o parámetros de acción requeridos o manejados. Para esto, uso este Controlador de acción abstracto y Objeto de solicitud personalizado, pero este no es el caso que discutimos aquí.

Obviamente, utilizo Reflexiones para determinar automáticamente los parámetros de acción y los objetos de dependencia.

Esta es una gran ventaja y simplifica el código, pero también tiene un impacto en el rendimiento (mínimo y no importante en el caso de mi aplicación y servidor), pero puede implementar algo de almacenamiento en caché para acelerarlo. Calcule los beneficios y los inconvenientes, luego decida.

Las anotaciones de DocBlock se están convirtiendo en un estándar de la industria bastante conocido, y analizarlas con fines de evaluación se vuelve más popular (por ejemplo, Doctrine 2). Utilicé esta técnica para muchas aplicaciones y funcionó muy bien.

Escribiendo esta clase me inspiré en Acciones, ¡ahora con params! y la publicación del blog de Jani Hartikainen .

Entonces, aquí está el código:

<?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, * e.g. * @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(); } }

Para usar esto, solo:

Your_FineController extends Your_Controller_Action {}

y proporcione anotaciones a las acciones, como de costumbre (al menos usted ya debería;).

p.ej

/** * @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); }

(Se requiere DocBlock, sin embargo uso Netbeans IDE, por lo que DocBlock se genera automáticamente en función de los argumentos de acción)

Actualmente estoy tratando de aprender Zend Framework y, por lo tanto, compré el libro "Zend Framework in Action".

En el capítulo 3, se presenta un modelo básico y un controlador junto con pruebas unitarias para ambos. El controlador básico se ve así:

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

Places es la clase modelo que obtiene los últimos lugares de la base de datos. ¿Qué me molesta aquí? ¿Cómo debo probar el IndexController de forma aislada ? Como la referencia a la clase de Places está "codificada", no puedo inyectar ningún código auxiliar o simulacro en IndexController .

Lo que me gustaría tener es algo como esto:

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(); } }

El primer ejemplo de código que publiqué definitivamente no es apto para pruebas unitarias, ya que IndexController no se puede probar de forma aislada. El segundo es mucho mejor. Ahora solo necesito alguna forma de inyectar las instancias del modelo en los objetos del controlador.

Sé que Zend Framework por sí mismo no tiene ningún componente para la inyección de dependencia. Pero hay algunos buenos marcos para PHP, ¿se pueden usar junto con Zend Framework? ¿O hay alguna otra manera de hacer esto en Zend Framework?


Actualmente estoy trabajando en la misma pregunta, y después de una investigación profunda, decidí usar el componente Inyección de dependencia de Symfony. Puede obtener buena información del sitio web oficial http://symfony.com/doc/current/book/service_container.html .

He creado el método personalizado getContainer () en bootstrap, que ahora devuelve el contenedor de servicio, y simplemente se puede usar en controladores como

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

Aquí puede encontrar cómo hacerlo http://blog.starreveld.com/2009/11/using-symfony-di-container-with.html . Pero cambié ContainerFactory, debido al uso del componente Symfony2, en lugar de la primera versión.


Con el Service Manager en Zend Framework 3.

Documentación oficial:

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

  1. Las dependencias en su Controlador generalmente son inyectadas por el inyector DI Constructor.
  2. Podría proporcionar un ejemplo, que inyecte un responsable de Factory para crear la instancia de ViewModel en el controlador.

Ejemplo:

Controlador `

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); } }

`Ejemplo completo en https://github.com/fmacias/SMSDispatcher

Espero que esto ayude a alguien


Ok, así es como lo hice:

Como IoC Framework usé este componente del framework Symfony (pero no descargué la última versión, usé una anterior que usé en proyectos antes ... ¡ten eso en cuenta!). /library/ioc/lib/ sus clases en /library/ioc/lib/ .

Agregué esta función init en mi Bootstrap.php para registrar el cargador automático del marco IoC:

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

A continuación, hice algunos ajustes en application.ini que establecían la ruta al xml de cableado y permitían desactivar la inyección de dependencia automática, por ejemplo, en pruebas unitarias:

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

Luego, creé una clase de constructor personalizada, que extiende sfServiceContainerBuilder y la puse en /library/MyStuff/Ioc/Builder.php . En este proyecto de prueba mantengo todas mis clases en /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); } } }

Por último, creé una clase de controlador personalizado en /library/MyStuff/Controller.php cual todos mis controladores heredan:

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); } }

Lo que esto hace básicamente es usar el Marco de IoC para inicializar la instancia de controlador ya creada ( $this ). Las pruebas simples que hice parecían hacer lo que quiero ... veamos cómo funciona esto en situaciones de la vida real. ;)

Todavía se está parcheando el mono de alguna manera, pero Zend Framework no parece proporcionar un gancho donde pueda crear la instancia del controlador con una fábrica de controladores personalizada, así que esto es lo mejor que se me ocurrió ...


También puede usar el puente PHP-DI ZF: http://php-di.org/doc/frameworks/zf1.html

Sé que esta pregunta es muy antigua, pero aparece bastante alta en los motores de búsqueda cuando busco DI en ZF1, así que pensé en agregar una solución que no requiera que la escriba por su cuenta.