In depth analysis of laravel service container

Time:2021-11-24

This article was first published inIn depth analysis of laravel service container, reprint, please indicate the source. Like friends, don’t be stingy with your approval, thank you.

BeforeDeep mining laravel life cycleIn this article, we have to explore how laravel receives HTTP requests, how to generate responses and finally present them to users.

This chapter will lead you to study the core content of another laravel framework: “service container“. Have you read itLaravel documentMy friends should have noticed that the chapter “core architecture” contains several topics:life cycleService containerService providerFacadesandConcracts.

Today, let’s unveil the mystery of “laravel service container”.

Tip: the content of this article is long and may take more reading time. In addition, it contains laravel kernel code. It is recommended to select an appropriate ide or text editor to read the source code.

directory structure

  • prologue
  • Basic concepts of dependency injection

    • What is dependency injection
    • What is a dependency injection container
    • What is inversion of control (IOC)
  • What is the laravel service container

    • Summary
  • Usage of laravel service container

    • Manage dependencies of classes to be created
    • Common binding methods

      • Bind simple binding
      • Singleton singleton binding
      • Instance instance binding
      • Contextual binding
      • Automatic injection and parsing
  • Implementation principle of laravel service container

    • Register basic services

      • Register basic service provider
      • Register core service alias to container
    • Manage the classes you need to create and their dependencies

      • Execution principle of bind method
      • Make parsing
  • data

prologue

If you have read my previous workDeep mining laravel life cycleYou should have noticed the words “app container”, “service container”, “binding” and “resolution”. Yes, these technologies are closely related to the “laravel service container”.

Before learning what is “laravel service container”, if you don’t know enough about “IOC”, “Di (dependency injection)” and “dependency injection container”, it is recommended to learn these materials first:

  • Inversion of Control Containers and the Dependency Injection pattern: learn the required classics of dependency injection;
  • Dependency injection tutorial series: the original tutorial was written by the creator of symfony framework. What I give is my translated article. The original tutorial is divided into six chapters. The first two chapters explain the basic knowledge of dependency injection, and the last four chapters explain the application of dependency injection in symfony, so it can be used as optional reading materials;
  • Easy to understand dependency injection: This is my article on dependency injection, which tries to explain the design pattern of “dependency injection” in an easy to understand way.

Although, these learning materials explain the concepts related to containers in detail. However, it is still necessary to introduce the basic concepts related to “laravel service container”.

Basic concepts of dependency injection

This section will explain the concepts of “IOC” (control inversion), “Di (dependency injection)” and “dependency injection container”.

What is dependency injection

The dependency “plug-in” that the application needs to use only depends on the definition of the interface in the compilation (coding) stage. In the run-time stage, an independent assembly module (container) completes the instantiation of the implementation class and “injects” it into the application, which is called dependency injection.

In a word: interface oriented programming.

As for how to implement interface oriented programming, inDependency injection tutorial seriesThere are examples in the first two articles. Interested friends can read this tutorial. More details can be readInversion of Control Containers and the Dependency Injection patternandEasy to understand dependency injection

What is a dependency injection container

In the process of dependency injection, an independent assembly module (container) completes the instantiation of the implementation class, then this assembly module is the “dependency injection container”.

Generally speaking, there is no need for human flesh when using the dependency injection containernewKeyword to instantiate the dependent “plug-in”, and then the “dependency injection container” automatically completes the assembly, configuration and instantiation of a module.

What is inversion of control (IOC)

IOC is the abbreviation of inversion of control, which is often called control inversion. Control inversion is not easy to understand literally.

To master what “control reversal” is, we need to fully understand what “control reversal” in the project actually “reverses”, and it needs to solve how to locate (obtain) the implementation of the dependencies required by the service.

When implementing control inversion, the control inversion operation is completed by instantiating the specific implementation class originally completed inside the module, moving it to the outside of the module, and then “injecting” the specific instance into the module through “dependency injection”.

The result of “dependency injection” is the purpose of “control reversal”, that is to sayControl reversalThe ultimate goal is toAchieve high cohesion and low coupling of the project, andAchieve this goalThe way is throughDependency injectionThis design pattern.

These are some basic concepts about service containers. As I said earlier, this article is not an article on dependency injection, so you need to learn more details by yourself from the references I listed earlier.

