How to use dependency injection in Zend Framework?
目前我正在努力学习Zend框架,因此我买了本书"Zend框架的实际应用"。
在第三章中,介绍了一个基本模型和控制器,以及它们的单元测试。基本控制器如下:
1 2 3 4 5 6 7 8 9 | class IndexController extends Zend_Controller_Action { public function indexAction() { $this->view->title = 'Welcome'; $placesFinder = new Places(); $this->view->places = $placesFinder->fetchLatest(); } } |
我想要的是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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(); } } |
我发布的第一个代码示例显然不是单元测试友好的,因为
我知道Zend框架本身没有用于依赖注入的组件。但是有一些很好的PHP框架,可以和Zend框架一起使用吗?或者在Zend框架中是否有其他方法可以做到这一点?
逻辑到模型
首先,值得一提的是,控制器应该只需要功能测试,尽管所有的逻辑都属于模型。
我的实施下面是我的操作控制器实现的一个摘录,它解决了以下问题:
- 允许对操作插入任何依赖项
- 验证操作参数,例如,当需要整数时,不能在
$_GET 中传递数组
我的完整代码还允许生成基于标准URL(用于SEO或用于统计的唯一页哈希)或必需或处理的操作参数。为此,我使用这个抽象的动作控制器和自定义请求对象,但我们在这里讨论的情况并非如此。
显然,我使用反射自动确定动作参数和依赖对象。
这是一个巨大的优势,简化了代码,但对性能也有影响(对于我的应用程序和服务器来说,这是最小的,也不重要),但是您可以实现一些缓存来加快速度。计算出好处和缺点,然后决定。
docblock注释正在成为一个相当著名的行业标准,为了评估的目的而解析它变得更加流行(例如,条令2)。我在许多应用程序中使用了这种技术,它运行得很好。
写这节课的时候,我受到了动作的启发,现在和参数一起!以及Jani Hartikainen的博客帖子。
所以,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | <?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(); } } |
要使用它,只需:
1 | Your_FineController extends Your_Controller_Action {} |
并像往常一样为操作提供注释(至少您已经应该;)。
例如
1 2 3 4 5 6 7 8 9 10 | /** * @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框架,我使用了symfony框架的这个组件(但我没有下载最新版本,我使用了以前在项目中使用过的一个旧版本…。记住这一点!).I在
为了注册IOC框架的自动加载程序,我在我的
1 2 3 4 5 6 | protected function _initIocFrameworkAutoloader() { require_once(APPLICATION_PATH . '/../library/Ioc/lib/sfServiceContainerAutoloader.php'); sfServiceContainerAutoloader::register(); } |
接下来,我在
1 2 | ioc.controllers.wiringXml = APPLICATION_PATH"/objectconfiguration/controllers.xml" ioc.controllers.enableIoc = 1 |
然后,我创建了一个自定义生成器类,扩展了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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); } } } |
最后,我在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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框架来初始化已经创建的控制器实例(
它仍然是猴子补丁不知何故,但Zend框架似乎没有提供一个钩子,我可以创建一个自定义控制器工厂的控制器实例,所以这是我提出的最好的…
我目前正在研究同一个问题,经过深入研究,我决定使用symfony依赖注入组件。您可以从官方网站http://symfony.com/doc/current/book/service_container.html获得好的信息。
我已经在bootstrap中构建了自定义getContainer()方法,它现在重新生成服务容器,并且它可以简单地用于
1 2 3 4 5 | 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。但我改变了集装箱工厂,因为使用了symfony2组件,而不是第一个版本。
您也可以使用php-di zf桥:http://php-di.org/doc/frameworks/zf1.html
我知道这个问题真的很老,但是在ZF1中查找d i时,它在搜索引擎中出现得相当高,所以我想我会添加一个不需要你自己写的解决方案。
Zend Framework 3的服务管理器。
官方文件:
https://zendframework.github.io/zend-servicemanager/
例子:
控制器`
1 2 3 4 5 6 7 8 9 10 11 | class JsonController extends AbstractActionController { private $_jsonFactory; private $_smsRepository; public function __construct(JsonFactory $jsonFactory, SmsRepository $smsRepository) { $this->_jsonFactory = $jsonFactory; $this->_smsRepository = $smsRepository; } ... } |
Creates the Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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
我希望它能帮助别人