thinkphp 5.1 Framework Parsing: Containers and Dependency Injection

In the last article, we talked about how ThinkPHP can be loaded automatically, if you want to see it.
Analysis of ThinkPHP 5.1 Source Code (2) Automatic Loading Mechanism

Before reading this article, I hope you have grasped the basic knowledge of IOC, DI and Facade. If you don't understand it, please read a few articles first.

Deep understanding of control inversion (IoC) and dependency injection (DI)

So get to the point.

Service invocation

Entry script file index.php based on analysis framework

// Loading base files
require __DIR__ . '/../thinkphp/base.php';

// Support for setting Request and Config objects in advance using static methods

// Execute applications and respond
Container::get('app')->run()->send();

The role of base.php above is to load the automatic loading mechanism, exception handling, and log functionality.

// Execute applications and respond
Container::get('app')->run()->send();

In this case, the IOC container function is used to get the app container. 1

After entering Container, we first introduce his class attributes.

protected static $instance; // Define our container class instance, use the singleton pattern, instantiate only once
protected $instances = [];    // Object instances in containers
protected $bind = [];        // Container Binding Identification
protected $name = [];        // Container Identification Alias

$instances implements the registry tree pattern 2 Store values

array (
  'think\\App' => App Example,
  'think\\Env' => Env Example,
  'think\\Config' => Config Example,
   ...
)

When bind is initialized, it loads a bunch of initial data and records a bunch of mapping relationships between category names and class names.

protected $bind = [
      'app' => 'think\\App',
      'build' => 'think\\Build',
      'cache' => 'think\\Cache',
      'config' => 'think\\Config',
      'cookie' => 'think\\Cookie',
        ...
    ]

The values recorded by the name and bind attributes are very similar, both of which are mapping relationships between category names and class names. The difference is that name records the mapping relationships that have been instantiated.

Enter get method

public static function get($abstract, $vars = [], $newInstance = false)
{
    return static::getInstance()->make($abstract, $vars, $newInstance);
}

There's nothing to say about this piece of code, but first get the instance of the current container (singleton) and instantiate it.

Enter make method

public function make($abstract, $vars = [], $newInstance = false)
{
    if (true === $vars) {
        // Always create new instantiated objects
        $newInstance = true;
        $vars        = [];
    }
    // If a class already exists and is instantiated, get its class with an alias
    $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
    // If it has been instantiated and does not need to create a new instance every time, it returns the instance directly to the registry tree.
    if (isset($this->instances[$abstract]) && !$newInstance) {
        return $this->instances[$abstract];
    }
    // If we have bound this class, for example,'app'=>'think App',
    if (isset($this->bind[$abstract])) {
        $concrete = $this->bind[$abstract];
        // Because ThinkPHP implementations can bind a closure or enter anonymous functions, here's how closures are handled
        if ($concrete instanceof Closure) {
            $object = $this->invokeFunction($concrete, $vars);
        } else {
            // Record mapping relationships and instantiate them by class name, such as think\App
            $this->name[$abstract] = $concrete;
            return $this->make($concrete, $vars, $newInstance);
        }
    } else {
        // Call the class by its name
        $object = $this->invokeClass($abstract, $vars);
    }

    if (!$newInstance) {
        $this->instances[$abstract] = $object;
    }
    // Return to the generated class
    return $object;
}

Let's split it up.

if (true === $vars) {
        // Always create new instantiated objects
        $newInstance = true;
        $vars        = [];
}

This code allows our function to call this function using make($abstract, true) so that each time we get a new instance. (I don't think that's a good way to do it. The meaning of each variable is not clear.)

// If a class already exists and is instantiated, get its class with an alias
    $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
    // If it has been instantiated and does not need to create a new instance every time, it returns the instance directly to the registry tree.
    if (isset($this->instances[$abstract]) && !$newInstance) {
        return $this->instances[$abstract];
    }

As mentioned earlier, the name stores the mapping relationship of the alias=> class name that has been instantiated. Here we try to extract the class name. If the class is instantiated, we return it directly.

// If we have bound this class, for example,'app'=>'think App',
    if (isset($this->bind[$abstract])) {
        $concrete = $this->bind[$abstract];
        // Because ThinkPHP implementations can bind a closure or enter anonymous functions, here's how closures are handled
        if ($concrete instanceof Closure) {
            $object = $this->invokeFunction($concrete, $vars);
        } else {
            // Record mapping relationships and instantiate them by class name, such as think\App
            $this->name[$abstract] = $concrete;
            return $this->make($concrete, $vars, $newInstance);
        }
    } else {
        // Call the class by its name
        $object = $this->invokeClass($abstract, $vars);
    }

Here's to see if the classes we need to load from the container have previously been bound with aliases (we can also set one directly by bind('classNickName').

  1. If it's bound, instantiate it.
  2. If not, it is assumed that it is a class name and called directly. 3

facade

In the IOC container above, we need $ioc - > get ('test'); to get the test class and greet with our $user - > Hello () method. With the facade, we can make static calls directly with Test::hello(). Let's introduce this below.

Classes under the facade package are often used to make static calls to interfaces when we write code. Here's an example of the official website.

Suppose we define an app common Test class with a hello dynamic method.

<?php
namespace app\common;

class Test
{
    public function hello($name)
    {
        return 'hello,' . $name;
    }
}

The code that calls the hello method should be similar to:

$test = new \app\common\Test;
echo $test->hello('thinkphp'); // Output hello, thinkphp

Next, we define a static proxy class app facade Test for this class (this class name is not necessarily the same as the Test class, but it is generally recommended that the name be unified for ease of administration).

<?php
namespace app\facade;

use think\Facade;

class Test extends Facade
{
    protected static function getFacadeClass()
    {
        return 'app\common\Test';
    }
}

As long as this class library inherits think Facade, it can call the dynamic method of dynamic class app common Test statically. For example, the code above can be changed to:

// Calling hello directly in a static way without instantiation
echo \app\facade\Test::hello('thinkphp');

The result also outputs hello, thinkphp.

To put it bluntly, the Facade function allows classes to be invoked statically without instantiation.

Working Principle of Facade

  1. The core principle of Facede is to inject IoC containers into the Facade ahead of time.
  2. Define a service provider's external class, in which a class's variables are defined, as is the key bound by the ioc container.
  3. The current hello method you want to call can be obtained by the static magic method _callStatic
  4. Use static: $ioc - > make ('Test');

Why use Facade

The main thing about using Facades is that it provides a simple, easy-to-remember grammar that eliminates the need to manually inject or configure long class names. In addition, because of their unique call to PHP static methods, it is very easy to test.

  1. The system can't find the location of the Container class here, so the automatic loading mechanism is executed to find the location of the Container and load it.
  2. Hang a bunch of examples on a tree and use them when needed.
  3. Direct invocation is the result of using reflection, and the knowledge points about reflection are viewed by themselves.

Keywords: PHP

Added by danboy712 on Mon, 29 Jul 2019 07:21:47 +0300