Home > Backend Development > PHP Tutorial > PHP Master | Dependency Injection with Pimple

PHP Master | Dependency Injection with Pimple

Christopher Nolan
Release: 2025-02-24 08:57:10
Original
508 people have browsed it

PHP Master | Dependency Injection with Pimple

Core points

  • Dependency injection is a key concept in application development, and by injecting dependencies into modules rather than hard-code them, you can write more efficient and more maintainable code.
  • Pimple is a simple dependency injection container that uses PHP's closure to define dependencies in a manageable way, helping to keep your code maintainable.
  • The two main techniques for injecting dependencies are constructor-based dependency injection and setter-based injection, each with its own advantages and disadvantages.
  • Pimple supports the DRY principle of software development by acting as a container that defines dependencies, avoiding duplication, making it easier to manage and centralize services in applications.
  • Pimple also provides advanced features such as the ability to return the same instance using shared objects, and the ability to dynamically modify existing closures without affecting the original implementation.

In application development, we try to create standalone modules to reuse code in future projects. However, creating completely independent modules that provide useful functionality is difficult; unless their dependencies are properly managed, they can lead to maintenance nightmare. This is where dependency injection comes in handy, as it allows us to inject the dependencies needed for the code to function properly without hard-code them into the module. Pimple is a simple dependency injection container that utilizes PHP's closure to define dependencies in a manageable way. In this article, we will explore the problem of hard-coded dependencies, how dependency injection solves these problems, and how to use Pimple to make code leveraging dependency injection easier to maintain.

Specific dependencies issue

When writing applications, we use many PHP classes. A class may need to call methods of one or more other classes to provide the expected functionality, so we say that the first class depends on other classes. For example:

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

Class A depends on class B. If class B is not available, the above code will not work. Furthermore, every time we hardcode the creation of an object in the class, we will have specific dependencies on the class. Specific dependencies are a barrier to writing testable code. A better approach is to provide an object of class B to class A. These objects can be provided by A's constructor or setter method. Before we further discuss, let's look at a more realistic scenario.

Sharing content on social network sites is very common these days, and most websites directly display their social profile feeds on their websites. Suppose we have a class called SocialFeeds that generates feeds from social sites such as Twitter, Facebook, Google, etc. Create separate classes to handle each of these services. Here we will look at the class TwitterService that interacts with Twitter. The SocialFeeds class uses TwitterService to request Twitter feeds. TwitterService interacts with the database to retrieve specific user tokens that access the API. The token is passed to the OAuth class, which retrieves the feed using the provided token and returns it to the SocialFeeds class.

<?php
class A
{
    public function a1() {
        $b = new B();
        $b->b1();
    }
}
Copy after login
Copy after login
Copy after login
Copy after login
<?php
class SocialFeeds
{
    public function getSocialFeeds() {
        $twService = new TwitterService();
        echo $twService->getTweets();
    }
}
Copy after login
Copy after login
Copy after login
<?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);
    }
}
Copy after login
<?php
class OAuth
{
    public function requestTwitterFeed($token) {
        // Retrieve and return twitter feed using the token         
    }
}
Copy after login

It is obvious that SocialFeeds relies on TwitterService. But TwitterService depends on DB and OAuth, so SocialFeeds depend on DB and OAuth indirectly. So what is the problem? SocialFeeds rely on the concrete implementation of three classes, so it is impossible to test SocialFeeds separately without the real implementation of other classes. Or, suppose we want to use a different database or a different OAuth provider. In this case, we have to replace the existing class with the new class throughout the code.

Fix specific dependencies

The solution to these dependencies is simple, i.e. dynamically provide objects when necessary without using concrete implementations. There are two types of techniques that can inject dependencies: constructor-based dependency injection and setter-based injection.

Constructor-based injection

Using constructor-based dependency injection, the dependency object is created externally and passed as a parameter to the constructor of the class. We can assign these objects to class variables and use them anywhere within the class. The constructor-based injection of the SocialFeeds class is as follows:

<?php
class DB
{
    public function getQueryResults($query) {
        // Get results from database and return token
    }
}
Copy after login

An instance of TwitterService is passed as an object to the constructor. SocialFeeds still relies on TwitterService, but now we are free to provide different versions of Twitter service providers, and even mock objects for testing purposes. Regarding TwitterService, DB and OAuth classes are also defined in a similar way.

<?php
class SocialFeeds
{
    public $twService;

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

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

Seter-based injection

Using setter-based injection, objects are provided by setter methods instead of constructors. The following is the setter-based dependency injection implementation of the SocialFeeds class:

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

The initialization code now includes DB and OAuth looks like this:

<?php
class SocialFeeds
{
    public $twService;

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

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

Constructor and Setter Injection

Select constructor-based injection or setter-based injection is up to you. Constructor-based injection is suitable when all dependencies are required to instantiate a class. Setter-based injection is suitable when dependencies are not required every time.

Pros

  • Constructor – Just look at the constructor of the class to identify all dependencies of the class
  • Setor - Adding new dependencies is as simple as adding a new setter method and does not break existing code

Disadvantages