Next is today’s dinner. I will explain the relevant contents of laravel service container from the following perspectives:

  • What is the laravel service container;
  • Usage of laravel service container;
  • Principle of laravel service container technology.

What is the laravel service container

stayLaravel documentIn, there is an introduction to the laravel service container:

The laravel service container is a tool for managing class dependencies and performing dependency injection. Dependency injection, a fancy term, essentially means that class dependencies are “injected” into classes through constructors or, in some cases, through “setter” methods.

Highlight that the “laravel service container” is used forDependency of management classandPerform dependency injectionoftool

Through the previous section “basic concepts of dependency injection”, it is not difficult for us to draw such a simple conclusion that “laravel service container” is “dependency injection container”.

In fact, as a “dependency injection container”, the service container is only one of the core functions of the “laravel service container” to complete the registration, binding and resolution of the dependencies required by laravel; In addition, the “laravel service container” also serves as the registration program for laravel applications.

An excerpt fromDeep mining laravel life cycle“About the service container:

Creating an application instance is instantiationIlluminate\Foundation\ApplicationThis service container, which we will later callApp container。 After creating the app container, it is mainly completed: register the basic path of the application and bind the path toApp container. register the basic service provider toApp container. register the core container alias toApp containerAnd other basic services.

Therefore, it is necessary to study the larvel service containerIlluminate\Foundation\ApplicationConstructor for:

    /**
     * Create a new Illuminate application instance.
     *
     * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php#L162:27
     * @param  string|null  $basePath
     * @return void
     */
    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }
        $this->registerBaseBindings();
        $this->registerBaseServiceProviders();
        $this->registerCoreContainerAliases();
    }

Yes, the constructor of the application class performs three operations:

  • adoptregisterBaseBindings()Method to register the app instance (i.e. laravel service container) itself with the laravel service container;
  • adoptregisterBaseServiceProviders()Register the basic service provider applying laravel framework;
  • adoptregisterCoreContainerAliases()Register the specific dependency injection container and its alias to the laravel service container.

In the final analysis, the “registration” mentioned here is to perform the “bind” operation of the “laravel service container” to complete the binding interface to the implementation.

To show that what I said is true, let’s seeregisterBaseBindings()method:

/**
     *Register the basic bindings into the container
     *
     * @return void
     */
    protected function registerBaseBindings()
    {
        static::setInstance($this);

        $this->instance('app', $this);
        $this->instance(Container::class, $this);
        $this->instance(PackageManifest::class, new PackageManifest(
            new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
        ));
    }

We knowinstance()Method will instantiate the object$this * * is bound to the * * app * * and * * container:: class * * interfaces of the container. Subsequently, the implementation class obtained through * * app() – > make (‘app ‘) * * or * * app() – > make (container:: class) * * is * * $this (i.e. laravel service container instance)Object. ofinstanceYou can refer to the usage ofThe laravel service container parses the documentHowever, I will also give relevant instructions below.

Here, I believe you have a clear understanding of the “laravel service container”.

Summary

The so-called “laravel service container” not only performs the function of “dependency injection container”; At the same time, it will also serve as the registration center of laravel project to complete the registration of basic services. To put it bluntly, it will “bind” the implementation classes of many services to the “laravel service container”. To sum up, its role can be divided into the following two aspects:

  1. Registration of basic services;
  2. Manage the classes you need to create and their dependencies.

Usage of laravel service container

The use of laravel service container is generally divided into two stages: bind the implementation to the interface before use; When used, the service is parsed (made) through the interface.

Laravel has built-in many different binding methods for different usage scenarios. But no matter what kind of binding method, their ultimate goal is the same: binding interface to implementation.

The advantage of this is that the mapping relationship between interface and implementation is established in the coding stage of the project, and its specific implementation is resolved through abstract classes (interfaces) in the use stage, so as to realize decoupling in the project.

Before explaining these binding methods, let’s talk about a usage scenario of laravel service container.

Manage dependencies of classes to be created

By binding the class to be created and its dependencies to the service container, the instance of this class can be directly resolved from the service container when this class needs to be used. The instantiation of class and the injection of its dependencies are completely completed automatically by the service container.

For example, compared with passing throughnewKeyword create class instance:

<?php
$dependency = new ConfigDependency(config('cache.config.setting'));
$cache = new MemcachedCache($dependency);

Each time we instantiate, we need to manually$dependencyPassed into the constructor.

If we manage dependencies through the “laravel service container” binding:

<?php
App::bind(Cache::class, function () {
    $dependency = new ConfigDependency(config('cache.config.setting'));
    return $cache = new MemcachedCache($dependency);
});

You only need to create the required dependency once in an anonymous function$dependencyThen, the dependency is passed into the service for instantiation, and the service instance is returned.

At this point, useCacheThe service can be parsed (made) from the “laravel service container” without manual input every timeConfigDependencyDependent re instantiation service. Because all dependency injection work is performed byLaravel service containerIt is automatically done for us, which simplifies the service processing.

The following demonstrates how to parseCacheServices:

<?php
$cache = App::make(Cache::class);

First, learn about a usage scenario of laravel service container, and learn about the service containerBinding modebe of great advantage.

fromLaravel service container resolution bindingIn this part of the document, we know that the common binding methods are:

  • Bind ($abstract, $concrete) simple binding: bind the implementation to the interface and return a new instance each time when parsing;
  • Singleton ($abstract, $concrete) singleton binding: bind the implementation to the interface. Different from the bind method, the first parsing is to create an instance, and the subsequent parsing directly obtains the instance object parsed for the first time;
  • Instance ($abstract, $instance) instance binding: bind the implementation instance to the interface;
  • Context binding and automatic injection.

Next, we will learn about these binding methods.

Common binding methods

Bind simple binding

bindThe function of the method is to bind the implementation of the service to the abstract class, and then the laravel container will recreate the instance object every time the service resolution operation is performed.

The use method of bind has been briefly demonstrated in the section “managing dependencies of classes to be created”, which will be used every timeApp::make(Cache::class)When resolving the cache service, re execute the closure defined in the bind operation and re create itMemcachedCacheCache instance.

bindIn addition to receiving closures as implementations, methods can also:

  • Receive the class name of the specific implementation class;
  • Receive a null value to bind itself.

Singleton singleton binding

When single instance binding is adopted, the instance is only created during the first parsing, and subsequent parsing service operations using make will directly obtain the parsed objectshareOperation.

Binding processing is similar to bind binding. You only need to replace the bind method with the singleton method:

App::singleton(Cache::class, function () {
    $dependency = new ConfigDependency(config('cache.config.setting'));
    return $cache = new MemcachedCache($dependency);
});

Instance instance binding

The function of instance binding is to bind the created instance object to the interface for subsequent use. This use scenario is similar toregistry

For example, for storing user models:

<?php
//Create a user instance
$artisan = new user ('young Master Liu ');

//Bind instance to service container
App::instance('login-user', $artisan);

//Get user instance
$artisan = App::make('login-user');

Contextual binding

Before understanding context binding, first explain what context is and refer to the explanation of “brother wheel”:

Every program has many external variables. Only simple functions like add have no external variables. Once your program has external variables, the program is incomplete and cannot run independently. In order to make them run, you have to write some values to all external variables one by one. The collection of these values is called context. “What is context in programming?”-Vczh’s answer

Context binding inLaravel service container resolution – context bindingRelevant examples are given in the document:

use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;

$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('local');
          });

$this->app->when(VideoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('s3');
          });

The storage function is often used in the project, thanks to the built-in integration of laravelFlySystemofFilesystemInterface, we can easily implement a variety of storage service projects.

In the example, the user’s Avatar is stored locally, and the small video uploaded by the user is stored in the cloud service. In this case, it is necessary to distinguish such different usage scenarios (i.e. context or environment).

When the user’s storage avatar (photocontroller:: class) needs to use the storage service (filesystem:: class), we give the local storage driver as the implementationPhotoController::class

function () {
    return Storage::disk('local');
}

And when users upload videosVideoController::class, when the storage service (filesystem:: class) needs to be used, we will use the cloud service driver as the implementationVideoController::class

function () {
    return Storage::disk('s3');
}

In this way, different service implementations can be obtained based on different environments.

Automatic injection and parsing

The reason why the “laravel service container” is powerful is that it not only provides the method of manually binding the interface to the implementation, but also supports the function of automatic injection and parsing.

