Laravel learning notes — magical service container

Time:2022-1-14

This article is reproduced to:https://www.insp.top/learn-laravel-container

Laravel learning notes — magical service container

Someone thinks I copied from laravel college, Xinsai. The world outlook has collapsed.

Container, literally, is what holds things. Common variables, object attributes, etc. can be regarded as containers. What a container can hold depends entirely on your definition of the container. Of course, there is such a container, which stores not text and values, but objects, object descriptions (classes and interfaces) or provides object callbacks. Through this container, we can realize many advanced functions, among which “decoupling” and “dependency injection (DI)” are most often mentioned. This article starts from here.

IOC container, the core of laravel

The core of laravel is oneIOC container, according to the document, it is called“Service container”, as its name implies, the container provides a series of services required in the whole framework. As a beginner, many people will have difficulties in this concept. Therefore, I intend to start with some basic contents, and gradually uncover the veil of “dependency injection” and gradually understand this magical design concept by understanding the generation and solutions of dependency in object-oriented development.

Most of the content of this article is through examples to let readers understand what isIOC (control reversal)andDi (dependency injection), we can go deeper by understanding these concepts. For more information about the usage of the laravel service container, it is recommended to read the documentation.

The story of the birth of IOC container

There are many articles on IOC containers, which I have written before. But now I’m going to use the current inspiration to start over, so let’s start.

Superman and super power, the generation of dependence!

Object oriented programming has the following things in constant contact:Interfaceclassalsoobject。 Among them, the interface is the prototype of a class, and a class must abide by the interface it implements; Object is the product of instantiation of a class, which we call an instance. Of course, this is certainly not conducive to understanding. We will actually write code that we can’t see in the actual point to assist learning.

The world of monsters always needs some super characters to deal with.

We take a “Superman” as a class,

class Superman {}

We can imagine that a superman must have at least one superpower when he was born. This superpower can also be abstracted as an object. Define a class for this object to describe him. A superpower must have multiple attributes and (operation) methods, which is imaginable. However, at present, we will roughly define a “superpower” with only attributes. As for what we can do, we will enrich it later:

class Power {
    /**
     *Capability value
     */
    protected $ability;

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

    public function __construct($ability, $range)
    {
        $this->ability = $ability;
        $this->range = $range;
    }
}

At this time, let’s go back and modify the previous “Superman” class so that a “Superman” is given a super ability when it is created:

class Superman
{
    protected $power;

    public function __construct()
    {
        $this->power = new Power(999, 100);
    }
}

In this way, when we create an instance of “Superman”, we also create an instance of “superpower”. However, we see that there is an inevitable dependency between “Superman” and “superpower”.

The so-called “dependence” is “if I depend on you, I can’t leave you”.

In a project that implements object-oriented programming, such dependencies can be seen everywhere. A small amount of dependence will not have too intuitive impact. We gradually spread out this example to make everyone gradually realize what a nightmare experience it is when dependence reaches an order of magnitude. Of course, I will naturally talk about how to solve the problem.

A mess – terrible dependence

In the previous example, the superpower class is a specific superpower after instantiation, but we know that Superman’s superpowers are diversified, and the methods and attributes of each superpower are quite different, which can not be completely described by one kind. Let’s modify it now. We assume that Superman can have the following super abilities:

  • Flight attributes include: flight speed and continuous flight time
  • Brute force, the attributes are: strength value
  • Energy bomb. Its attributes include: damage value, shooting distance and number of simultaneous shots

We 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 trouble, I didn’t write it in detail__construct()All of this constructor only writes the parameters that need to be passed.

Well, now our Superman is a little “busy”. When Superman initializes, will we instantiate its super abilities 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), which is not good. It is conceivable that if the demand changes (different monsters cross the earth), more targeted information is needednewSuper ability, or needchangeSuper power method, we mustRemouldingSuperman*In other words, while changing my superpowers, I have to recreate a superman*。 Too inefficient! The new Superman has not been created yet, and the world has long been destroyed.

At this time, the person with an idea thought: why not? Superman’s ability can be changed at any time, just add or update a chip or other devices (didn’t think of iron man). In that case, don’t start all over again.

