Understanding PHP Dependency Injection Example Analysis

高洛峰
Release: 2023-03-03 17:50:01
Original
990 people have browsed it

The examples in this article describe the understanding of PHP dependency injection. I share it with you for your reference. The details are as follows:

Looking at Laravel’s IoC container document only introduces examples, but does not explain the principle. I didn’t pay attention to this concept when using the MVC framework before. I accidentally saw this detailed introduction in the phalcon document. , feeling suddenly enlightened, I copied and pasted it, mainly because I haven’t written anything for a long time, and now I am really lazy!

First, we assume that we want to develop a component named SomeComponent. A database connection will now be injected into this component.

In this example, the database connection is created in the component. This method is impractical. If we do this, we will not be able to change some parameters such as database connection parameters and database type.

class SomeComponent {
  /**
   * The instantiation of the connection is hardcoded inside
   * the component so is difficult to replace it externally
   * or change its behavior
   */
  public function someDbTask()
  {
    $connection = new Connection(array(
      "host" => "localhost",
      "username" => "root",
      "password" => "secret",
      "dbname" => "invo"
    ));
    // ...
  }
}
$some = new SomeComponent();
$some->someDbTask();
Copy after login

In order to solve the above problem, we need to create an external connection before use and inject it into the container. For now this looks like a good solution:

class SomeComponent {
  protected $_connection;
  /**
   * Sets the connection externally
   */
  public function setConnection($connection)
  {
    $this->_connection = $connection;
  }
  public function someDbTask()
  {
    $connection = $this->_connection;
    // ...
  }
}
$some = new SomeComponent();
//Create the connection
$connection = new Connection(array(
  "host" => "localhost",
  "username" => "root",
  "password" => "secret",
  "dbname" => "invo"
));
//Inject the connection in the component
$some->setConnection($connection);
$some->someDbTask();
Copy after login

Now let's consider a problem where we are using this component in different places in our application where the database connection will be created multiple times. Use a method similar to the global registry to obtain a database connection instance from here instead of creating it once.

class Registry
{
  /**
   * Returns the connection
   */
  public static function getConnection()
  {
    return new Connection(array(
      "host" => "localhost",
      "username" => "root",
      "password" => "secret",
      "dbname" => "invo"
    ));
  }
}
class SomeComponent
{
  protected $_connection;
  /**
   * Sets the connection externally
   */
  public function setConnection($connection){
    $this->_connection = $connection;
  }
  public function someDbTask()
  {
    $connection = $this->_connection;
    // ...
  }
}
$some = new SomeComponent();
//Pass the connection defined in the registry
$some->setConnection(Registry::getConnection());
$some->someDbTask();
Copy after login

Now, let's imagine that we have to implement two methods in the component, first of which we need to create a new database connection and the second one always gets a shared connection:

class Registry
{
  protected static $_connection;
  /**
   * Creates a connection
   */
  protected static function _createConnection()
  {
    return new Connection(array(
      "host" => "localhost",
      "username" => "root",
      "password" => "secret",
      "dbname" => "invo"
    ));
  }
  /**
   * Creates a connection only once and returns it
   */
  public static function getSharedConnection()
  {
    if (self::$_connection===null){
      $connection = self::_createConnection();
      self::$_connection = $connection;
    }
    return self::$_connection;
  }
  /**
   * Always returns a new connection
   */
  public static function getNewConnection()
  {
    return self::_createConnection();
  }
}
class SomeComponent
{
  protected $_connection;
  /**
   * Sets the connection externally
   */
  public function setConnection($connection){
    $this->_connection = $connection;
  }
  /**
   * This method always needs the shared connection
   */
  public function someDbTask()
  {
    $connection = $this->_connection;
    // ...
  }
  /**
   * This method always needs a new connection
   */
  public function someOtherDbTask($connection)
  {
  }
}
$some = new SomeComponent();
//This injects the shared connection
$some->setConnection(Registry::getSharedConnection());
$some->someDbTask();
//Here, we always pass a new connection as parameter
$some->someOtherDbTask(Registry::getConnection());
Copy after login

So far, we have seen Here's how to use dependency injection to solve our problem. Instead of creating a dependency within the code, we pass it as a parameter, which makes our program easier to maintain, reduces the coupling of the program code, and achieves a loose coupling. But in the long run, this form of dependency injection also has some disadvantages.

For example, if there are more dependencies in the component, we need to create multiple setter methods to pass, or create a constructor to pass. In addition, every time a component is used, a dependent component needs to be created, making the code less easy to maintain. The code we write may look like this:

//Create the dependencies or retrieve them from the registry
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();
//Pass them as constructor parameters
$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);
// ... or using setters
$some->setConnection($connection);
$some->setSession($session);
$some->setFileSystem($fileSystem);
$some->setFilter($filter);
$some->setSelector($selector);
Copy after login

I think, we have to create this object in many places in the application. If you don't need the dependent components, we have to go to the code injection part to remove the parameters in the constructor or the setter method. To solve this problem, we once again go back to using a global registry to create the component. However, before creating the object, it adds a new layer of abstraction:

class SomeComponent
{
  // ...
  /**
   * Define a factory method to create SomeComponent instances injecting its dependencies
   */
  public static function factory()
  {
    $connection = new Connection();
    $session = new Session();
    $fileSystem = new FileSystem();
    $filter = new Filter();
    $selector = new Selector();
    return new self($connection, $session, $fileSystem, $filter, $selector);
  }
}
Copy after login

