Inversion of Control – The Hollywood Principle

Share this article

There’s a consensus among programmers (myself included, so here’s my own public mea culpa
) that Inversion of Control (IoC) is nothing more than a synonym for plain old Dependency Injection (DI). There’s a pretty intuitive reason that sustains this mindset: if the motivation behind DI is to promote the design of classes whose external collaborators are supplied by its surrounding context rather than inversely looking up them, the process can effectively been seen as a form of IoC. But while the DI = IoC equation can be considered generally valid, the concept of inversion of control itself is actually much broader. In fact, it could be said that DI is a specific use case which exploits the benefits of IoC, but it’s far from being the only one. This leads us back to the start; if DI is just a pattern that relies on the strengths of IoC, what’s IoC really then? Traditionally, application components have been designed to operate on and control the execution environment, an approach that delivers well to some extent. For instance, a logging module could be implemented to log data to a file, and how and when to log the data would be a entirely under control of the module. The log file (a part of the environment in this case) would be just an external, passive element with no influence on the way the module works. But let’s say we need to extend the module’s functionality and give it the ability for additionally logging data to a database, or eventually even via email. Upgrading the module to expose the extra functionality will make it grow in complexity, becoming more bloated as the logic required to attend to these additional duties is packaged behind the same API. The approach works, but won’t scale at all. This tangled situation can be sorted out in a fairly simple manner. Instead of making the module completely responsible for logging data to multiple endpoints, we can transfer the responsibility straight to the external environment. The module’s implementation would remain ridiculously simple, limited to acting as a simple event dispatcher. On the flip side, the environment would be responsible for implementing all of the logic required to log data entirely independent from the module in question. Not surprisingly, the process of inverting these responsibilities between components and the environment is formally known as Inversion of Control (or in a more relaxed jargon, The Hollywood Principle), and its implementation can be a real boost when it comes to developing extensible, highly-decoupled program modules. Of course, IoC is a language-agnostic paradigm, and as such it’s possible to consume it in the PHP world without much fuss.

Achieving Inversion of Control – Observing Domain Objects

IoC has indeed an ubiquitous presence, so it’s pretty easy to find implementations of it production. The first use case that comes to mind is Dependency Injection, but there are many other cases equally demonstrative, especially when stepping on the terrain of Event-Driven Design. If you’re wondering in what parallel universe IoC gets along with event handling mechanisms, consider a classic in the GoF repertoire: the Observer pattern. Used nearly everywhere, even client-side via JavaScript, observers are a shining example of the IoC concept in action; there’s a highly-decoupled subject focused on doing just a few narrow tasks without polluting the surrounding context while one or more external observers are responsible for implementing the logic required for handling the events triggered by the subject. How to handle the events, and even processing new ones, is entirely a responsibility of the observers rather than the subject. An example might be a nice way to make my previous babbling a little bit clearer. So, let’s say we’ve implemented a primitive Domain Model which defines a one-to-many relationship between blog posts and comments. In this case we’ll be deliberately ambitious and give the model the ability for firing off an email to notify the system administrator when a new comment is added to a post. Honestly, implementing such a feature without appealing to IoC would literally be a tangled mess, as we’d be asking the domain objects to doing something that’s way outside their scope. Instead, we could take an IoC-based approach and define the domain classes as follows:
<?php
namespace Model;

interface PostInterface
{
    public function setTitle($title);
    public function getTitle();
    
    public function setContent($content);
    public function getContent();
    
    public function setComment(CommentInterface $comment);
    public function getComments();
}
<?php
namespace Model;

class Post implements PostInterface, SplSubject
{
    private $title;
    private $content;
    private $comments  = [];
    private $observers = [];

    public function __construct($title, $content) {
        $this->setTitle($title);
        $this->setContent($content);
    }
    
    public function setTitle($title) {
        if (!is_string($title) 
            || strlen($title) < 2
            || strlen($title) > 100) {
            throw new InvalidArgumentException(
                "The post title is invalid.");
        }
        $this->title = $title;
        return $this;
    }

    public function getTitle() {
        return $this->title;
    }
    
    public function setContent($content) {
        if (!is_string($content) || strlen($content) < 10) {
            throw new InvalidArgumentException(
                "The post content is invalid.");
        }
        $this->content = $content;
        return $this;
    }
    
