Laravel's in-depth understanding of control reversal (IoC) and dependency injection (DI)

Source: Internet
Author: User
Tags abstract anonymous bind constructor manual documentation reflection

A container, literally, is something to install. Common variables and object attributes can be regarded as containers. What a container can hold depends on your definition of the container. Of course, there is such a container that stores not text, numeric, but object, object description (class, interface), or provides object callback. Through this container, we have been able to implement many advanced features, most often referred to as "decoupling" and "Dependency Injection (DI )". This article starts from here.

IoC container -- core of Laravel
Laravel's core is an IoC container. According to the documentation, it is called a "service container". As the name suggests, this container provides a series of services required in the entire framework. As a beginner, many people will have difficulties in this concept. Therefore, I plan to explain some basic content and understand the generation and solution of dependency in object-oriented development, to gradually unveil the "dependency injection" and gradually understand this magical design concept.

Most of the content in this article uses examples to help readers understand what IoC (control inversion) and DI (Dependency Injection) are. By understanding these concepts, we can gain a deeper understanding. For more information about the usage of laravel service containers, see the documentation.

The story of IoC container
I have written many articles about IoC containers. But now I plan to use the current inspiration to start over.

Super Humans and super capabilities, dependent generation

Object-oriented programming involves the following: Interfaces, classes, and objects. In this case, the interface is the prototype of the class, and a class must comply with its implemented interface; the object is the product of a class instantiation, and we call it an instance. Of course, this is definitely not easy to understand. We can help you learn the code that is not used in actual writing points.

In a world where monsters are rampant, it is always necessary to draw out super characters.
We regard a "Superman" as a class,

Class Superman {}
We can imagine that a Superman must have at least one super power when it was born. This super power can also be abstracted as an object, defining a class for this object to describe it. A super power will certainly have multiple attributes and (operation) methods. This is an incredible concept. But at present, we will roughly define a "super power" with only Attributes. What can we do? We will enrich it in the future:

Class Power {
/**
* Capability value
*/
Protected $ ability;

/**
* Capability range or distance
*/
Protected $ range;

Public function _ construct ($ ability, $ range)
    {
$ This-> ability = $ ability;
$ This-> range = $ range;
    }
}
In this case, we will go back and modify the previous "Superman" class so that a "Superman" will be given a super power when it is created:

Class Superman
{
Protected $ power;

Public function _ construct ()
    {
$ This-> power = new Power (999,100 );
    }
}
In this way, when we create a "Superman" instance, we also create a "super power" instance. However, we can see that, there is an inevitable dependency between "Superman" and "Super Power.

The so-called "dependency" means "if I depend on you, I cannot leave you ".

In a project that implements object-oriented programming, such dependencies can be seen everywhere. A small number of dependencies do not have a too intuitive impact. As we gradually expand this example, we will gradually realize how a nightmare experience is achieved when the dependency reaches a magnitude. Of course, I will naturally explain how to solve the problem.

A bunch of chaos-terrible dependency

In the previous example, the super power class is actually a specific super power, but we know that the super power of Superman is diversified, and the methods and attributes of each super power are quite different, it cannot be completely described by a type. Now let's make changes. We assume that Superman can have the following Super powers:

Flight: flight speed and duration
Brute Force, attributes: power value
Energy bullet, attributes: damage value, shooting distance, and number of simultaneous shots
We have created the following classes:

Class Flight
{
Protected $ speed;
Protected $ holdtime;
Public function _ construct ($ speed, $ holdtime ){}
}

Class Force
{
Protected $ force;
Public function _ construct ($ force ){}
}

Class Shot
{
Protected $ atk;
Protected $ range;
Protected $ limit;
Public function _ construct ($ atk, $ range, $ limit ){}
}
To save the trouble, I did not write in detail all the constructor _ construct (). I only wrote the parameters to be passed.

Now, our Superman is a little busy. During Superman initialization, will we instantiate its super power as needed, roughly as follows:

Class Superman
{
Protected $ power;

Public function _ construct ()
    {
$ This-> power = new Fight (9,100 );
// $ This-> power = new Force (45 );
// $ This-> power = new Shot (99, 50, 2 );
/*
$ This-> power = array (
New Force (45 ),
New Shot (99, 50, 2)
);
*/
    }
}
We need to manually instantiate a series of required classes in the constructor (or other methods). This is not good. As you can imagine, if the demand changes (different monsters go on Earth) and more targeted new super powers or ways to change super powers, we must reengineer Superman. In other words, while changing super power, I have to create a new Superman. Efficiency is too low! The new Superman has not yet created a complete world and has been destroyed.

At this time, the brainwave thought: why not? Superman's capabilities can be changed at any time, just adding or updating a chip or other device (think of iron Xia ). In this case, you should not try again.