When writing a controller, we often use the type prompt function to pass a class as a dependency into the constructor; However, when executing this class, we do not need to instantiate the dependencies required by this class, which is due to the ability of automatic parsing.

For example, our user controller needs to obtain user information and define it in the constructorUserModel as dependency:

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;
class UserController
{
    private $user = null;

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

Then, when accessing the user module, laravel will automatically parse the user model without manual common model examples.

In addition to the above data binding methods, there areTag (label binding)andExtend (extended binding)Wait, there is no doubt that these contents areLaravel documentThere are also introductions, so there will be no more introductions here.

In the next section, we will go deep into the source code to see how the laravel service container performs binding and parsing.

Implementation principle of laravel service container

To understand the implementation principle of a technology, it is inevitable to explore the source code. Learning the source code is an interesting thing. This process not only makes us understand how it works, but also may bring us some surprises.

We know that the laravel service container actually handles the following two aspects:

  1. Registration of basic services;
  2. Manage the classes you need to create and their dependencies.

Register basic services

For basic registration services, clickDeep mining laravel life cycle“Has actually been covered in the article, but it is not in-depth.

This paper will further study the details of basic registration services. In addition to studying how these services are registered to the service container, you will also learn how they are used. All these need us to go deep intoIlluminate\Foundation\ApplicationInside the class:

    /**
     * Create a new Illuminate application instance.
     *
     * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php#L162:27
     * @param  string|null  $basePath
     * @return void
     */
    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }
        $this->registerBaseBindings();
        $this->registerBaseServiceProviders();
        $this->registerCoreContainerAliases();
    }

We have studied it beforeregisterBaseBindings()Method and learned that this method mainly binds itself to the service container, so that we can use it in the project$this->app->make(‘something’)To parse a service.

Now let’s focus onregisterBaseServiceProvidersandregisterCoreContainerAliasesThese two methods.

Register basic service provider

openregisterBaseServiceProvidersMethod will find that there are only three lines of code in the method body, namely registrationEventServiceProviderLogServiceProviderandRoutingServiceProviderThese three service providers:

/**
     *Register all of the base service providers
     *
     * @return void
     */
    protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));
        $this->register(new LogServiceProvider($this));
        $this->register(new RoutingServiceProvider($this));
    }

    /**
     * Register a service provider with the application.
     *
     * @param  \Illuminate\Support\ServiceProvider|string  $provider
     * @param  array  $options
     * @param  bool   $force
     * @return \Illuminate\Support\ServiceProvider
     */
    public function register($provider, $options = [], $force = false)
    {
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }

        // If the given "provider" is a string, we will resolve it, passing in the
        // application instance automatically for the developer. This is simply
        // a more convenient way of specifying your service provider classes.
        if (is_string($provider)) {
            $provider = $this->resolveProvider($provider);
        }

        //When the service provider has a register method, execute the register method to complete the binding process
        if (method_exists($provider, 'register')) {
            $provider->register();
        }

        $this->markAsRegistered($provider);

        // If the application has already booted, we will call this boot method on
        // the provider class so it has an opportunity to do its boot logic and
        // will be ready for any usage by this developer's application logic.
        //Execute the service provider boot method launcher
        if ($this->booted) {
            $this->bootProvider($provider);
        }

        return $provider;
    }

    /**
     *Boot the given service provider
     *
     * @param  \Illuminate\Support\ServiceProvider  $provider
     * @return mixed
     */
    protected function bootProvider(ServiceProvider $provider)
    {
        if (method_exists($provider, 'boot')) {
            return $this->call([$provider, 'boot']);
        }
    }

When the laravel service container executes the registration method, it needs to do the following:

  1. If the service provider has a register method, it will bind the service implementation to the container operation $provider – > register();;
  2. If the service provider has a boot method, the boot method will be executed within the bootprovider method to start the service.

It is worth noting that in the service provider’sregisterMethod, it is best to perform only the “bind” operation.

To better illustrate that the service provider only completes the binding operation, let’s take a lookEventServiceProviderService and see what it does:

<?php

namespace Illuminate\Events;

use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;

