Heim >PHP-Framework >Denken Sie an PHP >ThinkPHP6-Quellcode: Sehen Sie, wie die Abhängigkeitsinjektion aus der Instanziierung der Http-Klasse implementiert wird

ThinkPHP6-Quellcode: Sehen Sie, wie die Abhängigkeitsinjektion aus der Instanziierung der Http-Klasse implementiert wird

藏色散人
藏色散人nach vorne
2019-08-16 14:28:513708Durchsuche

ThinkPHP6-Quellcode: Sehen Sie, wie die Abhängigkeitsinjektion aus der Instanziierung der Http-Klasse implementiert wird

ThinkPHP 6 trennte die App -Klasse von der ursprünglichen Http -Klasse, die für die Anwendungsinitialisierung und -planung verantwortlich ist, während sich die App -Klasse auf die Containerverwaltung konzentriert das Prinzip der Einzelverantwortung.

In der folgenden Quellcode-Analyse können wir aus dem Instanziierungsprozess der Klassen App und Http lernen, wie die Klasse die automatische Instanziierung implementiert und wie die Abhängigkeitsinjektion implementiert wird.

Beginnen Sie mit der Eintragsdatei


Wenn Sie eine von ThinkPHP erstellte Site besuchen, beginnt das Framework zunächst mit der Eintragsdatei, dann wird die Anwendung initialisiert und die Route aufgelöst. Controller-Aufrufe und Antwortausgaben sowie andere Vorgänge.

Der Hauptcode der Eintragsdatei lautet wie folgt:

// 引入自动加载器,实现类的自动加载功能(PSR4标准)
// 对比Laravel、Yii2、Thinkphp的自动加载实现,它们基本就都一样
// 具体实现可参考我之前写的Laravel的自动加载实现:
// @link: https://learnku.com/articles/20816
require __DIR__ . '/../vendor/autoload.php';
// 这一句和分为两部分分析,App的实例化和调用「http」,具体见下文分析
$http = (new App())->http;
$response = $http->run();
$response->send();
$http->end($response);

App-Instanziierung


Beim Ausführen einer neuen App()-Instanziierung wird Folgendes angezeigt: wird als erster Konstruktor bezeichnet. Der

public function __construct(string $rootPath = '')
{
    // thinkPath目录:如,D:\dev\tp6\vendor\topthink\framework\src\
    $this->thinkPath   = dirname(__DIR__) . DIRECTORY_SEPARATOR;
    // 项目根目录,如:D:\dev\tp6\
    $this->rootPath    = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
    $this->appPath     = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
    $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
    // 如果存在「绑定类库到容器」文件
    if (is_file($this->appPath . 'provider.php')) {
        //将文件里的所有映射合并到容器的「$bind」成员变量中
        $this->bind(include $this->appPath . 'provider.php');
    }
    //将当前容器实例保存到成员变量「$instance」中,也就是容器自己保存自己的一个实例
    static::setInstance($this);
    // 保存绑定的实例到「$instances」数组中,见对应分析
    $this->instance('app', $this);
    $this->instance('think\Container', $this);
}

-Konstruktor implementiert die Initialisierung verschiedener Basispfade des Projekts, liest die Datei „provider.php“ und integriert die Bindung seiner Klasse in die Mitgliedsvariable $bind, den Standardinhalt der Datei „provider.php“. ist wie folgt:

return [
    'think\Request'          => Request::class,
    'think\exception\Handle' => ExceptionHandle::class,
];

Nach dem Zusammenführen ist der Wert der $bind-Mitgliedsvariablen wie folgt:

ThinkPHP6-Quellcode: Sehen Sie, wie die Abhängigkeitsinjektion aus der Instanziierung der Http-Klasse implementiert wird

Der Wert von $bind ist eine Zuordnung von eine Reihe von Klassenkennungen für Klassen. Aus dieser Implementierung ist auch ersichtlich, dass wir nicht nur der Klassenzuordnung in der Datei „provider.php“ eine Identifikation hinzufügen, sondern auch deren ursprüngliche Zuordnung überschreiben können, d. h. einige Kernklassen durch selbstdefinierte Klassen ersetzen können. Die Funktion von

static::setInstance($this) ist wie in der Abbildung dargestellt:

ThinkPHP6-Quellcode: Sehen Sie, wie die Abhängigkeitsinjektion aus der Instanziierung der Http-Klasse implementiert wird

