Dependency Injection with Pimple

Rakhitha Nimesh
Share

In application development, we try to create independent modules so that we can reuse code in future projects. But, it’s difficult to create completely independent modules which provide useful functionality; their dependencies can cause maintenance nightmares unless they are managed properly. This is where Dependency Injection comes in handy, as it gives us the ability to inject the dependencies our code needs to function properly without hard coding them into the modules.

Pimple is a simple dependency injection container which takes advantage PHP’s closures to define dependencies in manageable way. In this article we’ll look at the problems with hard-coding dependencies, how dependency injection solves them, and how to use Pimple to keep code that takes advantage of dependency injection more maintainable.

The Issues with Concrete Dependencies

We use a number of PHP classes when writing applications. One class may need to call methods of one or more other classes to provide the intended functionality, and so we say the first class depends on the other classes. For example:

<?php
class A
{
    public function a1() {
        $b = new B();
        $b->b1();
    }
}

Class A depends on class B. If class B is not available then the above code will not work.

Moreover, every time we hard code the creation of an object inside a class, we are making a concrete dependency to that class. Concrete dependencies are a barrier to writing testable code. A better way is to provide an object of class B to class A. These objects can be provided through A’s constructor or a setter method.

Let’s take a look at a more realistic scenario before we go any further.

Sharing content on social networking websites is very common these days, and most sites display their social profile feeds right on their site itself. Suppose we have a class called SocialFeeds which generates feeds from social sites like Twitter, Facebook, Google+, etc. Separate classes are created to work with each of these services. Here we’ll look at the class that interfaces with Twitter, TwitterService.

The SocialFeeds class requests a Twitter feed using TwitterService. TwitterService interacts with the database to retrieve the specific user token to access the API. The token is passed to the OAuth class which retrieves the feeds using the provided token and returns it to the SocialFeeds class.

<?php
class SocialFeeds
{
    public function getSocialFeeds() {
        $twService = new TwitterService();
        echo $twService->getTweets();
    }
}
<?php
class TwitterService
{
    public function getTweets() {
        $db = new DB();
        $query = "Query to get user token from database";
        $token = $db->getQueryResults($query);

        $oauth = new OAuth();
        return $oauth->requestTwitterFeed($token);
    }
}
<?php
class OAuth
{
    public function requestTwitterFeed($token) {
        // Retrieve and return twitter feed using the token    		
    }
}
<?php
class DB
{
    public function getQueryResults($query) {
        // Get results from database and return token
    }
}

It’s clear that SocialFeeds depends on TwitterService. But TwitterService depends on DB and OAuth, and so SocialFeeds depends on both DB and OAuth indirectly.

So what are the issues? SocialFeeds depends on concrete implementations of three classes, so it is impossible to test SocialFeeds as a separate unit without having real implementations of the other classes. Or, let’s say we want to use a different database or a different OAuth provider. In this case we would have to replace the existing classes with new class in each occurrence throughout our code.

Fixing Concrete Dependencies

The solution to these dependency issues is as simple as providing the objects dynamically when necessary without using concrete implementations. There are two types of techniques to inject dependencies: constructor-based dependency injection and setter-based injection.

Constructor-Based Injection

With constructor-based dependency injection, dependant objects are created externally and passed to the class’s constructor as parameters. We can assign these objects to class variables and use anywhere inside the class. Constructor-based injection for the SocialFeeds class looks like this:

<?php
class SocialFeeds
{
    public $twService;

    public function __construct($twService) {
        $this->twService = $twService;
    }

    public function getSocialFeeds() {
        echo $this->twService->getTweets();
    }
}

An instance of TwitterService is passed as an object to the constructor. SocialFeeds still depends on TwitterService, but now we can have the freedom to provide different versions of the Twitter service provider or even a mock object for testing purposes. The DB and OAuth classes are defined similarly with regard to TwitterService.

<?php
$db = new DB();
$oauth = new OAuth();
$twService = new TwitterService($db, $oauth);
$socialFeeds = new SocialFeeds($twService);
$socialFeeds->getSocialFeeds();

Setter-Based Injection

With setter-based injection, objects are provided through setter methods instead of the constructor. Here’s a setter-based implementation of dependency injection for the SocialFeeds class:

<?php
class SocialFeeds
{
    public $twService;

    public function getSocialFeeds() {
        echo $this->twService->getTweets();
    }

    public function setTwitterService($twService) {
        $this->twService = $twService;
    }
}

The initialization code including DB and OAuth for now looks like this:

<?php
$db = new DB();
$oauth = new OAuth();
$twService = new TwitterService();
$twService->setDB($db);
$twService->setOAuth($oauth);

$socialFeeds = new SocialFeed();
$socialFeeds->setTwitterService($twService);
$socialFeeds->getSocialFeeds();

Constructor vs Setter Injection

It’s up to you to choose between constructor or setter-based injection. Constructor-based injections are suitable when all the dependencies are required in order to instantiate the class. Setter-based injections are suitable when dependencies are not required in each occasion.