class EventServiceProvider extends ServiceProvider
{
    /**
     *Register the service provider
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('events', function ($app) {
            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                return $app->make(QueueFactoryContract::class);
            });
        });
    }
}

you ‘re rightEventServiceProviderEverything done, just throughregisterMethod binds the closure to the service container, and there is nothing else.

Register core service alias to container

Friends who have used the laravel framework should know that there is an alias system in laravel. The most common usage scenario is to set a route throughRouteClass completes the registration of a new route, such as:

Route::get('/', function() {
    return 'Hello World';
});

Thanks to laravel facades and alias system, we can easily use various services provided by laravel through alias.

Register the mapping relationship between the alias and the corresponding serviceregisterCoreContainerAliasesMethod. Due to space constraints, this paper will not expand the specific details. A separate article on alias system will be published later.

However, it is still necessary to browse what alias services laravel provides:

/**
     *Register the core class aliases in the container
     *
     * @return void
     */
    public function registerCoreContainerAliases()
    {
        foreach ([
            'app'                  => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class,  \Psr\Container\ContainerInterface::class],
            'auth'                 => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
            'auth.driver'          => [\Illuminate\Contracts\Auth\Guard::class],
            'blade.compiler'       => [\Illuminate\View\Compilers\BladeCompiler::class],
            'cache'                => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
            'cache.store'          => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
            'config'               => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
            'cookie'               => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
            'encrypter'            => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
            'db'                   => [\Illuminate\Database\DatabaseManager::class],
            'db.connection'        => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
            'events'               => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
            'files'                => [\Illuminate\Filesystem\Filesystem::class],
            'filesystem'           => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
            'filesystem.disk'      => [\Illuminate\Contracts\Filesystem\Filesystem::class],
            'filesystem.cloud'     => [\Illuminate\Contracts\Filesystem\Cloud::class],
            'hash'                 => [\Illuminate\Contracts\Hashing\Hasher::class],
            'translator'           => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
            'log'                  => [\Illuminate\Log\Writer::class, \Illuminate\Contracts\Logging\Log::class, \Psr\Log\LoggerInterface::class],
            'mailer'               => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
            'auth.password'        => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
            'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
            'queue'                => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
            'queue.connection'     => [\Illuminate\Contracts\Queue\Queue::class],
            'queue.failer'         => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
            'redirect'             => [\Illuminate\Routing\Redirector::class],
            'redis'                => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
            'request'              => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
            'router'               => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
            'session'              => [\Illuminate\Session\SessionManager::class],
            'session.store'        => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
            'url'                  => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
            'validator'            => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
            'view'                 => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
        ] as $key => $aliases) {
            foreach ($aliases as $alias) {
                $this->alias($key, $alias);
            }
        }
    }

Manage the classes you need to create and their dependencies

For the laravel service container, its internal implementation isbindsingletontagstillextendTheir basic principles are roughly similar. So in this paper, we only studybindBind to see the leopard.

We know that the binding method is defined in the laravel service containerIlluminate\Foundation\ApplicationIn class, andApplicationInherited fromIlluminate\Container\ContainerClass. These methods related to service container binding are directly inherited fromContainerClass.

Execution principle of bind method

bindBinding, as the most basic binding method, can well explain how laravel implements binding service processing.

Take it out belowContainerIn containerbindMethods and their associated methods. Since there are many methods involved in binding processing, I directly translated and supplemented the notes related to important code fragments for reading:

/**
     * Register a binding with the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bind($abstract, $concrete = null, $shared = false)
    {
        //If the implementation class $concrete is not provided, we will directly use the abstract class as the implementation $abstract.
        //After that, we do not need to explicitly specify whether $abstract and $concrete are singleton modes,
        //Instead, the $shared ID is used to determine whether they are singletons or need to be instantiated each time.
        $this->dropStaleInstances($abstract);

        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        //If the implementation class passed in during binding is not a closure, that is, the binding time is directly given the class name of the implementation class,
        //At this time, the class name should be encapsulated into a closure to ensure the unity of processing methods during parsing.
        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }

        $this->bindings[$abstract] = compact('concrete', 'shared');

        //Finally, if the abstract class has been resolved by the container, we will trigger the rebound listener.
        //And update the latest implementation of any parsed service to the abstract interface by triggering the rebound listener callback.
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }

    /**
     *Get the closure to be used when building a type. When the binding is implemented as a class name, it is encapsulated into a closure and returned.
     *
     * @param  string  $abstract
     * @param  string  $concrete
     * @return \Closure
     */
    protected function getClosure($abstract, $concrete)
    {
        return function ($container, $parameters = []) use ($abstract, $concrete) {
            if ($abstract == $concrete) {
                return $container->build($concrete);
            }

            return $container->make($concrete, $parameters);
        };
    }

    /**
     *Fire the "bound" callbacks for the given abstract type. Trigger its "bound" callback according to the given Abstract service interface
     *
     * @param  string  $abstract
     * @return void
     */
    protected function rebound($abstract)
    {
        $instance = $this->make($abstract);

        foreach ($this->getReboundCallbacks($abstract) as $callback) {
            call_user_func($callback, $this, $instance);
        }
    }

    /**
     *Get the inbound callbacks for a given type. Get the callback function of the given Abstract service.
     *
     * @param  string  $abstract
     * @return array
     */
    protected function getReboundCallbacks($abstract)
    {
        if (isset($this->reboundCallbacks[$abstract])) {
            return $this->reboundCallbacks[$abstract];
        }

        return [];
    }

staybindIn the method, the following aspects are mainly completed:

  • Kill the previously resolved service instance;
  • Encapsulate the bound implementation classes into closures to ensure the unity of subsequent processing;
  • For the resolved service instance, trigger the rebinding callback function again, and update the latest implementation class to the interface.

During the binding process, the service container will not perform the service parsing operation, which is conducive to improving the performance of the service. The corresponding service to be used will not be truly resolved until it is used during the project operation to realize “on-demand loading”.

Make parsing

Resolution processing is defined in the same way as bindingIlluminate\Container\ContainerClass, whether through manual parsing or automatic injection, the implementation principle is based on the reflection mechanism of PHP.

So we’re still directly frommakeThe method begins to dig out the relevant details:

/**
     *Resolve the given type from the container. Resolve the specific implementation of the given service from the container
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     */
    public function make($abstract, array $parameters = [])
    {
        return $this->resolve($abstract, $parameters);
    }

    /**
     *Resolve the given type from the container. Resolve the specific implementation of the given service from the container
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     */
    protected function resolve($abstract, $parameters = [])
    {
        $abstract = $this->getAlias($abstract);

        //If binding is based on context, you need to resolve the context implementation class
        $needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );

        //If a given type is bound to the singleton pattern, the instance is returned directly from the service container without re instantiation
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $this->with[] = $parameters;

        $concrete = $this->getConcrete($abstract);

        //You are ready to create an instance of this binding. The following will instantiate the given instance and all embedded dependent instances.
        //Here we are ready to create an instance. Only the service that can be built can execute the build method to instantiate the service;
        //Otherwise, that is to say, our services still have dependencies, and then constantly resolve the nested dependencies to know that they can be built (isbuildable).
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        //If our service has an extend binding, we need to execute the extension.
        //Extension binding is used to modify the configuration of a service or to decorate the service implementation.
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        //If our service is bound in the singleton mode, there is no need to cache the resolved service in the singleton object pool (instances),
        //Subsequently, you can directly obtain the singleton service object.
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

        $this->fireResolvingCallbacks($abstract, $object);

        $this->resolved[$abstract] = true;

        array_pop($this->with);

        return $object;
    }

    /**
     *Determine if the given concrete is buildable
     *
     * @param  mixed   $concrete
     * @param  string  $abstract
     * @return bool
     */
    protected function isBuildable($concrete, $abstract)
    {
        //It can be built only when the implementation class and interface are the same or implemented as a closure
        return $concrete === $abstract || $concrete instanceof Closure;
    }

    /**
     *Instance a concrete instance of the given type
     *
     * @param  string  $concrete
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function build($concrete)
    {
        //If the given implementation is a closure, execute the closure directly and return the execution result
        if ($concrete instanceof Closure) {
            return $concrete($this, $this->getLastParameterOverride());
        }

        $reflector = new ReflectionClass($concrete);

        //If the class to be resolved cannot be instantiated, i.e. trying to resolve an abstract class type, such as interface or abstract class instead of implementation class, throw an exception directly.
        if (! $reflector->isInstantiable()) {
            return $this->notInstantiable($concrete);
        }

        $this->buildStack[] = $concrete;

        //Get and implement class constructors through reflection
        $constructor = $reflector->getConstructor();

        //If the implementation class does not define a constructor, it means that the implementation class has no related dependencies.
        //We can directly instantiate this implementation class without automatically resolving dependencies (automatic injection).
        if (is_null($constructor)) {
            array_pop($this->buildStack);

            return new $concrete;
        }

        //Gets the implementation class constructor dependent parameter
        $dependencies = $constructor->getParameters();

        //Resolve all dependencies
        $instances = $this->resolveDependencies(
            $dependencies
        );

        array_pop($this->buildStack);

        //This is where we can create a service instance and return.
        return $reflector->newInstanceArgs($instances);
    }

    /**
     *Resolve all of the dependencies from the reflectionparameters
     *
     * @param  array  $dependencies
     * @return array
     */
    protected function resolveDependencies(array $dependencies)
    {
        $results = [];

        foreach ($dependencies as $dependency) {
            // If this dependency has a override for this particular build we will use
            // that instead as the value. Otherwise, we will continue with this run
            // of resolutions and let reflection attempt to determine the result.
            if ($this->hasParameterOverride($dependency)) {
                $results[] = $this->getParameterOverride($dependency);

                continue;
            }

            //When the constructor parameters are non class, that is, when the parameters are scalar types such as string and int or closures, they are resolved according to scalars and closures;
            //Otherwise, you need to resolve the class.
            $results[] = is_null($dependency->getClass())
                            ? $this->resolvePrimitive($dependency)
                            : $this->resolveClass($dependency);
        }

        return $results;
    }

    /**
     *Resolve a non class hinted primitive dependency. Resolve scalar type (closure) data according to the type prompt
     *
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolvePrimitive(ReflectionParameter $parameter)
    {
        if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->name))) {
            return $concrete instanceof Closure ? $concrete($this) : $concrete;
        }

        if ($parameter->isDefaultValueAvailable()) {
            return $parameter->getDefaultValue();
        }

        $this->unresolvablePrimitive($parameter);
    }

    /**
     *Resolve a class based dependency from the container. Resolve the class dependency from the service container (auto injection)
     *
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolveClass(ReflectionParameter $parameter)
    {
        try {
            return $this->make($parameter->getClass()->name);
        }

        catch (BindingResolutionException $e) {
            if ($parameter->isOptional()) {
                return $parameter->getDefaultValue();
            }

            throw $e;
        }
    }

The above is the core of laravel service container parsing. Thanks to the reflection mechanism of PHP, it realizes automatic dependency injection and service parsing. To sum up, it includes the following steps:

    1. For single instance binding data, if the service has been parsed, it will be returned directly; otherwise, continue to perform parsing;
    1. For the service type that is not a singleton binding, obtain the binding implementation class through the interface;
    1. When the interface is a service or closure, build processing is carried out. During construction, automatic dependency injection is carried out based on PHP reflection mechanism to parse the complete service instance object; Otherwise, continue to resolve (make) all nested dependencies;
    1. If the service has an extension binding, resolve the extension binding result;
    1. If the binding service is a singleton, add the resolved service to the singleton object pool;
    1. Other processes, such as triggering the binding listener, marking the service as resolved, and returning the service instance.

More details still need to be explored in the kernel, but it’s not much worse. Interested friends can personally understand the source code analysis and processing of other binding methods.

The above is the whole content of today’s laravel service container. I hope it can inspire you.

data

Thank you for your excellent learning materials:

https://www.insp.top/learn-la…

https://laravel-china.org/art…

https://laravel-china.org/art…

https://hk.saowen.com/a/6c880…

http://rrylee.github.io/2015/…

https://blog.tanteng.me/2016/…

https://juejin.im/entry/5916a…

https://laravel-china.org/top…

Recommended Today

Seven solutions for distributed transactions

1、 What is distributed transaction Distributed transaction means that transaction participants, transaction supporting servers, resource servers and transaction managers are located on different nodes of different distributed systems. A large operation is completed by more than n small operations. These small operations are distributed on different services. For these operations, either all of them are […]