Die Mitgliedsvariable $instance der thinkApp-Klasse zeigt auf eine Instanz der thinkApp-Klasse. Das heißt, die Klasse selbst speichert eine Instanz von sich selbst.

Instance()-Methodenimplementierung:

public function instance(string $abstract, $instance)
{
    //检查「$bind」中是否保存了名称到实际类的映射,如 'app'=> 'think\App'
    //也就是说,只要绑定了这种对应关系,通过传入名称,就可以找到实际的类
    if (isset($this->bind[$abstract])) {
        //$abstract = 'app', $bind = "think\App"
        $bind = $this->bind[$abstract];
        //如果「$bind」是字符串,重走上面的流程
        if (is_string($bind)) {
            return $this->instance($bind, $instance);
        }
    }
    //保存绑定的实例到「$instances」数组中
    //比如,$this->instances["think\App"] = $instance;
    $this->instances[$abstract] = $instance;
    return $this;
}

Das Ausführungsergebnis ist wahrscheinlich so:

ThinkPHP6-Quellcode: Sehen Sie, wie die Abhängigkeitsinjektion aus der Instanziierung der Http-Klasse implementiert wird

Http-Klasseninstanziierung Und das Prinzip der Abhängigkeitsinjektion


Hier $http = (new App())->http Die erste Hälfte ist leicht zu verstehen, aber die zweite Hälfte ist zunächst etwas verwirrend Blick Die App-Klasse verfügt nicht über http-Mitgliedsvariablen. Warum nennen Sie also mutig etwas, das nicht existiert?

Es stellt sich heraus, dass die App-Klasse von der Container-Klasse erbt und die Container-Klasse die magische Methode __get() implementiert. In PHP ist die magische Methode __get() vorhanden, wenn die Variable, auf die zugegriffen wird, nicht vorhanden ist ausgelöst. Die Implementierung dieser Methode ist wie folgt:

public function __get($name)
{
    return $this->get($name);
}

ruft tatsächlich die get()-Methode auf:

public function get($abstract)
{
    //先检查是否有绑定实际的类或者是否实例已存在
    //比如,$abstract = 'http'
    if ($this->has($abstract)) {
        return $this->make($abstract);
    }
    // 找不到类则抛出类找不到的错误
    throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
}

Tatsächlich handelt es sich jedoch hauptsächlich um die make()-Methode:

public function make(string $abstract, array $vars = [], bool $newInstance = false)
    {
        //如果已经存在实例,且不强制创建新的实例,直接返回已存在的实例
        if (isset($this->instances[$abstract]) && !$newInstance) {
            return $this->instances[$abstract];
        }
        //如果有绑定,比如 'http'=> 'think\Http',则 $concrete = 'think\Http'
        if (isset($this->bind[$abstract])) {
            $concrete = $this->bind[$abstract];
            if ($concrete instanceof Closure) {
                $object = $this->invokeFunction($concrete, $vars);
            } else {
                //重走一遍make函数,比如上面http的例子,则会调到后面「invokeClass()」处
                return $this->make($concrete, $vars, $newInstance);
            }
        } else {
            //实例化需要的类,比如'think\Http'
            $object = $this->invokeClass($abstract, $vars);
        }
        if (!$newInstance) {
            $this->instances[$abstract] = $object;
        }
        return $object;
    }

Allerdings basiert die Methode make() hauptsächlich auf invokeClass(), um die Klasseninstanziierung zu implementieren. Detaillierte Analyse dieser Methode:

public function invokeClass(string $class, array $vars = [])
    {
        try {
            //通过反射实例化类
            $reflect = new ReflectionClass($class);
            //检查是否有「__make」方法
            if ($reflect->hasMethod('__make')) {
                //返回的$method包含'__make'的各种信息,如公有/私有
                $method = new ReflectionMethod($class, '__make');
                //检查是否是公有方法且是静态方法
                if ($method->isPublic() && $method->isStatic()) {
                    //绑定参数
                    $args = $this->bindParams($method, $vars);
                    //调用该方法(__make),因为是静态的,所以第一个参数是null
                    //因此,可得知,一个类中,如果有__make方法,在类实例化之前会首先被调用
                    return $method->invokeArgs(null, $args);
                }
            }
            //获取类的构造函数
            $constructor = $reflect->getConstructor();
            //有构造函数则绑定其参数
            $args = $constructor ? $this->bindParams($constructor, $vars) : [];
            //根据传入的参数,通过反射,实例化类
            $object = $reflect->newInstanceArgs($args);
            // 执行容器回调
            $this->invokeAfter($class, $object);
            return $object;
        } catch (ReflectionException $e) {
            throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
        }
    }

