PHP, Zend Framework and Other Crazy Stuff
Posts tagged bdd
The Zend Framework, Dependency Injection and Zend_Di
Feb 5th
A while ago I wrote this neat little subclass of Zend_Loader to add a midgen of Dependency Injection to the Zend Framework application I was then building. After emailing the lists to propose it be fully adopted into Zend_Loader some time ago, I realised someone had proposed a novel new component called Zend_Di, and that small change was since accepted by Federico into his DI buster. This is a quick overview with very simple use cases - read the full proposal for even more gory details about your object innards using DI…
http://framework.zend.com/wiki/display/ZFPROP/Zend_Di+-+Dependency+Injection+Container
But what is Dependency Injection (DI)? And why should you care?
Dependency Injection is both the ultimate bane and blessing in PHP programming. If you’re an experienced object oriented programmer, chances are you already know what the term means, and why it’s an all-consuming obsession. If you don’t, then here’s an overview.
When you start mucking about with objects you eventually realise that the best way to get objects working together towards a unified objective, is through composition. In other words, objects use other objects, which in turn can use others. You end up not with a restrictive inheritance tree, but a pool of objects injected into each other to build up an overall purpose. The problem of course is how to inject one object into another!
We can identify a few methods:
1. Pass objects in via a constructor
2. Pass object in via a setter
3. Let some external object manage it
The first two are prevelant in the Zend Framework and for good reason - it helps ensure source code can be maintained in a highly decoupled state. Which make it easier to subclass the Zend Framework to death , and modify it’s components before use.
The third is often confused with the Singleton and Registry design patterns. In short, people sometimes think that banging all dependencies into a Registry and then retrieving from within objects is the only extent of dependency injection required. Now it’s quite true a Registry goes a long way, but let’s remember the Registry has to get into the object before you used it. FYI - you probably passed in through a setter (perhaps as a Front Controller user parameter) or are using the Registry class as a Singleton Registry (calling Zend_Registry::get() statically for example). Basically, the object’s dependency becomes the Registry… And let’s assume the Registry is only useful for objects used very frequently by all Controllers. And then let’s assume mocking objects from a static scope is less then exciting…
A neat example I like to use is that of a Controller. Let’s say you wanted to create a Controller Action which fires off an email to your address. We can make a few simple assumptions:
1. Only a few Controller Actions need Email support
2. An instance of Zend_Email won’t be in a Registry
3. We’ll assume Zend_Mail’s transport was setup previously (via static calls on Zend_Mail)
Here’s the sample Controller (accessed from http://example.com/email/developer):
[geshi lang=php]class EmailController extends Zend_Controller_Action
{
public function developerAction()
{
$mail = new Zend_Mail;
$mail->setBodyText(‘This is the text of the mail.’);
$mail->setFrom(‘[email protected]’, ‘Some Sender’);
$mail->addTo(‘[email protected]’, ‘Some Recipient’);
$mail->setSubject(‘TestSubject’);
$mail->send();
}
}[/geshi]
Small problem, how do we mock Zend_Mail?
It’s an increasingly common practice in the real world to apply Test-Driven Development (or to a much lesser barely existing extent in PHP, Behaviour-Driven Development (BDD) ), before writing implementation code. Part of those practices is to isolate the system under test (SUT), or in BDD parlance to identify the behaviour being specified.
In our case, it’s simply that the Controller should send an email. Do we need to actually send an email? Well, if you really want to test Zend_Mail and don’t trust Simon Mundy… . Otherwise we should either stub, or mock, Zend_Mail out of the system.
How about?
[geshi lang=php]class EmailController extends Zend_Controller_Action
{
public function developerAction()
{
$mail = Zend_Di::create(‘Zend_Mail’);
$mail->setBodyText(‘This is the text of the mail.’);
$mail->setFrom(‘[email protected]’, ‘Some Sender’);
$mail->addTo(‘[email protected]’, ‘Some Recipient’);
$mail->setSubject(‘TestSubject’);
$mail->send();
}
}[/geshi]
Hey?! Where’s my Zend_Mail “new” keyword vanished?
The above is a viable use case for the new Zend_Di proposal. In short, Zend_Di offers a level of indirection, whereby you use an external system (Zend_Di) to create objects while your testing framework (or PHPSpec) can access the same external system to implant a replacement Mock Object when executing tests. In this simple case, it’s basically a proxy to Zend_Loader::loadClass() and a little Reflection.
If you can bang Zend Framework ops into PHPUnit, it works there too. For now indulge a PHPSpec developer:
[geshi lang=php]class DescribeEmailController extends PHPSpec_Context_Zend
{
public function itShouldSendEmailToDeveloperOnDeveloperAction()
{
$mockMail = PHPMock::mock(‘Zend_Mail’);
Zend_Di::replaceClass(‘Zend_Mail’, $mockMail);
$mockMail->shouldReceive(‘setBodyText’)->with(‘This is the text of the mail.’)->once()->ordered();
$mockMail->shouldReceive(‘setFrom’)->with(‘[email protected]’, ‘Some Sender’)->once()->ordered();
$mockMail->shouldReceive(‘addTo’)->with(‘[email protected]’, ‘Some Recipient’)->once()->ordered();
$mockMail->shouldReceive(‘setSubject’)->with(‘TestSubject’)->once()->ordered();
$mockMail->shouldReceive(‘send’)->withNoArgs()->once()->ordered();
$this->get(‘developer’);
$mockMail->verify();
}
}[/geshi]
We’re cutting it fine by omitting configuration of the email details, but you get the point. To write a Controller test or spec, you really need to isolate the Controller by mocking its dependencies to ensure the Controller behaves as expected, and interacts with Zend_Mail as expected. We already know ZF has unit tests for Zend_Mail.Now you could take the route of functional or acceptance testing, but since that’s for a different purpose (client req’s met?) it’s not that useful in a TDD or BDD session when the View doesn’t even exist yet and this is not a final Controller version .
But that’s just Zend_Di with a little indirection. What about this?!
[geshi lang=php]class FormatController extends Zend_Controller_Action
{
public function boldemphasisAction()
{
$text = $this->getRequest()->text;
$formatter = new Text_Bold(new Text_Emphasis($text));
$this->view->text = $formatter->toString();
}
}[/geshi]
Decorating some text with HTML/CSS is just a simple idea… But how to mock this entire construct in a controller? Or even more relevant, how to change the precise Text_* classes being used without editing the code every time it needs modification?
Well…
[geshi lang=php]return array(
‘Formatter’ => array(
‘class’ => ‘Text_Bold’,
‘arguments’ => array(
‘__construct’ => ‘Emphasis’
)
),
‘Emphasis’ => array(
‘class’ => ‘Text_Emphasis’
}
);[/geshi]
And back to our controller…
[geshi lang=php]class FormatController extends Zend_Controller_Action
{
public function boldemphasisAction()
{
$config = new Zend_Config( require ‘/path/to/config/di/format/boldemphasis.php’ );
$di = new Zend_Di_Container($config);
$formatter = $di->loadClass(‘Formatter’)->newInstance();
$this->view->text = $formatter->toString();
}
}[/geshi]
Now if we ever intend boldemphasisAction() to perform a dozen other formatting steps, we can just stick them into the DI config file without editing the actual code in the controller. For a simple example, the usefulness is limited - but in a more complex web of objects you can see the benefit more clearly. Especially if using a similar web of objects numerous times with few differences.
Besides this sudden burst of flexibility, how does it help in testing? And indeed how do we ensure not to overuse it for testing (the trick question plaguing Spring… ). Obviously I’ve introduced more dependencies into the Controller (i.e. Zend_Config is directly instantiated). Secondly, how can we influence the Zend_Di_Container to substitute mock objects for the real ones it would normally intantiate?
The first is an easy:
[geshi lang=php]Zend_Di::loadClass(‘Zend_Config’,
require ‘/path/to/config/di/format/boldemphasis.php’);[/geshi]
Which is really the only right answer for a simple use case. You see, Zend_Di doesn’t get mocked. It’s a Dependency Injection container, which we use as part of the overall testing platform. The only thing we have to change, is the configuration it uses…so that it instantiates Mock Objects or Stubs we create in our test cases (or spec) instead. Perhaps using (in a test):
[geshi lang=php]Zend_Di::replaceClass(‘Zend_Config’, $config);[/geshi]
As for overuse - DI works wonders in small measures. There will always be a tipping point where the benefits of a DI container are outweighed by convenience, a point usually close to where mocking an object provides little real benefit. Personally, I think DI containers work wonders for Controller development in a TDD or BDD environment, even better with a good mocking framework available!
The PHPSpec Zend Framework App Testing Manifesto: ZF Integration
Jan 17th
In the Preamble to this Manifesto, I set out the basic proposal for direct integration of support for applying Behaviour-Driven Development (BDD) to the Zend Framework. The ideal was to move away from line-by-line setup and manipulation of Zend instances towards a more simplified model which is standardised for any PHPSpec specs.
This morning I merged the experimental branch for the implementation of the Zend Framework specific PHPSpec_Context into the main trunk for release in PHPSpec 0.3.0. Documentation of the Zend Context will be completed within the next day or two and added to the growing PHPSpec Manual at http://dev.phpspec.org/manual now available.
The main customisation required to get the Zend Context operational is to set up elements of the FrontController, I’ve selected two primary methods. The first is to use several static methods on PHPSpec_Context_Zend (addControllerDirectory() and addModuleDirectory()) and a more flexible addFrontControllerSetupCallback() which will accept a custom function or static method reference to execute. The former is handy since you can easily organise your bootstrap file into a static class and segregate FrontController, Dispatcher, Action Helper, etc. elements you are using to customise how the Zend Framework operates. Both can be called from any file included into a Context file (e.g. SpecHelper.php).
Here’s a really simple example of a Zend Framework spec. I’ll post a more in-depth tutorial next week once the Manual is finalised, and some final cleanups applied (e.g. allowing for Router specification). The only priority for basic operations left is an implementation of a Zend_Factory class to allow you to replace objects directly instantiated within controllers (to maintain the golden standard of specifying/testing controllers isolated from it’s dependencies). We’ll really start kicking some ZF ass when PHPMock has an initial devel release!
[geshi lang=php]PHPSpec_Context_Zend::addControllerDirectory(‘../src/application/controllers’);
class DescribeTwitterController extends PHPSpec_Context_Zend
{
public function itShouldDisplayHomeTweetsFromDefaultRetrieveAction()
{
$this->get(‘retrieve’);
$this->response()->should->haveText(‘Tweets From Twitterers Followed:’);
}
public function itShouldPostTweetOnSendActionUsingPostData()
{
$this->post(‘send’, array(‘message’=>’@padraicb: All your specs are belong to us’));
$this->response()->should->haveText(‘You submitted a new Tweet!’);
}
public function itShouldRetrieveMessagesForUserIdInParamsFromRetrieveAction()
{
$this->get(‘twitter/retrieve/user/padraicb’);
$this->response()->should->beSuccess();
}
}[/geshi]
It’s obvious this is bare minimum. There is still a lot of ground to cover incl. integrating PHPMock, so you can literally mock/stub the Twitter service package being used in the controller action itself (Note to Matthew - implement Zend_Service_Twitter already…;)) and set expectations of how the Controller should use that Twitter service. Adding some basic HTML/XML safe Matchers would also add some fairly substantial help for specifying what the response output should be, or what template any controller action should render.
But the basics, and in short the most time consuming, bit of integration to get something working with the Zend Framework at a level not requiring any devious workarounds is now complete. The weekend should see some really useful additions to this base. Your comments are most welcome as usual whether here, or on the mailing list.