Since looking at the Laravel source code to see how ServiceProvider loads, we know that Application (or Container) acts as a Laravel Container, incorporating almost all of Laravel’s core functionality into this Container.

What is this Application/Container?

Before we get to the Container, we need to briefly introduce the Inversion of Control principle.

Inversion of Control

Before we know what he means, we’d better understand one principle:

Dependence Inversion principle (DIP) advocates:

  • High-level modules should not depend on low-level modules. Both should rely on abstraction
  • Abstractions should not depend on details, details should depend on abstractions
  • Program for interfaces, not implementations

In programming, we develop the code modularized, and there is no way to avoid dependencies between them. For example, module A depends on module B, so according to the DIP, module A should rely on the interface of module B rather than the implementation of module B.

Let’s take an example.

We need a Logger function that outputs system logs to a file. We could write it like this:

class LogToFile {
    public function execute($message) {
        info('log the message to a file :'.$message); }}Copy the code

Call directly where needed:

class UseLogger {
    protected $logger;

    public function __construct(LogToFile $logger) {
        $this->logger = $logger;
    }

    public function show(a) {
        $user = 'yemeishu';
        $this->logger->execute($user); }}Copy the code

Write a test case:

$useLogger = new UseLogger(new LogToFile());

$useLogger->show();
Copy the code

If we need to print logs to pins, we can rewrite the LogToDD class:

class LogToDD {
    public function execute($message) {
        info('Log the message to nail :'.$message); }}Copy the code

In this case, we also need to modify the UseLogger code to introduce the LogToDD class:

class UseLogger {
    protected $logger;

    public function __construct(LogToDD $logger) {
        $this->logger = $logger;
    }

    public function show(a) {
        $user = 'yemeishu';
        $this->logger->execute($user); }}Copy the code

This is where you can actually “smell” bad code:

If I use a lot of ends, that means I have to introduce changes everywhere.

According to the DIP principle, we should be interface oriented. Let the user rely on the interface, not the implementation. So we create an interface:

interface Logger {
    public function execute($message);
}
Copy the code

Then let LogToFile and LogToDD be Logger implementations:

class LogToFile implements Logger {
    public function execute($message) {
        info('log the message to a file :'.$message); }}class LogToDD implements Logger {
    public function execute($message) {
        info('Log the message to nail :'.$message); }}Copy the code

So when we use the end, we introduce the Logger interface directly and let the code strip the implementation.

class UseLogger {
    protected $logger;

    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }

    public function show(a) {
        $user = 'yemeishu';
        $this->logger->execute($user); }}Copy the code

In this way, you can ensure that whether you save the file or send it to the nail, you don’t need to change the code, you just need to call it again, as the business needs to choose. As the test:

$useLogger1 = new UseLogger(new LogToFile());
$useLogger1->show();

$useLogger2 = new UseLogger(new LogToDD());
$useLogger2->show();
Copy the code

Results:

There is a problem, however, that we will still “hardcode” our implementation class (LogToDD or LogToFile) when instantiating it.

Is there a way to reverse the control of the final new LogToDD() as well?

Implementation of binding

Here we bind the implementation class to an interface or identity key, and we just parse that interface or key to get our implementation class.

Let’s write a simple class for binding and parsing:

class SimpleContainer {

    // Used to store all binding key-values
    protected static $container = [];

    public static function bind($name, Callable $resolver) {
        static::$container[$name] = $resolver;
    }

    public static function make($name) {
        if(isset(static::$container[$name])){
            $resolver = static::$container[$name] ;
            return $resolver();
        }
        throw new Exception("Binding does not exist in container"); }}Copy the code

We can test it:

SimpleContainer::bind(Logger::class, function (a) {
    return new LogToFile();
});

$useLogger3 = new UseLogger(SimpleContainer::make(Logger::class));

$useLogger3->show();
Copy the code

As long as there is a binding between Logger and LogToFile in one place, references can be resolved directly wherever they need to be called.

This means that, in this way, all the coding is done by introducing “interfaces” instead of implementing classes. Fully implement the DPI principle.

When we bring all of these bindings together, they make up our subject for today: “Container” — illuminate/Container.

Laravel Container

Before exploring The Laravel Container, let’s use the Container example above to see how it can be easily implemented.

$container = new Container();
$container->bind(Logger::class, LogToFile::class);

$useLogger4 = new UseLogger($container->make(Logger::class));
$useLogger4->show();
Copy the code

To achieve the same effect

[2018-05-19 15:36:30] testing.INFO: log the message to a file :yemeishu
Copy the code

Note: During Laravel development, we will write this binding in the Boot or Register of the APPServiceProvider, or any other ServiceProvider.

See Laravel source code to learn how to load ServiceProvider and explain the above principles. We are familiar with the use of Containers.

Next, you can look at the source code for the Container.

Container source code

Container is used for binding and parsing.

The binding

Let’s look at the main binding types:

  1. Bind a singleton
  2. Binding instance
  3. Bind the interface to the implementation
  4. Bind initial data
  5. Situational binding
  6. Tag binding

Here we analyze according to these types:

1. Bind a singleton

public function singleton($abstract, $concrete = null)
    {
        $this->bind($abstract, $concrete, true);
    }
Copy the code

The main argument, $share = true, is used to mark the binding as a singleton.

2. Bind the instance

public function instance($abstract, $instance)
{
    $this->removeAbstractAlias($abstract);

    $isBound = $this->bound($abstract);

    unset($this->aliases[$abstract]);

    // We'll check to determine if this type has been bound before, and if it has
    // we will fire the rebound callbacks registered with the container and it
    // can be updated with consuming classes that have gotten resolved here.
    $this->instances[$abstract] = $instance;

    if ($isBound) {
        $this->rebound($abstract);
    }

    return $instance;
}
Copy the code

$instance = $instances; $instances = $instances;

3. Bind the tag

/**
 * Assign a set of tags to a given binding.
 *
 * @param  array|string  $abstracts
 * @paramarray|mixed ... $tags *@return void
 */
public function tag($abstracts, $tags)
{
    $tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1);

    foreach ($tags as $tag) {
        if (! isset($this->tags[$tag])) {
            $this->tags[$tag] = [];
        }

        foreach ((array) $abstracts as $abstract) {
            $this->tags[$tag][] = $abstract; }}}Copy the code

This good understanding, mainly put $of array under the same set of tags, finally can pass tag, analytic $of this group.

4. The binding

public function bind($abstract, $concrete = null, $shared = false)
{
    // If no concrete type was given, we will simply set the concrete type to the
    // abstract type. After that, the concrete type to be registered as shared
    // without being forced to state their classes in both of the parameters.
    $this->dropStaleInstances($abstract);

    // If the implementation passed in is empty, bind $concrete itself
    if (is_null($concrete)) {
        $concrete = $abstract;
    }

    // If the factory is not a Closure, it means it is just a class name which is
    // bound into this container to the abstract type and we will just wrap it
    // up inside its own Closure to give us more convenience when extending.
    // The purpose is to convert $concrete into a closure function
    if (! $concrete instanceof Closure) {
        $concrete = $this->getClosure($abstract, $concrete);
    }

    // Store them in the $Bindings array. If $shared = true, it represents the bindings singleton
    $this->bindings[$abstract] = compact('concrete'.'shared');

    // If the abstract type was already resolved in this container we'll fire the
    // rebound listener so that any objects which have already gotten resolved
    // can have their copy of the object updated via the listener callbacks.
    if ($this->resolved($abstract)) {
        $this->rebound($abstract); }}Copy the code

parsing

/**
 * Resolve the given type 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.
 *
 * @param  string  $abstract
 * @param  array  $parameters
 * @return mixed
 */
protected function resolve($abstract, $parameters = [])
{
    $abstract = $this->getAlias($abstract);

    $needsContextualBuild = ! empty($parameters) || ! is_null(
        $this->getContextualConcrete($abstract)
    );

    // If an instance of the type is currently being managed as a singleton we'll
    // just return an existing instance instead of instantiating new instances
    // so the developer can keep using the same objects instance every time.
    if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
        return $this->instances[$abstract];
    }

    $this->with[] = $parameters;

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

    // We're ready to instantiate an instance of the concrete type registered for
    // the binding. This will instantiate the types, as well as resolve any of
    // its "nested" dependencies recursively until all have gotten resolved.
    if ($this->isBuildable($concrete, $abstract)) {
        $object = $this->build($concrete);
    } else {
        $object = $this->make($concrete);
    }

    // If we defined any extenders for this type, we'll need to spin through them
    // and apply them to the object being built. This allows for the extension
    // of services, such as changing configuration or decorating the object.
    foreach ($this->getExtenders($abstract) as $extender) {
        $object = $extender($object, $this);
    }

    // If the requested type is registered as a singleton we'll want to cache off
    // the instances in "memory" so we can return it later without creating an
    // entirely new instance of an object on each subsequent request for it.
    if ($this->isShared($abstract) && ! $needsContextualBuild) {
        $this->instances[$abstract] = $object;
    }

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

    // Before returning, we will also set the resolved flag to "true" and pop off
    // the parameter overrides for this build. After those two things are done
    // we will be ready to return back the fully constructed class instance.
    $this->resolved[$abstract] = true;

    array_pop($this->with);

    return $object;
}
Copy the code

Let’s walk through the “analytic” function step by step:

$needsContextualBuild = ! empty($parameters) || ! is_null(
    $this->getContextualConcrete($abstract)
);
Copy the code

This method is mainly to distinguish whether the parsed object has parameters, if there are parameters, but also need to do further analysis of the parameters, because the passed parameters, may also be dependency injection, so also need to pass the parameters for parsing; We’ll do that later.

// If an instance of the type is currently being managed as a singleton we'll
// just return an existing instance instead of instantiating new instances
// so the developer can keep using the same objects instance every time.
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
    return $this->instances[$abstract];
}
Copy the code

If it is a bound singleton and does not require the above parameter dependency. $this->instances[$this->instances]

$concrete = $this->getConcrete($abstract); ./**
 * Get the concrete type for a given abstract.
 *
 * @param  string  $abstract
 * @return mixed   $concrete
 */
protected function getConcrete($abstract)
{
    if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
        return $concrete;
    }

    // If we don't have a registered resolver or concrete for the type, we'll just
    // assume each type is a concrete name and will attempt to resolve it as is
    // since the container should be able to resolve concretes automatically.
    if (isset($this->bindings[$abstract])) {
        return $this->bindings[$abstract]['concrete'];
    }

    return $abstract;
}
Copy the code

This step is mainly from the context of the binding to find whether you can find the binding class; If not, find the associated implementation class from $bindings[]; If not, return $abstract itself.

// We're ready to instantiate an instance of the concrete type registered for
// the binding. This will instantiate the types, as well as resolve any of
// its "nested" dependencies recursively until all have gotten resolved.
if ($this->isBuildable($concrete, $abstract)) {
    $object = $this->build($concrete);
} else {
    $object = $this->make($concrete); }.../**
 * Determine if the given concrete is buildable.
 *
 * @param  mixed   $concrete
 * @param  string  $abstract
 * @return bool
 */
protected function isBuildable($concrete, $abstract)
{
    return $concrete === $abstract || $concrete instanceof Closure;
}
Copy the code

$this->build($concrete); $this->build($concrete); $this->build($concrete); $this->make($concrete) ¶ $this->make($concrete) ¶

$this->build($concrete)