Wie aus dem obigen Code ersichtlich ist, wird das Hinzufügen der __make()-Methode zu einer Klasse zuerst aufgerufen, wenn die Klasse instanziiert wird. Die bemerkenswerteste oben ist die bindParams()-Methode:

protected function bindParams($reflect, array $vars = []): array
{
    //如果参数个数为0,直接返回
    if ($reflect->getNumberOfParameters() == 0) {
        return [];
    }
    // 判断数组类型 数字数组时按顺序绑定参数
    reset($vars);
    $type   = key($vars) === 0 ? 1 : 0;
    //通过反射获取函数的参数,比如,获取Http类构造函数的参数,为「App $app」
    $params = $reflect->getParameters();
    $args   = [];
    foreach ($params as $param) {
        $name      = $param->getName();
        $lowerName = self::parseName($name);
        $class     = $param->getClass();
        //如果参数是一个类
        if ($class) {
            //将类型提示的参数实例化
            $args[] = $this->getObjectParam($class->getName(), $vars);
        } elseif (1 == $type && !empty($vars)) {
            $args[] = array_shift($vars);
        } elseif (0 == $type && isset($vars[$name])) {
            $args[] = $vars[$name];
        } elseif (0 == $type && isset($vars[$lowerName])) {
            $args[] = $vars[$lowerName];
        } elseif ($param->isDefaultValueAvailable()) {
            $args[] = $param->getDefaultValue();
        } else {
            throw new InvalidArgumentException('method param miss:' . $name);
        }
    }
    return $args;
}

Und unter diesen ist die getObjectParam()-Methode die bemerkenswerteste:

protected function getObjectParam(string $className, array &$vars)
{
    $array = $vars;
    $value = array_shift($array);
    if ($value instanceof $className) {
        $result = $value;
        array_shift($vars);
    } else {
        //实例化传入的类
        $result = $this->make($className);
    }
    return $result;
}

getObjectParam()-Methode ist wieder großartig. Rufen Sie die an make()-Methode, um eine Klasse zu instanziieren, und diese Klasse ist genau der Parameter, der aus dem HTTP-Konstruktor extrahiert wurde, und dieser Parameter ist genau eine Instanz einer Klasse – eine Instanz der App-Klasse. Zu diesem Zeitpunkt instanziiert das Programm nicht nur die HTTP-Klasse über die Reflexionsklasse von PHP, sondern instanziiert auch die abhängige App-Klasse der HTTP-Klasse. Wenn die App-Klasse von Klasse C und Klasse C von Klasse D abhängt ... Unabhängig von der Anzahl der Ebenen können die Klassen instanziiert werden, von denen die gesamte Abhängigkeitskette abhängt.

Im Allgemeinen ist der gesamte Prozess wie folgt: Sie müssen die HTTP-Klasse instanziieren ==> Extrahieren Sie den Konstruktor und stellen Sie fest, dass er von der App-Klasse abhängt ==> Beginnen Sie mit der Instanziierung der App-Klasse (falls vorhanden). Abhängigkeiten gefunden, dann mit dem Extrahieren bis zum Ende der Zeit fortfahren) ==> Übergeben Sie die instanziierte Abhängigkeit (eine Instanz der App-Klasse) an die Http-Klasse, um die Http-Klasse zu instanziieren.

Dieser Prozess wird, wenn man ihm einen prätentiösen Namen gibt, „Dependency Injection“ genannt, wenn man ihm einen verwirrenden Namen gibt, heißt er „Inversion of Control“.

Wenn wir in die Antike zurückgehen, um die HTTP-Klasse zu instanziieren, wird dieser Prozess wahrscheinlich so implementiert (wenn es viele Schichten von Abhängigkeiten gibt):

.
.
.
$e = new E();
$d = new D($e);
$c = new D($d);
$app = new App($c);
$http = new Http($app);
.
.
.

Wie anstrengend das sein muss Sei. Was modernes PHP betrifft, überlassen Sie es einfach dem „Container“. Container haben auch viele Funktionen, die später im Detail erläutert werden.

Das obige ist der detaillierte Inhalt vonThinkPHP6-Quellcode: Sehen Sie, wie die Abhängigkeitsinjektion aus der Instanziierung der Http-Klasse implementiert wird. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:learnku.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen