A pretty popular myth about avoiding global state (singletons, multitons, registries, global variable, static variables/methods) is that it results in creating widely used objects more often than needed. The most common example in this case is a database connection. We try to avoid global state to let objects express their dependencies clearly: the object constructor should be as readable as “give me this, give me that and I will work”. Let’s talk about a situation where we instanciate a relatively complex set of domain objects including a service layer. For the example, we assume that we read an existing customer. We use the CustomerServiceLayer to retrieve the Customer, which uses the CustomerRepository to create the Customer object which needs a DatabaseConnection connection and passes a strategy (NameFormattingStrategy) to format the name of the customer to the Customer object and a CustomerDataMapper to allow the Customer object to save itself. Here are the constructor signatures of the involved components:
class Customer ...
public function __construct(
NameFormattingStrategy $nameFormattingStrategy
)
class CustomerRepository ...
public function __construct(
DatabaseConnection $connection,
CustomerDataMapper $dataMapper,
NameFormattingStrategy $nameFormattingStrategy
)
class CustomerServiceLayer ...
public function __construct(CustomerRepository $repository)
class CustomerDataMapper ...
public function __construct(DatabaseConnection $connection)
class DatabaseConnection ...
public function __construct(
string $host,
int $port,
string $username,
string $password,
string $database
)
So, all we do in the page controller, may it be a (page) controller or a plain PHP file is instanciating the service layer and its dependencies:
$databaseConnection = new DatabaseConnection(...);
$customerDataMapper = new CustomerDataMapper($databaseConnection);
$nameFormattingStrategy = new NameFormattingStrategy();
$customerRepository = new CustomerRepository(
$databaseConnection,
$customerDataMapper,
$nameFormattingStrategy
);
$serviceLayer = new CustomerServiceLayer($customerRepository);
$customer = $serviceLayer->getCustomerById((int)$_GET['customer_id]);
... pass it to the view, do nifty things ...
If other components, like the OrderRepository needs a database connection, just pass it to it. No need to let the order repository know how to get it. It is just there. In your unit test you can passed a mocked repository, a mocked database connection and a mocked data mapper depending on what particular part of the chain you are going to test. By the way: the heavy construction work could be easily passed to a number of factories just responsible for creating your objects. These factories are easily testable too as the only assertion made would be is the returned object correctly configured.
Filed under OOD, OOP, PHP, Unit testing & eleven comments & no trackbacks
Trackback specific URI for this entry
Avi opines:
published on February 15th 2009, 05:04:23 amInstead of a factory, one could also use a DI container…although I’m not sure at this point if there are any DI container’s for PHP which are mature enough to be used production at this point.
Reply
Lars Strojny means:
published on February 15th 2009, 05:17:51 amThat’s the point. "Flow3":http://flow3.typo3.org/ implements a DI container and there are various proposals for the Zend Framework, let’s see what’s happening in the next months.
Reply
Stephan Hochdoerfer reckons:
published on February 15th 2009, 08:53:59 amThank you for this article. Great to see the mindshift of IoC comming (slowly) to the PHP world. There exist quite a lot of other frameworks that provide an DI container like Crafty, Sphicy, Solar and Lion Framework. I have not tested those yet, since I use my own implementation that works quite well :)
As far as I can say from my 3 year experience with IoC in PHP it really helps you focus on high code quality. Testing components works like a charm. Even on the application level it sometimes can be useful to "just configure" a new object and use it, instead of extending a parent class and setting the members to the expected values in the subclass.
Reply
Les replys:
published on February 15th 2009, 08:55:17 pmMindshift slowing coming to PHP?
Sorry, but the notion of DI has been with PHP for the last 4 years or so but the only problem (as stated) is that there has not been a stable implementation.
With PHP though, we as PHP developers tend a lot to just develop (roll your own) our own solutions, so it’s a cultural issue as much as a communitity issue.
As for Zend Framework, I’m not holding my breath, as what was once promised to be a bloat free, light weight framework, it’s far from that today.
It [Zend Framework] may be modular but it carries too much weight with it, so any realistic DI container for PHP based upon ZF will be limited to just that communitity, so again we have no real PHP solution for everyday use.
Reply
Lars Strojny replys:
published on February 15th 2009, 10:16:58 pmI’m more and more thinking that DI could be done in an PECL extension hooking the Zend Engine. The reason why is, that it would not require developers to learn a new way how to create objects as this is done internally. Anyway, this idea needs further thinking and somebody implementing a prototype.
Reply
Sebastian Deutsch means:
published on February 15th 2009, 06:04:10 amIn the last paragraph you’re talking about an OrderRepository – do you really mean different domain components or do you mean the CustomerRepository which gets explicitly a DatabaseConnection.
Reply
Lars Strojny means:
published on February 15th 2009, 12:11:29 pmIt was meant to illustrate how to avoid duplicating the database connection object but just passing it to the other repository for the other major type of domain objects.
Reply
Lawrence Krubner responses:
published on February 15th 2009, 08:47:57 amFor managing dependency injection (or a service locator) Pico now has a PHP version. There is a good discussion of it here:
Given the brilliance of the folks behind Pico, its PHP version needs to be taken seriously.
Reply
Lars Strojny returns:
published on February 15th 2009, 12:50:37 pmI’ve checked out pico two years ago and pretty interesting to see, that it now comes as a PHP 5 version. Worth to recheck.
Reply
Lawrence Krubner returns:
published on February 15th 2009, 08:49:55 amDamn, the anti-spam measures would not allow me to include the URL. I will try again, with an edited URL:
www.sitepoint.com/forums/showthread.php?t=232030
Reply
Lars Strojny opines:
published on February 15th 2009, 12:49:09 pmI’m sorry for the inconvenience.
Reply