/usr/portage

Not having globals state doesn't mean you're doomed 11

Clear Dependencies
hbp_pix©

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 , , , & eleven comments & no trackbacks

Trackbacks

Trackback specific URI for this entry

No Trackbacks

Comments

  1. Avi opines:
    published on February 15th 2009, 05:04:23 am *

    Instead 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

  2. Lars Strojny responses:
    published on February 15th 2009, 05:17:51 am *

    That’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

  3. Stephan Hochdoerfer reckons:
    published on February 15th 2009, 08:53:59 am *

    Thank 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

  4. Les replys:
    published on February 15th 2009, 08:55:17 pm *

    Mindshift 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

  5. Lars Strojny supposes:
    published on February 15th 2009, 10:16:58 pm *

    I’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

  6. Sebastian Deutsch responses:
    published on February 15th 2009, 06:04:10 am *

    In 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

  7. Lars Strojny responses:
    published on February 15th 2009, 12:11:29 pm *

    It 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

  8. Lawrence Krubner responses:
    published on February 15th 2009, 08:47:57 am *

    For 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

  9. Lars Strojny answers:
    published on February 15th 2009, 12:50:37 pm *

    I’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

  10. Lawrence Krubner returns:
    published on February 15th 2009, 08:49:55 am *

    Damn, 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

  11. Lars Strojny reckons:
    published on February 15th 2009, 12:49:09 pm *

    I’m sorry for the inconvenience.

    Reply

Add a Comment & let me know what you think