Among the standard object oriented principles is favouring composition over inheritance, and there are plenty of design patterns that work along this line. However, one of the most useful day-to-day facets of the idea doesn't seem to get a lot of attention from PHP developers, namely dependency injection.
The general idea is, that if your class depends on some other object, that object should be passed in rather than generated internally or retrieved via a global variable or singleton.
For example, a webservice client might query it's service using a SoapClient, and store the data in a Zend_Cache_Core object before returning it. In this case, it would be entirely normal to create the soap client and create a new cache object from a shared config resource.
However, it would be preferable to set the objects with setClient and setCache methods, or to pass the values in on the webservice client constructor. From a development point of view it make the object more flexible, manageable and shows you where the dependencies lie, but in my experience the biggest benefit comes when you want to unit test the class.
By injecting the cache and the soapclient, it is now easy to replace those with stubs or mocks and write tests. This even aids development, as if the service is unavailable (or the developer is trying to get something done on a train!) the unit test stubs can be used to return example data, and the processing code in the client to be developed independently.
Dependency injection is the standard way of working in some frameworks, such as the Java framework Spring (where it is sometimes also referred to as inversion of control). The main difficulty for a PHP developer is finding a good place to inject standard dependencies, especially in frameworks.
In Zend Framework MVC, injecting dependencies to models is fairly easy by calling a setter from the controller. Injecting into the controller itself is tricker, so here are a few ideas:
1) Zend Registry
This is perhaps the most straightforward, but also the least conceptually clean. For one, it isnt a true inversion of control, and it introduces a dependency on the Registry. It also has the potential of creating some "back door" coupling, where two classes become interdependent through modifications to the shared objects in the Registry. Still, from a pragmatic point of view it's a useful one to have in the toolbox.
To use, in the bootstrapper or wherever you need to create the objects, simply do:
Zend_Registry::set('widget', new Example_Widget());And in the controller, pick it up with:
Zend_Registry::get('widget');Zend Registry will throw an exception if the key has not been set before being retrieved.
2) Front Controller Params
The front controller's invocation parameters can be used to pass in objects as well.
Set an object up with:
$frontController = Zend_Controller_Front::getInstance();
$frontController->setParam('widget', new Example_Widget());
Then in the controller, you can retrieve the widget with:
$this->getInvokeArg('widget');This doesn't throw any kind of exception, so you'll need to check the data is ok with an instanceof.
3) Action Helper
The third, and probably most flexible method, is to use the action helper functionality present in Zend Framework. First you'll need to write a new action helper that implements your dependency logic:
<?php
class Example_Helper extends Zend_Controller_Action_Helper_Abstract {
protected $_widget;
public function init() {
$this->_widget = new Example_Widget();
}
public function preDispatch() {
$this->_actionController->setWidget($this->_widget);
}
}
?>
As you can see, this is a pretty simple example, but note that the actual injection is triggered in the pre-dispatch - there's no need for the action itself to do anything.
In the bootstrapper add:
Zend_Controller_Action_HelperBroker::addHelper(new Example_Helper());
And of course in the controller you'll need a function to store the object for later use, something like:
public function setWidget(Example_Widget $widget) {
$this->_widget = $widget;
}
Following last month's article by Ian, here's some thoughts on how to test a Zend Framework application. One of the unit testing best practices suggests to break dependencies, so you can test each component separately. The first problem that arises
Tracked: Aug 29, 15:23