Yes, that’s it.

We should not manually solidify the initialization behavior of his “super ability” in the “Superman” class, but turn to the external responsibility. The external creation of super ability modules, devices or chips (we will uniformly call them “modules”) is implanted into a certain interface in Superman. This interface is an established interface, As long as the “module” meets the device of this interface, it can be used by Superman, which can improve and increase a certain ability of Superman. We can call this behavior of external responsibility for its dependent requirements“Control reversal (IOC)”。

Factory mode, dependency transfer!

Of course, there are several ways to achieve control inversion. Before that, let’s learn something interesting.

We can imagine that components, tools (or Superman modules) are things that can be produced. Of course, the place of production is the “factory“. Therefore, someone proposed such a model:Factory mode

The factory pattern, as its name implies, is a development pattern in which instances of external things that a class depends on can be created by one or more “factories”“Factory mode”。

In order to manufacture super power modules for Superman, we have created a factory, which can manufacture various modules through only 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 factory
        $factory = new SuperModuleFactory;

        //The required modules are manufactured by the method 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])
        );
        */
    }
}

It can be seen that we no longer need to initialize many third-party classes at the beginning of Superman initialization. We only need to initialize a factory class to meet the requirements. But this seems to be no different from the past, but not so muchnewkeyword. In fact, if we transform 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 factory
        $factory = new SuperModuleFactory;

        //The required modules are manufactured by the method provided by the factory
        foreach ($modules as $moduleName => $moduleOptions) {
            $this->power[] = $factory->makeModule($moduleName, $moduleOptions);
        }
    }
}

//Create Superman
$superman = new Superman([
    'Fight' => [9, 100], 
    'Shot' => [99, 50, 2]
    ]);

The result of the modification is satisfactory. Now, the creation of “Superman” no longer depends on any “super ability” class. If we modify or add a new super ability, we only need to modify itSuperModuleFactoryJust. It is very easy for us to expand Superman’s ability without re editing Superman’s class files. But this is just the beginning.

Go further! An important component of IOC container – dependency injection!

From Superman’s dependence on “super power” to Superman’s dependence on “super power module factory”, it becomes more convenient to deal with small monsters. But as you can see, dependence has not been lifted, but has changed from dependence on multiple external to dependence on one “factory”. If something goes wrong in the factory, the problem becomes very difficult.

In fact, in most cases, the factory model is enough. The disadvantages of factory mode are: unknown interface (i.e. there is no good contract model, which I will explain shortly) and single object type. In short, it is still not flexible enough. Nevertheless, the factory model is still excellent and suitable for most cases. But in order to explain the laterDependency injection, let’s exaggerate the defects of the factory model.

We know that for the modules Superman relies on, we need a unified interface, so as to connect with the injection interface on Superman, and finally improve the super ability.

In fact, I lied before, not only a bunch of small monsters, but also more big monsters. hey. Well, at this time, it seems that the factory’s production capacity is somewhat insufficient – because in the factory mode, all modules have been arranged in the factory class. If new and advanced modules are added, we must modify the factory class (such as 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 many!': .......
        }
    }
}

See… Nightmare feeling!

In fact, inspiration is one step away! You may think of a more flexible way! Yes, the next step is our main supporting role today – di (dependency injection)

Due to the increasing demand for super power modules, we need to gather high IQ talents from the whole world to solve problems together. We should not be monopolized by only a few factories. However, high IQ talents are very conceited and think their ideas are right. The super ability modules created have no unified interface and naturally can not be used normally. At this time, we need to propose a contract so that no matter who creates the module, it conforms to such an interface and can be used normally.

interface SuperModuleInterface
{
    /**
     *Super ability activation method
     *
     *Any superpower must have this method and have a parameter
     *@Param array $target is for the target, which can be one or more, yourself or others
     */
    public function activate(array $target);
}

In the above, we have defined an interface (specification and contract of super power modules). All created modules must comply with the specification before they can be produced.

