I've been watching Google's clean code talks by Misko Hevery. These talks say: ask for dependencies in the constructor, so other programmers can see exactly what is needed up front, to instantiate an instance of a given object (law of demeter). This also makes testing easier as a programmer knows exactly what needs to be mocked.
Example time
If I have a class Customer
and I also have a CustomerDAO
class to abstract data access away. When I construct a customer object I might do the following:
database = new Database('dsn');
customerDao = new CustomerDAO(database);
customer = new Customer(customerDao);
This might happen in my controller. I can simplify this object construction via use of a dependency injection container. Below I've used a DI container to obtain an instance of my database class, as that is widely used throughout my application. This reduces the construction code to one place and can be mocked for testing.
Should I be adding my domain class dependencies (in this case DAO objects) to my DI container? If my application is large, will this make my DI container huge?
Using a DI container my code might look like this:
// container setup
container->dsn = '...';
container->dbh = function($c) {
return new Database($c->dsn);
};
container->customerDao = function($c) {
return new CustomerDAO($c->dbh);
};
// controller code
class ControllerCustomer extends ControllerBase {
public function index() {
container = this->getContainer();
customer = new Customer(container->customerDao);
view->customerName = customer->getName();
view->render();
}
}
Seems to be OK, if another programmer wants to test Customer
, they need only mock CustomerDAO
.
Taking this example a step further, if I have domain classes with dependencies on other domain classes, surely my DI container should not need to know how to construct every domain class? For example:
My customer might be a company/institution and therefore have many users.
class Customer {
protected _dao;
public function Customer(dao) {
_dao = dao;
}
public function listUsers() {
userIds = _dao->getAllUserIds();
users = array();
foreach (userIds as uid) {
user = new User(new UserDAO(new Database('dsn')); // problem
users[] user->load(uid);
}
return users;
}
}
Problems
- As I've not passed my DI container to my
Customer
object, it can't create user objects as shown above as it has no reference to the database DSN (and shouldn't really need to know how to make users) - Creating it's own dependencies makes this code untestable as they're concrete with no seams for mocking.
- If I do pass the container to my
Customer
class, does this make my interface forCustomer
lie? (See 9:15 in the linked Google video).
Should I be passing a user factory to Customer
to enable it to construct User
objects?
database = new Database('dsn');
userDao = new UserDAO(database);
userFactory = new UserFactory(userDao);
customer = new Customer(customerDao, userFactory);
Should the construction for UserFactory
be in my DI container?