Advantages

  • Constructor – all the dependencies of a class are identifiable simply by looking at the class’s constructor
  • Setter – adding a new dependency is as easy as adding a new setter method, which does not break existing code

Disadvantages

  • Constructor – adding a new dependency increases the constructor’s parameters; existing code needs to be updated throughout our application to provide the new dependency
  • Setter – we have to manually search for the necessary dependencies as they are not specified anywhere

With knowledge of dependency injection and various injection techniques, it’s time to look at Pimple and see how it fits in.

The Role of Pimple in DI

You might be wondering why Pimple is necessary when we can inject the dependencies already using the techniques previously mentioned. To answer this question, we need to look to the DRY principle.

Don’t Repeat Yourself (DRY) is a principle of software development aimed at reducing repetition of information of all kinds, especially useful in multi-tier architectures. The DRY principle is stated as “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system – Wikipedia

Consider the constructor-based injection example. Each time we want an object of the SocialFeed class, we have to repeat the whole setup process of instantiating and passing its dependencies. According to DRY, such code should be avoided to prevent maintenance headaches. Pimple acts as a container for defining such dependencies to avoid repetition.

Let’s look at a simple example to see how Pimple works.

<?php
$container = new Pimple();
$container['class_name'] = 'Test';
$container['object_name'] = function ($c) {
    return new $c['class_name']();
};

$testClass = $container['object_name'];

An instance of Pimple is created to act as the container for storing dependencies. It implements the SPL ArrayAccess interface so working with it is very similar to working with an array. First we’ve defined a key which holds the name of some arbitrary class we want. Then we’ve defined a closure to return the instance of the specified class which acts as a service. Note that $c will be passed an instance of the container, so we can reference other defined keys as we please; each defined parameter or object is available in the closure through the $c variable. Now whenever we want an instance of the class, we can reference the key to retrieve the object.

Let’s convert the SocialFeeds example to Pimple. The examples on the official Pimple site show constructor-based injection, so here we’ll illustrate setter-based injection. Keep in mind that any of the setter methods or code we defined in earlier do not need to be modified for us to use Pimple – we simply encapsulate the logic.

<?php
$container = new Pimple();
$container['oauth'] = function($c) {
	return new OAuth();
};
$container['db'] = function($c) {
	return new DB();
};
$container['tweet_service'] = function($c) {
	$twService = new TwitterService();
	$twService->setDB($c['db']);
	$twService->setOauth($c['oauth']);
	return $twService;
};
$container['social_feeds'] = function($c) {
	$socialFeeds = new SocialFeeds();
	$socialFeeds->setTwitterService($c['tweet_service']);
	return $socialFeeds;
};

$socialFeeds = $container['social_feeds'];
$socialFeeds->getSocialFeeds();

Both DB and OAuth classes are independent modules, so we directly return a new instance of them inside closures. Then we add dependencies to the TwitterService class using setter-based injections. We added DB and OAuth classes into the container already, so we can directly access them inside the function using $c['db'] and $c['oauth'].

Now the dependencies are encapsulated inside the container as services. Whenever we want to use different DB class or different OAuth service, we can just replace the class inside the container statement and everything will work perfectly. With Pimple you need to add new dependencies in just one place.

Advanced Pimple Usage

In the above scenario, Pimple will return new instances of each class from the closure whenever one is requested. There are certain scenarios where we need to use the same object without initializing new instances each time, for example connecting to database is a perfect example.

Pimple provides the ability to return the same instance using sharing objects, doing so requires us to specify the closure through the share() method as shown below:

<?php
$container['db'] = $container->share(function ($c) {
    return new DB();
});

Also, so far we’ve defined all our dependencies in a single location inside the Pimple container. But think of a situation where we need the services with its dependencies but configured in a slightly different way than the original. For example, let’s say we need to access an ORM for certain functionality of the TwitterService class. We can’t change the existing closure since it will force all of the existing functionality to use the ORM.

Pimple provides the method extend() to modify the existing closure dynamically without affecting the original implementation. Consider the following code:

<?php
$container['tweet_service'] = $container->extend('tweet_service', function($twSservice, $c) {
	$twService->setDB(new ORM());
	return $twService;
});

Now we’re able to use different extended versions of tweet_service in special scenarios. The first parameter is the name of the service, the second is a function that gets access to the object instance and the container.

Indeed, extend() is a powerful way of adding dependencies dynamically to suit different situations, but make sure to limit the extended versions of services to a minimum as it increases the amount of duplicate code.

Summary

Managing dependencies is one of the most essential and difficult tasks in web application development. We can use Dependency Injection using constructors and setter methods to manage them effectively. Dependency injection comes with its own hassles though, which Pimple solves by providing a lightweight container for creating and storing object dependencies in a DRY manner.

Feel free to share your experiences of managing dependencies in your projects and what you think about Pimple as a dependency injection container in the comments below.

Image via Fotolia