In fact, this is PHPInterfaceThe use and significance of! Many people think, why does PHP need interfaces? Aren’t languages like Java and c#? So, as long as it is a normal object-oriented programming language (although PHP can be process oriented), it should have this feature. Because oneObjectItself is made of his template or prototype——Class, a specific thing generated after instantiation. Sometimes, when implementing a unified method and different functions (or features), there will be many classes. At this time, there needs to be a contract for everyone to write an interface that can be replaced at any time without impact. This rigid specification proposed by the programming language itself will add more excellent features.

Although there are some problems, we will slowly understand the benefits of the interface through our next examples.

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

/**
 *X-superenergy
 */
class XPower implements SuperModuleInterface
{
    public function activate(array $target)
    {
        //This is just an example.. Specific self brain tonic
    }
}

/**
 *Ultimate bomb (so vulgar)
 */
class UltraBomb implements SuperModuleInterface
{
    public function activate(array $target)
    {
        //This is just an example.. Specific self brain tonic
    }
}

At the same time, in order to prevent some “brick house” from acting wisely, or some traitors from maliciously making trouble, disobeying the contract, randomly manufacturing modules and affecting Superman, we have modified the Superman initialization method:

class Superman
{
    protected $module;

    public function __construct(SuperModuleInterface $module)
    {
        $this->module = $module
    }
}

The transformation is complete! Now, when we initialize the “Superman” class, the provided module instance must be aSuperModuleInterfaceInterface implementation. Otherwise, an error will be prompted.

Because the creation of Superman becomes easy, a superman does not need too many superpowers. We can create multiple Superman and inject the required superpower modules respectively. In this case, although a Superman has only one super ability, Superman is easier to become more, and we are not afraid of monsters!

Now someone is confused. What do you want to sayDependency injectionAnd?

In fact, the above content is dependency injection.

What is itDependency injection

A series of dependencies mentioned from the beginning to the present in this article, as long as they are not internally produced (such as initialization and constructor)__constructIt is injected externally in the form of parameters or other forms through factory methods and manual newDependency injection (DI)。 Is it suddenly clear? In fact, it’s that 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);

That’s all we need to say about dependency injection, the main supporting role of this article. With an understanding of dependency injection, we can go further. Slowly approach today’s protagonist

More advanced factory – IOC container!

Just listed a code:

$superModule = new XPower;

$superMan = new Superman($superModule);

Readers should see that they manually created a super power module, manually created Superman and injected the newly created super power module. Hehe, manual.

Modern society should be efficient production, clean workshop and perfect automatic assembly.

A group of monsters are coming. It’s unrealistic to produce Superman with such low efficiency. We need automation – at most one instruction and thousands of troops to meet. We need an advanced production workshop. We only need to submit a script to the production workshop, and 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’s really simple now, but it doesn’t prevent us from further improving him. Now, let’s see how to use this container!

//Create a container (later called a super factory)
$container = new Container;

//Add Superman's production script to the super factory
$container->bind('superman', function($container, $moduleName) {
    return new Superman($container->make($moduleName));
});

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

//Ibid
$container->bind('ultrabomb', function($container) {
    return new UltraBomb;
});

//************************* gorgeous dividing line**********************
//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 initialBindOperation, we registered some production scripts with the super factory, which will be executed when the production order is issued. Did you find it? We have completely removed the dependency between Superman and the super power module. More importantly, the container class has no dependency on them at all! We add a callback that can be executed (can be anonymous function, non anonymous function or class method) to the container through registration and binding as a way to produce an instance of a classscript, only in realMakeThe operation is triggered only when it is called for execution.

In this way, it is easier and more flexible for us to solve its dependencies while creating an instance. When there are new requirements, just 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 automatically search for the matching dependency requirements in a pile of registered and bound instances according to the dependency requirements of the class, and automatically inject them into the constructor parameters. This is exactly what the service container of the laravel framework does. Realizing this function is actually not troublesome in theory, but I won’t write it in this article because… I’m too lazy to write.

However, I tell you that this function of automatically searching dependency requirements is throughReflectionImplemented, just right, PHP perfectly supports reflection mechanism! About reflection, PHP official documents have detailed information, and the Chinese translation basically covers, enough to study and study!

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