    public function getContent() {
        return $this->content;
    }

    public function setComment(CommentInterface $comment) {
        $this->comments[] = $comment;
        $this->notify();
    }
    
    public function getComments() {
        return $this->comments;
    }

    public function attach(SplObserver $observer) { 
        $id = spl_object_hash($observer);
        if (!isset($this->observers[$id])) {
            $this->observers[$id] = $observer;
        }
        return $this;
    }
    
    public function detach(SplObserver $observer) {
        $id = spl_object_hash($observer);
        if (!isset($this->observers[$id])) {
            throw new RuntimeException(
                "Unable to detach the requested observer.");
        }
        unset($this->observers[$id]); 
        return $this;
    }
    
    public function notify() {
        foreach ($this->observers as $observer) {    
            $observer->update($this);
        }
    }
}
<?php
namespace Model;

interface CommentInterface
{
    public function setContent($content);
    public function getContent();
    
    public function setAuthor($author);
    public function getAuthor();
}
<?php
namespace Model;

class Comment implements CommentInterface
{
    private $content;
    private $author;
    
    public function __construct($content, $author) {
       $this->setContent($content);
       $this->setAuthor($author);
    }
    
    public function setContent($content) {
        if (!is_string($content) || strlen($content) < 10) {
            throw new InvalidArgumentException(
                "The comment is invalid.");
        }
        $this->content = $content;
        return $this;
    }
    
    public function getContent() {
        return $this->content;
    }
    
    public function setAuthor($author) {
        if (!is_string($author) 
            || strlen($author) < 2
            || strlen($author) > 50) {
            throw new InvalidArgumentException(
                "The author is invalid.");
        }
        $this->author = $author;
        return $this;
    }
    
    public function getAuthor() {
        return $this->author;
    }
}
The interaction between the Post and Comment classes is trivial, but the Post class deserves an in-depth look. Effectively, it has been designed as a “classic” subject, hence providing the typical API which permits to attach/detach and notify observers at will. The most interesting facet of this process is the implementation of setComment() where the actual inversion of control takes place. The method just fires a “pull” update to all the registered observers whenever a comment is added. This means that all the logic required for sending out the email notification is delegated to one or more external observers, thus offloading the dirty work from Post, keeping it focused on just its own business logic. With this simple but effective schema of inversion of control in place, the only structure that needs to be added to the picture is at least one observer which should be responsible for dispatching the aforementioned email. To keep things easy to follow, I’m going to implement the observer as a thin entity living and breathing in the service layer.

Delegating Control to the External Environment – Implementing a Comment Notification Service

Building an observer service capable of triggering an email notification when a new comment is added to a blog post is a simple process, reduced to defining a class that implements the pertaining update() method. If you’re curious and want to see how the service in question looks, here it is:
<?php
namespace Service;

class CommentService implements SplObserver
{
    public function update(SplSubject $post) {
        $subject = "New comment posted!";
        $message = "A comment has been made on a post entitled " .
            $post->getTitle();
        $headers = "From: "Notification System" <notify@example.com>rnMIME-Version: 1.0rn";
        if (!@mail("admin@example.com", $subject, $message, $headers)) {
            throw new RuntimeException("Unable to send the update.");
        }
    }
}
The CommentService class does exactly what it’s supposed to; it invokes its update() method to dispatch an email to the sysadmin each time a user drops a comment related to a given post. It’d be a lot easier to see the benefits brought by inversion of control in this situation if I showed you a script that puts all the sample classes to work, so below is some code:
<?php
use LibraryLoaderAutoloader,
    ModelPost,
    ModelComment,
    ServiceCommentService;
         
require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader();
$autoloader->register();

$post = new Post(
    "A sample post",
    "This is the content of the sample post"
);

$post->attach(new CommentService());

$comment = new Comment(
    "A sample comment",
    "Just commenting on the previous post"
);

$post->setComment($comment);
Quite possibly, and this is just my personal preference, I don’t feel quite right injecting a service into the internals of a domain object. The powers that be have always proclaimed the domain layer must be agnostic about the service layer (unless you appeal to separated interfaces), and this one should lay down on an upper level interoperating with multiple clients. But in this case the approach isn’t really that sinful considering the Post
class is just a dummy container for the registered observers which are only consumed when triggering event updates. Moreover, taking into account how neatly the responsibilities between the service in question and the Post class have been inverted, my complaint should be considered pretty much a picky whim.