/**
 * Instantiate a concrete instance of the given type.
 *
 * @param  string  $concrete
 * @return mixed
 *
 * @throws \Illuminate\Contracts\Container\BindingResolutionException
 */
public function build($concrete)
{
    // If the concrete type is actually a Closure, we will just execute it and
    // hand back the results of the functions, which allows functions to be
    // used as resolvers for more fine-tuned resolution of these objects.
    // If a closure is passed in, the closure function is executed directly and the result is returned
    if ($concrete instanceof Closure) {
        return $concrete($this.$this->getLastParameterOverride());
    }

    // Use reflection to parse the class.
    $reflector = new ReflectionClass($concrete);

    // If the type is not instantiable, the developer is attempting to resolve
    // an abstract type such as an Interface of Abstract Class and there is
    // no binding registered for the abstractions so we need to bail out.
    if (! $reflector->isInstantiable()) {
        return $this->notInstantiable($concrete);
    }

    $this->buildStack[] = $concrete;

    // Get the constructor
    $constructor = $reflector->getConstructor();

    // If there are no constructors, that means there are no dependencies then
    // we can just resolve the instances of the objects right away, without
    // resolving any other types or dependencies out of these containers.
    // If there is no constructor, no arguments are passed in, which means no context-dependent parsing is required.
    if (is_null($constructor)) {
        // Pop the content of the build process, then construct the object output directly.
        array_pop($this->buildStack);

        return new $concrete;
    }

    // Get the constructor argument
    $dependencies = $constructor->getParameters();

    // Once we have all the constructor's parameters we can create each of the
    // dependency instances and then use the reflection instances to make a
    // new instance of this class, injecting the created dependencies in.
    // Parse out all context-dependent objects, insert them into functions, and construct object output
    $instances = $this->resolveDependencies(
        $dependencies
    );

    array_pop($this->buildStack);

    return $reflector->newInstanceArgs($instances);
}
Copy the code

This method splits into two branches: if $concrete instanceof Closure is called, the Closure function is called directly and the result is $concrete(); Another branch is to pass in a $concrete === $abstract === class name, which is parsed and new by reflection.

See the notes above for details. The use of the ReflectionClass class, specific reference: php.golaravel.com/class.refle…

Look at the code below:

// If we defined any extenders for this type, we'll need to spin through them
// and apply them to the object being built. This allows for the extension
// of services, such as changing configuration or decorating the object.
foreach ($this->getExtenders($abstract) as $extender) {
    $object = $extender($object, $this);
}

// If the requested type is registered as a singleton we'll want to cache off
// the instances in "memory" so we can return it later without creating an
// entirely new instance of an object on each subsequent request for it.
if ($this->isShared($abstract) && ! $needsContextualBuild) {
    $this->instances[$abstract] = $object;
}

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

// Before returning, we will also set the resolved flag to "true" and pop off
// the parameter overrides for this build. After those two things are done
// we will be ready to return back the fully constructed class instance.
$this->resolved[$abstract] = true;

array_pop($this->with);

return $object;
Copy the code

This is a little bit easier to understand. Mainly judge whether there is an extension, then extend the function accordingly; $this->instances; $this->instances; $this->instances; Finally, do some analytical “cleaning up”.

Tag = tag; tag = tag;

/**
 * Resolve all of the bindings for a given tag.
 *
 * @param  string  $tag
 * @return array
 */
public function tagged($tag)
{
    $results = [];

    if (isset($this->tags[$tag])) {
        foreach ($this->tags[$tag] as $abstract) {
            $results[] = $this->make($abstract); }}return $results;
}
Copy the code

If the tag value passed in is in the tags array, we iterate over all $abstract, parse it one by one, and save the result to the array output.

conclusion

While the core of the Container is clear, there are many details worth exploring, such as $alias-related, event-related, extension-related.

Finally, I recommend that you look at the Container: Silexphp /Pimple

PHP 5.3 Dependency Injection container pimple.symfony.com

To be continued