Yes.

We should not manually solidify his "super power" initialization behavior in the "Superman" class, but turn it to external responsibility, A super power module, device, or chip is created from the outside (we call it a "module" later), and an interface implanted into Superman is an established interface, as long as the device that meets this interface can be used by Superman, it can improve and increase a certain capability of Superman. We can call this external action to be responsible for its dependent needs as "control reversal (IoC )".

Factory mode, dependency transfer!
Of course, there are several methods to achieve control reversal. Before that, we should first understand some interesting things.

We can think of components, tools (or Superman's modules) as something that can be produced. The place where production is, of course, "Factory )", so someone proposed this model: factory model.
The factory model, as its name implies, is an instance of external things that a class depends on. It can be created by one or more "factories ".

In order to create super power modules for Superman, we have created a factory that can create various modules and only need one method:

Class SuperModuleFactory
{
Public function makeModule ($ moduleName, $ options)
    {
Switch ($ moduleName ){
Case 'fight ':
Return new Fight ($ options [0], $ options [1]);
Case 'Force ':
Return new Force ($ options [0]);
Case 'shot ':
Return new Shot ($ options [0], $ options [1], $ options [2]);
        }
    }
}
At this time, Superman can use this factory at the beginning of its creation!

Class Superman
{
Protected $ power;

Public function _ construct ()
    {
// Initialize the factory
$ Factory = new SuperModuleFactory;

// Create required modules through the methods provided by the factory
$ This-> power = $ factory-> makeModule ('fight ', [9,100]);
// $ This-> power = $ factory-> makeModule ('force', [45]);
// $ This-> power = $ factory-> makeModule ('shot ', [99, 50, 2]);
/*
$ This-> power = array (
$ Factory-> makeModule ('force', [45]),
$ Factory-> makeModule ('shot ', [99, 50, 2])
);
*/
    }
}
We can see that we no longer need to initialize many third-party classes at the beginning of Superman's initialization. We only need to initialize a factory class to meet our needs. However, this seems to be slightly different from the previous one, but there are not so many new keywords. In fact, if we change this class a little, you will understand the true meaning and value of the factory class.

Class Superman
{
Protected $ power;

Public function _ construct (array $ modules)
    {
// Initialize the factory
$ Factory = new SuperModuleFactory;

// Create required modules through the methods provided by the factory
Foreach ($ modules as $ moduleName => $ moduleOptions ){
$ This-> power [] = $ factory-> makeModule ($ moduleName, $ moduleOptions );
        }
    }
}

// Create a Superman
$ Superman = new Superman ([
'Fight '=> [9,100],
'Shot '=> [99, 50, 2]
]);
The modification results are satisfactory. Now, the creation of "Superman" is no longer dependent on any super class. If we modify or add new super powers, we only need to modify SuperModuleFactory. Super power expansion without the need to re-edit super class files, making us very easy. However, this is just the beginning.

An important component of IoC containers-dependency injection
From "Superman" to "super power" to "Superman" to "super power module factory", it became more handy to deal with small monsters. However, as you can see, the dependency has not been removed, but has changed from the original dependency on multiple external systems to the dependency on a "factory. If the factory is in trouble, the problem becomes very tricky.

In most cases, the factory model is enough. The disadvantage of the factory model is that the interface is unknown (that is, there is no good contract model, and I will explain it right away) and a single object type is generated. In short, it is not flexible enough. Even so, the factory model is still very good and applies to the vast majority of cases. However, in order to explain the subsequent dependency injection, here we will first exaggerate the defects of the factory model.
We know that we need a unified interface for the modules that Superman depends on, so that we can connect with the injection interfaces on Superman and ultimately improve super power.

In fact, I lied before, not only a bunch of small monsters, but also more big monsters. Hey. At this time, it seems that the production capacity of the factory is somewhat insufficient-because in the factory model, all modules have been arranged in the factory class. If new and advanced modules are added, we must modify the factory class (like adding a new production line ):

Class SuperModuleFactory
{
Public function makeModule ($ moduleName, $ options)
    {
Switch ($ moduleName ){
Case 'fight ':
Return new Fight ($ options [0], $ options [1]);
Case 'Force ':
Return new Force ($ options [0]);
Case 'shot ':
Return new Shot ($ options [0], $ options [1], $ options [2]);
// Case 'More ':.......
// Case 'and more ':.......
// Case 'and more ':.......
// Case 'Oh no! Its too quota! ':.......
        }
    }
}
No... A nightmare!

In fact, the inspiration is only one step away! You may think of a more flexible approach! Yes, the next step is our main supporting role today-DI (dependency injection)
As the demand for super-power modules continues to increase, we need to gather high-IQ talents from the whole world to solve the problem together. We should not have only a few factories in charge. However, high-IQ talents are very conceited and think that their ideas are correct. The super-power modules created do not have a unified interface, so they naturally cannot be used normally. At this time, we need to propose a contract, so that no matter who creates the module, it complies with such an interface, and can naturally be used normally.

Interface SuperModuleInterface
{
/**
* Super Power activation method
     *
* This method is required for any super power and has a parameter.
* @ Param array $ target: target, which can be one or more, yourself or others
*/
Public function activate (array $ target );
}
In the previous article, we set an interface (specification and contract of super power modules). All created modules must comply with this specification before they can be produced.

In fact, this is the use and significance of interfaces in php! Many people think, why does php need interfaces like this? Isn't it available in java, C #, and other languages? In this case, as long as it is a normal object-oriented programming language (although php can be process-oriented), it should have this feature. Because an object itself is a specific thing produced after being instantiated by its template or prototype-class, sometimes, when a unified method is implemented and different functions (or features) are implemented, many classes exist. At this time, a contract is required, this allows you to compile an interface that can be replaced at any time without any impact. This rigid specification proposed by the programming language itself will add more outstanding features.

Although there are some bypasses, we will gradually understand the benefits of interfaces through our next instance.

At this time, the high IQ talents who propose better super power modules follow this interface and create the following (module) categories:

/**
* X-supercapacity
*/
Class XPower implements SuperModuleInterface
{
Public function activate (array $ target)
    {
// This is just an example .. Self-developed brain
    }
}

/**
* Ultimate bomb (that's it)
*/
Class UltraBomb implements SuperModuleInterface
{
Public function activate (array $ target)
    {
// This is just an example .. Self-developed brain
    }
}
At the same time, in order to prevent some "brick-and-mortar" from being smart, or some traitors from making modules maliciously without complying with the contract and affecting Superman, we have transformed the Superman initialization method:

Class Superman
{
Protected $ module;

Public function _ construct (SuperModuleInterface $ module)
    {
$ This-> module = $ module;
    }
}
Transformation Completed! Now, when we initialize the "Superman" class, the provided module instance must be an implementation of the SuperModuleInterface interface. Otherwise, an error is prompted.

It is precisely because the creation of Superman has become easy that a Superman does not need much super power. We can create multiple super humans and inject the super power modules required separately. In this case, although a Superman has only one super power, Superman is more likely to become more and we are not afraid of monsters!

Some people are confused. What about dependency injection?

In fact, the content mentioned above is exactly dependency injection.

What is dependency injection?

A series of dependencies mentioned in this article from the beginning to the present, as long as they are not produced internally (such as initialization, construct_construct through the factory method, manually new ), it is injected by external entities as parameters or other forms, all of which belong to dependency injection (DI ). Is it clear? In fact, it is so simple. The following is a typical dependency injection:

// Super Power Module
$ SuperModule = new XPower;
// Initialize a Superman and inject a super power module dependency
$ SuperMan = new Superman ($ superModule );
The main supporting role of dependency injection in this article is so much to be discussed. After understanding the dependency injection, we can continue to explore the problem. Slowly approaching the main character of today ......

More advanced factory-IoC container
Just listed a piece of code:

$ SuperModule = new XPower;
$ SuperMan = new Superman ($ superModule );
The reader should have seen that a super power module was created manually, a super power module was created manually, and a super power module was just created. Haha, manual.

In modern society, it should be highly efficient production, clean workshops, and perfect automatic assembly.
It is unrealistic for a group of monsters to produce super humans with such low efficiency. We need automation-one command at most, and we can see each other. We need an advanced production workshop. We only need to submit a script to the production workshop so that the factory can automate production through instructions. This more advanced factory is the sublimation of the factory model-IoC container.

Class Container
{
Protected $ binds;

Protected $ instances;

Public function bind ($ abstract, $ concrete)
    {
If ($ concrete instanceof Closure ){
$ This-> binds [$ abstract] = $ concrete;
} Else {
$ This-> instances [$ abstract] = $ concrete;
        }
    }

Public function make ($ abstract, $ parameters = [])
    {
If (isset ($ this-> instances [$ abstract]) {
Return $ this-> instances [$ abstract];
        }

Array_unshift ($ parameters, $ this );

Return call_user_func_array ($ this-> binds [$ abstract], $ parameters );
    }
}
At this time, a very rough container was born. It is really simple, but it does not prevent us from further improving him. First, let's take a look at how to use this container!

// Create a container (hereinafter referred to as a super Factory)
$ Container = new Container;

// Add a Superman production script to the Super factory
$ Container-> bind ('Superman ', function ($ container, $ moduleName ){
Return new Superman ($ container-> make ($ moduleName ));
});

// Add the production script of super power modules to the Super factory
$ Container-> bind ('xpower', function ($ container ){
Return new XPower;
});

// Same as above
$ Container-> bind ('ultrabomb', function ($ container ){
Return new UltraBomb;
});

***************** *****
// Start production
$ Superman_1 = $ container-> make ('Superman', 'xpower ');
$ Superman_2 = $ container-> make ('Superman', 'ultrabomb ');
$ Superman_3 = $ container-> make ('Superman', 'xpower ');
//... Add at will
See? Through the initial bind operation, we registered some production scripts to the Super factory, which will be executed when the production commands are issued. Found? We have completely removed the dependency between Superman and super power modules. More importantly, the container class has no dependency with them! We add an executable callback (which can be an anonymous function, non-anonymous function, or class method) to the container as a script to generate a class instance, it is triggered only when a Real make operation is called and executed.

This method makes it easier for us to solve the dependency between an instance while creating it, and it is more flexible. When there are new requirements, you only need to bind another "production script.

In fact, the real IoC container is more advanced. In our current example, we still need to manually provide the module parameters required by Superman, but the real IoC container will meet the class dependency requirements, the system automatically searches for the required dependencies among the registered and bound instances and injects them into the constructor parameters. This is exactly what Laravel framework's service containers do. In theory, implementing this function is not troublesome, but I will not write it in this article, because ...... I am too lazy to write.

However, I tell you that this automatic search dependency function is implemented through Reflection, which is exactly the perfect Reflection mechanism supported by php! For reflection, php official documents have detailed information, and the Chinese translation is basically covered, enough to learn and study:

Http://php.net/manual/zh/book.reflection.php

So far, we are no longer afraid of monsters. High-IQ talents brainstorm, well-organized, and create standard super-power modules based on the interface contract. Superman started to generate data in batches. In the end, everyone is Superman, and you can be!

Review Laravel's core
Now, let's start to explain the core of Laravel. In fact, Laravel's core is an IoC container, which happens to be an advanced IoC container I mentioned earlier.

It can be said that Laravel's core is very lightweight, and there is no magical and substantial application function. Many people use various functional modules, such as Route (routing), Eloquent ORM (database ORM component), Request (Request), and Response (Response, in fact, they are provided by Class modules unrelated to the core. From registration to instantiation of these classes, they will be used by Laravel service containers.

We use the most common Route class as an example. You may often see that the routing definition is as follows:

Route: get ('/', function (){
// Bla...
});
In fact, the Route class is defined in this namespace: Illuminate \ Routing \ Router, and the file vendor/laravel/framework/src/Illuminate/Routing/Router. php.

Through opening, we found that this series of methods of this class, such as get, post, and any, are not static (static) methods. What is this? Don't worry. Let's continue.

Service provider

As mentioned in the IoC container section above, a class needs to be bound and registered to the container before it can be "manufactured ".

Yes, a class must be registered to this container before it can be extracted by the container. Since Laravel calls this container a service container, we need to register and bind a service to the container first. Then we provide the service and bind the service to the container, is the Service Provider ).

Although it is not necessary to bind a class to the container through the service provider.

However, we know that sometimes our classes and modules need other classes and components. To ensure that the required modules and components are not registered during the initialization phase, laravel splits registration and initialization actions. Registration is supported only during registration, and initialization is performed during initialization. The product after splitting is the current service provider.

The service provider is divided into two parts: register (registration) and boot (boot, initialization). For more information, see. Register is responsible for registering a "script" to the container, but note that the registration does not depend on unknown things. If yes, move to the boot section.

Facade)

Now let's answer the previous question about why the Route method can be accessed using static methods. In fact, this document is written to simulate a class, provide a static magic method _ callStatic, and map the static method to the real method.

The Route class we use is actually an alias created by the Illuminate \ Support \ Facades \ Route function through the class_alias () function, this class is defined in the file vendor/laravel/framework/src/Illuminate/Support/Facades/Route. php.

Let's open the file and check ...... Eh? Why is there only such a simple piece of code?

<? Php
Namespace Illuminate \ Support \ Facades;

/**
* @ See \ Illuminate \ Routing \ Router
*/
Class Route extends Facade {

/**
* Get the registered name of the component.
         *
* @ Return string
*/
Protected static function getFacadeAccessor ()
        {
Return 'router ';
        }

}
In fact, we will find that this class inherits a class called Facade, and the answer to this question is almost to be solved.

In the simple definition above, we can see that the getFacadeAccessor method returns a route. What does this mean? In fact, this value has been registered by a ServiceProvider. You should know what you have registered. Of course, it is the real routing class!

Someone may ask how Facade is implemented. I don't want to make it too detailed. One is that I am lazy, and the other is that I find something easier to understand and not forget. I have already explained many details. I suggest you study them on your own.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.