At this moment, we seem to be back to the beginning of the problem, we are creating dependencies inside the component, and we are modifying and finding a way to solve the problem every time methods, but these are not good practices.

A practical and elegant way to solve these problems is to use container dependency injection. As we saw earlier, the container acts as a global registry. Using container dependency injection as a bridge to solve dependencies allows us to The code coupling is lower, which greatly reduces the complexity of the component:

class SomeComponent
{
  protected $_di;
  public function __construct($di)
  {
    $this->_di = $di;
  }
  public function someDbTask()
  {
    // Get the connection service
    // Always returns a new connection
    $connection = $this->_di->get('db');
  }
  public function someOtherDbTask()
  {
    // Get a shared connection service,
    // this will return the same connection everytime
    $connection = $this->_di->getShared('db');
    //This method also requires a input filtering service
    $filter = $this->_db->get('filter');
  }
}
$di = new Phalcon\DI();
//Register a "db" service in the container
$di->set('db', function(){
  return new Connection(array(
    "host" => "localhost",
    "username" => "root",
    "password" => "secret",
    "dbname" => "invo"
  ));
});
//Register a "filter" service in the container
$di->set('filter', function(){
  return new Filter();
});
//Register a "session" service in the container
$di->set('session', function(){
  return new Session();
});
//Pass the service container as unique parameter
$some = new SomeComponent($di);
$some->someTask();
Copy after login

Now, the component only needs it when it accesses a certain service. If it does not need it, it will not even be initialized to save resources. The component is highly decoupled. Their behavior, or any other aspect of them, does not affect the components themselves. Our implementation method

PhalconDI is a component that implements the dependency injection function of the service, and it is also a container itself.

Due to the high degree of decoupling of Phalcon, PhalconDI is an essential part of the framework used to integrate other components. Developers can also use this component to dependency inject and manage instances of different class files in the application.

Basically, this component implements the Inversion of Control pattern. Based on this, the object no longer implements injection by receiving parameters in the constructor or using setters, but directly requests dependency injection of the service. This greatly reduces the overall program complexity because there is only one way to obtain the required dependencies of a component.
Additionally, this pattern enhances the testability of your code, making it less error-prone.
Register services in containers

Either the framework itself or developers can register services. When a component A requests a call to component B (or an instance of its class), it can request a call to component B from the container instead of creating an instance of component B.

This way of working provides us with many advantages:

We can replace a component, either from themselves or created easily by a third party.
Before the component is released, we can fully control the initialization of the object and perform various settings on the object.

We can use a unified way to get a structured global instance from the component
Services can be injected into the container in the following ways:

//Create the Dependency Injector Container
$di = new Phalcon\DI();
//By its class name
$di->set("request", 'Phalcon\Http\Request');
//Using an anonymous function, the instance will lazy loaded
$di->set("request", function(){
  return new Phalcon\Http\Request();
});
//Registering directly an instance
$di->set("request", new Phalcon\Http\Request());
//Using an array definition
$di->set("request", array(
  "className" => 'Phalcon\Http\Request'
));
Copy after login

In the above example, when requesting access to a request data from the framework, it will First determine whether the service with this "reqeust" name exists in the container.

The container will return an instance of the requested data, and developers finally get the component they want.
Each method in the above example has advantages and disadvantages. Which one to use depends on the specific scenario during the development process.

用一个字符串来设定一个服务非常简单,但缺少灵活性。设置服务时,使用数组则提供了更多的灵活性,而且可以使用较复杂的代码。lambda函数是两者之间一个很好的平衡,但也可能导致更多的维护管理成本。

Phalcon\DI 提供服务的延迟加载。除非开发人员在注入服务的时候直接实例化一个对象,然后存存储到容器中。在容器中,通过数组,字符串等方式存储的服务都将被延迟加载,即只有在请求对象的时候才被初始化。

//Register a service "db" with a class name and its parameters
$di->set("db", array(
  "className" => "Phalcon\Db\Adapter\Pdo\Mysql",
  "parameters" => array(
     "parameter" => array(
        "host" => "localhost",
        "username" => "root",
        "password" => "secret",
        "dbname" => "blog"
     )
  )
));
//Using an anonymous function
$di->set("db", function(){
  return new Phalcon\Db\Adapter\Pdo\Mysql(array(
     "host" => "localhost",
     "username" => "root",
     "password" => "secret",
     "dbname" => "blog"
  ));
});
Copy after login

以上这两种服务的注册方式产生相同的结果。然后,通过数组定义的,在后面需要的时候,你可以修改服务参数:

$di->setParameter("db", 0, array(
  "host" => "localhost",
  "username" => "root",
  "password" => "secret"
));
Copy after login

从容器中获得服务的最简单方式就是使用”get”方法,它将从容器中返回一个新的实例:

$request = $di->get("request");
Copy after login

或者通过下面这种魔术方法的形式调用:

$request = $di->getRequest();
Copy after login

Phalcon\DI 同时允许服务重用,为了得到一个已经实例化过的服务,可以使用 getShared() 方法的形式来获得服务。
具体的 Phalcon\Http\Request 请求示例:

$request = $di->getShared("request");
Copy after login

参数还可以在请求的时候通过将一个数组参数传递给构造函数的方式:

$component = $di->get("MyComponent", array("some-parameter", "other"));
Copy after login

  希望本文所述对大家PHP程序设计有所帮助。

   更多对PHP依赖注入的理解实例分析相关文章请关注PHP中文网!

Related labels:
source:php.cn
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
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template