Subscribe to PHP Freaks RSS

Reducing call sites with dependency injection and context passing

syndicated from on January 29, 2018

This article continues where "Unary call sites and intention-revealing interfaces" ended.

While reading David West's excellent book "Object Thinking", I stumbled across an interesting quote from David Parnas on the programming method that most of us use by default:

The easiest way to describe the programming method used in most projects today was given to me by a teacher who was explaining how he teaches programming. "Think like a computer," he said. He instructed his students to begin by thinking about what the computer had to do first and to write that down. They would then think about what the computer had to do next and continue in that way until they had described the last thing the computer would do... [...]

It may seem like a very logical thing to do. And it's what I've seen myself and many other programmers do: "How do we implement this feature?" "Well, first we need this piece of data. Then we need to have a little algorithm manipulating it. Then we need to save it somewhere. And then, and then, etc." The code that is the result of this approach is sequential, imperative in nature.

Parnas continues:

Any attempt to design these programs by thinking things through in the order that the computer will execute them leads to confusion and results in systems that nobody can understand completely.

Parnas, David Lorge. "Software Aspects of Strategic Defense Systems." American Scientist 73 (1985). pp. 432–440.

The book describes ways to counter this method using a different style of thinking called "object thinking". When I know more about it, I'll share it with you. For now, I just wanted to note that this "computer thinking" we do, leads to many issues that make the overall design of our application worse. I described one situation in a previous article about read models, where I realized that we often try to answer many different questions by querying one and the same model (which is also the write model). By splitting write from read, we end up with a more flexible design.

The same goes for introducing interfaces, to achieve dependency inversion. We do a little extra work, but we thereby allow ourselves to get rid of a bit of "computer think".

Singletons, service locators, registries...

In the past, we've invented some useful utilities to help us with our computer thinking. "We need to send an email for this feature." "Hmm, how do we get the mailer?" "I know! We get it from the service locator." So there we go:

$mailer = $this->container->get('mailer');

"What else do we need?" "The current Request object, so we can find out if the client has added a particular header." "Okay, let's add that the request to the service locator as well." "No, wait, the request is part of the context." "No problem!"

$request = sfContext::getInstance()->getRequest();

"Wait, the context hasn't been created yet? Let's tell the computer to do it for us."

$request = sfContext::createInstance()->getRequest();

"Wait, the context has sometimes been created and now creating another instance leads to subtle bugs and decreased performance?"

if (sfContext::hasInstance()) {
    $request = sfContext::getInstance()->getRequest();
} else {
    $request = sfContext::createInstance()->getRequest();

Okay, this is where we should stop the silliness. This story is over the top. But we do this kind of thing every day. We write code that will break. Not now, but once it's used in slightly different ways (in a not so far future).

We're lucky, since we can prevent this situation from happening. All the answers are there. They are old, and famous answers.

Dependency injection

One of the answers is: dependency injection. To prevent us from worrying about "how to get something", we have to assume we'll get it. In other words, when constructing an object, dependencies (like the mailer from the previous example), should be injected. No need to fetch it, when the code gets executed, it will be there:

final class SomeService
    public function __construct(Mailer $mailer)
        $this->mailer = $mailer;

public function doSomething() { $request = ... } }

Of course we

Truncated by Planet PHP, read more at the original (another 3564 bytes)