Dependency Injection with Pimple

Share this article

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

Frequently Asked Questions (FAQs) about Dependency Injection with Pimple

What is Pimple and why is it used in PHP?

Pimple is a simple PHP Dependency Injection Container that allows you to manage and centralize services in your application. It is used in PHP to make code more flexible, reusable, and testable. By using Pimple, you can instantiate objects in one place and then inject them into different parts of your application, reducing the need for global state and making your code easier to maintain and test.

How does Pimple work?

Pimple works by storing service definitions in a container. These definitions are callables (functions or methods) that return an instance of a service. When you access a service from the container, Pimple executes the service definition to create the service object. This allows you to manage your services in a centralized way and to share services across your application.

How do I install Pimple?

Pimple can be installed using Composer, a dependency management tool for PHP. You can install Composer globally on your system and then require Pimple in your project by running the command composer require pimple/pimple.

How do I define a service in Pimple?

In Pimple, you define a service by assigning a callable to a key in the container. The callable should return an instance of the service. For example, you can define a service for a mailer class like this:
$container['mailer'] = function ($c) {
return new Mailer($c['smtp']);
};
In this example, the mailer service is defined as a new instance of the Mailer class, with the smtp service injected as a dependency.

How do I access a service in Pimple?

You can access a service in Pimple by using array notation with the key of the service. For example, you can access the mailer service like this: $mailer = $container['mailer'];. When you access a service, Pimple executes the service definition and returns the service object.

How do I share a service in Pimple?

By default, Pimple returns a new instance of a service each time you access it. If you want to share a service and return the same instance each time, you can use the share() method. For example, you can share the mailer service like this: $container['mailer'] = $container->share(function ($c) { return new Mailer($c['smtp']); });.

Can I extend a service in Pimple?

Yes, you can extend a service in Pimple by using the extend() method. This allows you to modify a service after it has been defined. For example, you can extend the mailer service to add additional configuration like this:
$container['mailer'] = $container->extend('mailer', function ($mailer, $c) {
$mailer->setFrom($c['email.from']);
return $mailer;
});
In this example, the setFrom() method is called on the mailer service with the email.from service as a parameter.

How do I protect parameters in Pimple?

In Pimple, you can protect parameters (values that should not be treated as services) by using the protect() method. This allows you to store values in the container without them being treated as service definitions. For example, you can protect a configuration value like this: $container['config.value'] = $container->protect(function () { return 'value'; });.

How do I use Pimple in a project?

You can use Pimple in a project by creating a new instance of the Pimple\Container class and defining your services in it. You can then access your services from the container wherever you need them in your application. This allows you to manage your services in a centralized way and to inject them into different parts of your application.

What are the benefits of using Pimple?

Pimple provides several benefits for PHP development. It makes your code more flexible by allowing you to manage your services in a centralized way. It makes your code more reusable by allowing you to share services across your application. And it makes your code more testable by allowing you to inject mock services for testing. By using Pimple, you can improve the quality of your code and make it easier to maintain and test.

Rakhitha NimeshRakhitha Nimesh
View Author

Rakhitha Nimesh is a software engineer and writer from Sri Lanka. He likes to develop applications and write on latest technologies. He is available for freelance writing and WordPress development. You can read his latest book on Building Impressive Presentations with Impress.js. He is a regular contributor to 1stWebDesigner, Tuts+ network and SitePoint network. Make sure to follow him on Google+.

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