Closing Thoughts

Quite often considered an obscure, tangled concept, especially in PHP where many developers tend to intuitively associate the concept only with plain old Dependency Injection, Inversion of Control is a simple yet killer programming methodology which, when properly implemented, is a fantastic way for creating decoupled, orthogonal systems whose components can be easily tested in isolation. If you’re using Dependency Injection within your applications (you are, right?) then you should feel your coder’s instincts pretty well satisfied as you’re already exploiting the benefits that Inversion of Control provides. As I attempted to demonstrate before, however, there’s a wide array of situations where the approach fits well other than just managing class dependencies the right way. Event-Driven Design is certainly a good example. Image via Fotolia

Frequently Asked Questions about Inversion of Control and the Hollywood Principle

What is the main difference between Inversion of Control (IoC) and Dependency Injection (DI)?

IoC and DI are two principles closely related, but they are not the same. IoC is a design principle which suggests that the control flow of a program should be inverted, meaning that instead of the program controlling the flow, the external entities like frameworks or services should. On the other hand, DI is a form of IoC where the dependencies of an object are set by an external entity, usually a framework. In simple terms, IoC is the concept, and DI is the way to implement that concept.

How does the Hollywood Principle relate to IoC?

The Hollywood Principle is another name for IoC. It’s called the Hollywood Principle because of the phrase “Don’t call us, we’ll call you.” This means that instead of components or objects in a system calling each other for tasks, they should be designed in a way that they are called upon when needed. This principle is at the heart of IoC.

Why is IoC important in PHP?

IoC is important in PHP because it helps to create flexible, reusable and testable code. By using IoC, you can decouple the objects and their dependencies, making the code easier to manage and test. It also allows for better separation of concerns, as each object focuses on its own functionality, and not on how to get the dependencies it needs.

How does IoC work in Laravel?

Laravel uses a service container, which is a powerful tool for managing class dependencies and performing dependency injection. The service container allows you to bind classes and interfaces to it, and then resolve them later in your application. This is a form of IoC, as the control of creating and managing these dependencies is inverted to the Laravel framework.

What are the benefits of using the SOLID principles in PHP?

The SOLID principles are a set of design principles that help to create more understandable, flexible, and maintainable code. They help to reduce the complexity of the code, make it easier to refactor, and improve the code’s testability. By following these principles, you can create code that is easier to understand and maintain, and that can be easily extended or modified in the future.

How does the Hollywood Principle help in designing software?

The Hollywood Principle helps in designing software by promoting loose coupling and high cohesion. By following this principle, components or objects in a system don’t need to know about each other’s implementation details. They just need to know what methods to call. This makes the system more modular, easier to understand, and easier to change or extend.

Can IoC be used in other programming languages besides PHP?

Yes, IoC is a design principle that can be used in any object-oriented programming language. It’s not specific to PHP. Many modern frameworks in languages like Java, C#, Python, and Ruby also use IoC to manage dependencies and control the flow of the application.

What are some common pitfalls when implementing IoC?

Some common pitfalls when implementing IoC include overusing it, which can lead to code that is hard to understand and maintain, and not understanding the lifecycle of the dependencies, which can lead to unexpected behavior. It’s important to understand when and where to use IoC, and to manage the dependencies properly.

How does IoC relate to the concept of “separation of concerns”?

IoC promotes the concept of “separation of concerns” by decoupling the objects and their dependencies. Each object should only be concerned with its own functionality, and not with how to get the dependencies it needs. This separation makes the code easier to understand, test, and maintain.

How can I start implementing IoC in my PHP projects?

You can start implementing IoC in your PHP projects by using a framework that supports it, like Laravel or Symfony. These frameworks provide tools and structures that make it easier to implement IoC. You can also implement it manually by designing your classes and objects in a way that their dependencies are provided to them, instead of them creating or finding the dependencies themselves.

Alejandro GervasioAlejandro Gervasio
View Author

Alejandro Gervasio is a senior System Analyst from Argentina who has been involved in software development since the mid-80's. He has more than 12 years of experience in PHP development, 10 years in Java Programming, Object-Oriented Design, and most of the client-side technologies available out there.

Expert
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week
Loading form