  • Constructor – Adding new dependencies will add parameters to the constructor; existing code in the entire application needs to be updated to provide new dependencies
  • Setor - We have to search for necessary dependencies manually because they are not specified anywhere

After understanding dependency injection and various injection techniques, it's time to see Pimple and how it fits into it.

Pimple's role in DI

When we can already inject dependencies using the aforementioned techniques, you may be wondering why you need Pimple. To answer this question, we need to look at the DRY principle.

Don't repeat yourself (DRY) is a principle in software development that aims to reduce the duplication of various information, which is especially useful in multi-layer architectures. The statement of the DRY principle is that "every fragment of knowledge must have a single, clear, authoritative representation in a system" - Wikipedia

Consider the constructor-based injection example. Every time we want an object of the SocialFeed class, we have to repeat the entire setup process of instantiating and passing its dependencies. According to DRY, such code should be avoided to prevent maintenance problems. Pimple acts as a container that defines such dependencies to avoid duplication. Let's look at a simple example to see how Pimple works.

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

Create an instance of Pimple as a container for storing dependencies. It implements the SPL ArrayAccess interface, so using it is very similar to using arrays. First, we define a key that holds the name of any class we want. We then define a closure to return an instance of the specified class that acts as a service. Note that an instance of the container will be passed to $c, so we can refer to other defined keys as needed; each defined parameter or object can be used in the closure via the $c variable. Now, whenever we want an instance of a class, we can reference the key to retrieve the object. Let's convert the SocialFeeds example to Pimple. The examples on the official Pimple website show constructor-based injection, so here we will illustrate setter-based injection. Remember that in order to use Pimple, we don't need to modify any setter methods or code defined earlier - we just encapsulate the logic.

<?php
class SocialFeeds
{
    public function getSocialFeeds() {
        $twService = new TwitterService();
        echo $twService->getTweets();
    }
}
Copy after login
Copy after login
Copy after login

DB and OAuth classes are both independent modules, so we return their new instances directly within the closure. We then use setter-based injection to add dependencies to the TwitterService class. We have added the DB and OAuth classes to the container, so we can access them directly inside the function using $c['db'] and $c['oauth']. Now, the dependencies are encapsulated within the container as a service. Whenever we want to use a different DB class or a different OAuth service, we just replace the class in the container statement and everything will run perfectly. With Pimple, you just need to add new dependencies in one place.

Advanced Pimple Usage

In the above scenario, Pimple returns a new instance of each class from the closure every time it is requested. In some cases, we need to use the same object without initializing a new instance every time, such as connecting to a database is a perfect example. Pimple provides the ability to return the same instance using a shared object. In doing so, we need to specify the closure through the share() method, as shown below:

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

Also, so far we have defined all dependencies in a single location in the Pimple container. However, consider a case where we need a service that has its dependencies but is configured slightly differently from the original service. For example, suppose we need to access the ORM to implement some functionality of the TwitterService class. We cannot change the existing closure because it forces all existing functions to use ORM. Pimple provides the extend() method to dynamically modify existing closures without affecting the original implementation. Consider the following code:

<?php
class SocialFeeds
{
    public function getSocialFeeds() {
        $twService = new TwitterService();
        echo $twService->getTweets();
    }
}
Copy after login
Copy after login
Copy after login

Now, we are able to use different extension versions of tweet_service in special cases. The first parameter is the name of the service, and the second parameter is a function that can access object instances and containers. In fact, extend() is a powerful way to dynamically add dependencies to suit different situations, but make sure to limit the extended version of the service to a minimum, as it increases the amount of duplicate code.

Summary

Managing dependencies is one of the most important and difficult tasks in web application development. We can use dependency injection of constructors and setter methods to effectively manage them. However, dependency injection itself also has some troubles, and Pimple solves these problems by providing a lightweight container to create and store object dependencies in the DRY way. Feel free to share your experience managing dependencies in your project and your thoughts on Pimple as a dependency injection container in the comments below.

FAQs about Dependency Injection with Pimple (FAQ)

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, making the code more flexible, reusable and easier to test. By using Pimple, you can instantiate objects in one place and 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 containers. These definitions are callable (function or method) and they return instances of the service. When you access a service from a container, Pimple executes the service definition to create a service object. This allows you to manage services in a centralized manner and share services throughout the application.

How to install Pimple?

Pimple can be installed using Composer (PHP's dependency management tool). You can install Composer globally on your system and then introduce Pimple in your project by running the command composer require pimple/pimple.

How to define a service in Pimple?

In Pimple, you can define a service by assigning a callable object to a key in a container. The callable object should return an instance of the service. For example, you could define a service for the mail sender class like this:

$container['mailer'] = function ($c) { return new Mailer($c['smtp']); };

In this example, the mail sender service is defined as a new instance of the Mailer class, where the smtp service is injected as a dependency.

How to access services in Pimple?

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

How to share services in Pimple?

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

Can I extend the service in Pimple?

Yes, you can use the extend() method to extend services in Pimple. This allows you to modify the service after defining it. For example, you can extend the mail sender service like this to add additional configuration:

$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 mail sender service using the email.from service as a parameter.

How to protect parameters in Pimple?

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

How to use Pimple in a project?

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

What are the benefits of using Pimple?

Pimple provides many benefits for PHP development. It makes your code more flexible because it allows you to manage services in a centralized way. It makes your code easier to reuse because it allows you to share services throughout your application. It makes your code easier to test because it allows 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.

The above is the detailed content of PHP Master | Dependency Injection with Pimple. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template