Now, so far, we are no longer afraid of monsters. High IQ talents brainstorm and create standardized super ability modules according to the interface contract. Superman began mass production. In the end, everyone is Superman, so can youstuck_out_tongue_closed_eyes

Return to the normal world. We began to re-examine the core of laravel.

Now, we begin to slowly interpret the core of laravel. In fact, the core of laravel is an IOC container, which happens to be the advanced IOC container I mentioned earlier.

It can be said that the core of laravel itself is very lightweight and has no magical and substantive application functions. Various functional modules used by many people, such asRouteEloquent ORM (database ORM component)Request and responseIn fact, these classes are provided by core independent class modules. These classes are ultimately used by you from registration to instantiation. In fact, laravel’s service container is responsible for them.

We take the most commonRouteClass as an example. You may often see that the routing definition is as follows:

Route::get('/', function() {
    // bla bla bla...
});

actually,RouteClass is defined in this namespace:Illuminate\Routing\Router, filevendor/laravel/framework/src/Illuminate/Routing/Router.php

By opening, we find the series of methods of this class, such asgetpostanyAnd so on are not static methods. What’s the matter? Don’t worry, let’s go on.

Service provider

In the previous section on the IOC container, we mentioned that a class needs to be bound and registered in the container before it can be “manufactured”.

Yes, a class must be registered in the container before it can be extracted by the container. Since laravel calls this container a service container, if we need a service, we must first register and bind the service to the container. What provides the service and binds the service to the container isService provider

Although, binding a class to a container does not have to passService provider

However, we know that sometimes our classes and modules need other classes and components. In order to ensure that the required modules and components are not registered in the initialization stage, laravel splits the registration and initialization behavior. Only registration can be registered during registration, and initialization is initialization. The split product is nowService provider

Service providers are mainly divided into two parts,RegisterandBoot (boot, initialization)Refer to the documentation for details.registerBe responsible for registering the “script” with the container, but note that the registration part does not rely on unknown things. If so, move tobootpart.

Facade

Let’s now answer the previous question aboutRouteThe question of why methods can be accessed by static methods. In fact, it is written in the problem document. In short, it simulates a class and provides a static magic method__callStaticAnd map the static method to the real method.

We useRouteClass is actuallyIlluminate\Support\Facades\Routeadoptclass_alias()Function creationaliasThis class is defined in the filevendor/laravel/framework/src/Illuminate/Support/Facades/Route.php

Let’s open the file… Eh? Why is there only such a simple piece of code?

In fact, if you look carefully, you will find that this class inherits a class calledFacadeClass, here the answer is almost solved.

In the above simple definition, we seegetFacadeAccessorMethod returned aroute, what does that mean? In fact, this value isServiceProviderRegistered, you should know what you registered, of course, the real routing class!

Some people will ask how the facade is implemented. I don’t want to be too detailed. One reason is that I’m lazy. Another reason is that I find some things easier to understand and not easy to forget. I have already said a lot of details. I suggest you study them by yourself.

So far, we have almost talked about it.

Peace! We should sum up!

Anyway, world peace.

The content to be summarized here is that in fact, many things are not complex, but are afraid of complex theoretical content. I think that’s what happens when I figure out a lot of things. Many people think that laravel is not good, that is not good, and where is difficult here. I can only say that laravel is really not a first-class and excellent framework. If laravel is a first-class and excellent framework person, not a fan of laravel, it is hype. The biggest feature and excellence of laravel is that it uses a lot of relatively new (actually not new) concepts and technologies of PHP (that is, a pile of syntax candy). Therefore, laravel does fit into a framework suitable for learning. Laravel’s idea is really very different from other frameworks, which also requires that those who learn from him must be proficient in PHP andSolid foundation! If you find it very difficult to learn laravel framework, there is only one reason: your PHP foundation is not good.

In addition, if you are good at using many features of namespace and object-oriented to pursue something, you will find that it is so easy.

This article is written very hard. I hope you will respect the author’s original content. Reprint can not inform me, but be sure to keep the reprint source information, thank you!