In the previous article, we used a specific Web case to illustrate dependency injection. Today we will talk about dependency injection container (Container). First, let us start with an important statement:
大Most of the time, when you use dependency injection to decouple components, you don't need a container.
But if you want to manage many different objects and deal with complex dependencies between objects, containers become very useful.
Remember the example in the first article? Before creating a User object, you must first create a SessionStorage object. This is not a big deal, but I still want to say that this is because you clearly know the objects it depends on before you create the object you need. If there are many objects and the dependencies are complicated (assuming that the SessionStorage class depends on the cache class, the cache class depends on the file class and the inputFilter class, and the file class depends on the stdio class), then it's a problem. . . .
$storage = new SessionStorage('SESSION_ID'); $user = new User($storage);
In the following articles we will introduce the way to implement containers in Symfony 2. But for now, in order to explain containers simply and clearly, we will ignore Symfony for now. The following will use an example in Zend Framework to illustrate:
Zend Framework's Zend_Mail class simplifies email management. It uses PHP's mail() function to send emails by default, but its flexibility is not good. Thankfully, this behavior can be easily changed by providing a transport class.
The following code shows how to create the Zend_Mail class and use a Gmail account to send emails
$transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com', array( 'auth' => 'login', 'username' => 'foo', 'password' => 'bar', 'ssl' => 'ssl', 'port' => 465, )); $mailer = new Zend_Mail(); $mailer->setDefaultTransport($transport);
The Dependency Injection Container (Dependency Injection Container) is a large class that can instantiate and configure the various components it manages. and classes. In order to be able to do this, it must know the parameters and dependencies of the constructor methods of these classes.
The following is a hard-coded container, which still implements the work of obtaining the Zend_Mail object mentioned before:
class Container { public function getMailTransport() { return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array( 'auth' => 'login', 'username' => 'foo', 'password' => 'bar', 'ssl' => 'ssl', 'port' => 465, )); } public function getMailer() { $mailer = new Zend_Mail(); $mailer->setDefaultTransport($this->getMailTransport()); return $mailer; } } //容器的使用也很简单 $container = new Container(); $mailer = $container->getMailer();
When using the container, if you need to obtain a Zend_Mail object, you do not need to know details of creating it, because all the details of creating an object instance are built into the container. Zend_Mail's dependency on the Mail_Transport class can also be automatically injected into the Zend_Mail object through the container.
Obtaining dependent objects is mainly implemented by getMailTransport(). The power of the container is achieved by this simple get call.
But if you are smart, you must have discovered the problem. There are hard codes in the container (such as the account and password information for sending emails, etc.). So we need to go a step further and add parameters to the container to make the container more useful.
class Container { protected $parameters = array(); public function __construct(array $parameters = array()) { $this->parameters = $parameters; } public function getMailTransport() { return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array( 'auth' => 'login', 'username' => $this->parameters['mailer.username'], 'password' => $this->parameters['mailer.password'], 'ssl' => 'ssl', 'port' => 465, )); } public function getMailer() { $mailer = new Zend_Mail(); $mailer->setDefaultTransport($this->getMailTransport()); return $mailer; } }
Now you can easily switch the account and password for sending emails through the parameters of the container constructor
$container = new Container(array( 'mailer.username' => 'foo', 'mailer.password' => 'bar', )); $mailer = $container->getMailer();
If you feel that the Zend_Mail class cannot meet the current needs (for example, when testing, you need to do some logging), If you want to easily switch the mail sending class, you can also pass the class name through the parameters of the container constructor
class Container { // ... public function getMailer() { $class = $this->parameters['mailer.class']; $mailer = new $class(); $mailer->setDefaultTransport($this->getMailTransport()); return $mailer; } } $container = new Container(array( 'mailer.username' => 'foo', 'mailer.password' => 'bar', 'mailer.class' => 'MyTest_Mail', )); $mailer = $container->getMailer();
Finally, considering that the customer does not need to re-instantiate it every time when obtaining the mailer object (occupying overhead), The container should provide the same object instance every time.
Therefore, the program uses the protected static array $shared to store the first instantiated object. In the future, when the user obtains the mailer, the first instantiated object will be returned
class Container { static protected $shared = array(); // ... public function getMailer() { if (isset(self::$shared['mailer'])) { return self::$shared['mailer']; } $class = $this->parameters['mailer.class']; $mailer = new $class(); $mailer->setDefaultTransport($this->getMailTransport()); return self::$shared['mailer'] = $mailer; } }
Container Encapsulating these basic functions, the container needs to manage the instantiation and configuration of objects. These objects themselves do not know that they are managed by the container, and they can ignore the existence of the container. This is why the container can manage any PHP class. It would be better if the object itself uses dependency injection to handle dependencies, but of course this is not necessary.
But manually creating and maintaining containers can quickly become a nightmare. The following article will describe how Symfony 2 implements containers.
The above is what you need to understand the PHP dependency injection container series (2). For more related content, please pay attention to the PHP Chinese website (www.php.cn)!