Maison  >  Article  >  cadre php  >  Code source ThinkPHP6 : découvrez comment l'injection de dépendances est implémentée à partir de l'instanciation de la classe Http

Code source ThinkPHP6 : découvrez comment l'injection de dépendances est implémentée à partir de l'instanciation de la classe Http

藏色散人
藏色散人avant
2019-08-16 14:28:513660parcourir

Code source ThinkPHP6 : découvrez comment l'injection de dépendances est implémentée à partir de l'instanciation de la classe Http

ThinkPHP 6 a séparé la classe App de la classe Http d'origine, qui est responsable de l'initialisation et de la planification des applications, tandis que la classe App se concentre sur la gestion des conteneurs. le principe de responsabilité unique.

Dans l'analyse du code source suivante, nous pouvons apprendre du processus d'instanciation des classes App et Http comment la classe implémente l'instanciation automatique et comment l'injection de dépendances est implémentée.

Démarrer à partir du fichier d'entrée


Lors de la visite d'un site construit par ThinkPHP, le framework démarre d'abord à partir du fichier d'entrée, puis l'initialisation de l'application et la résolution de l'itinéraire, appels du contrôleur et sorties de réponse et autres opérations.

Le code principal du fichier d'entrée est le suivant :

// 引入自动加载器,实现类的自动加载功能(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);

Instanciation de l'application


Lors de l'exécution d'une nouvelle instanciation App(), il sera appelé le constructeur de first . Le constructeur

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);
}

implémente l'initialisation des différents chemins de base du projet, lit le fichier supplier.php et intègre la liaison de sa classe dans la variable membre $bind. Le contenu par défaut du fichier supplier.php. est la suivante :

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

Après la fusion, la valeur de la variable membre $bind est la suivante :

Code source ThinkPHP6 : découvrez comment linjection de dépendances est implémentée à partir de linstanciation de la classe Http

La valeur de $bind est un mappage de un ensemble d'identifiants de classe pour les classes. Il ressort également de cette implémentation que nous pouvons non seulement ajouter une identification au mappage de classe dans le fichier supplier.php, mais également écraser son mappage d'origine, c'est-à-dire remplacer certaines classes principales par des classes auto-définies. La fonction de

static::setInstance($this) est comme indiqué dans la figure :

Code source ThinkPHP6 : découvrez comment linjection de dépendances est implémentée à partir de linstanciation de la classe Http

La variable membre $instance de la classe thinkApp pointe vers une instance de la classe thinkApp. Autrement dit, la classe elle-même enregistre une instance d'elle-même.

Implémentation de la méthode Instance() :

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;
}

Le résultat de l'exécution est probablement comme ceci :

Code source ThinkPHP6 : découvrez comment linjection de dépendances est implémentée à partir de linstanciation de la classe Http

Instanciation de la classe HTTP Et le principe de l'injection de dépendances


Ici, $http = (new App())->http La première moitié est facile à comprendre, mais la seconde moitié est un peu déroutante au début. coup d'œil La classe App n'a pas de variables membres http, alors pourquoi appelez-vous hardiment quelque chose qui n'existe pas ?

Il s'avère que la classe App hérite de la classe Container, et la classe Container implémente la méthode magique __get() En PHP, lorsque la variable accédée n'existe pas, la méthode magique __get() le sera. déclenché. L'implémentation de cette méthode est la suivante :

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

Appelle en fait la méthode get() :

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

Cependant, en fait, il s'agit principalement de la méthode make() :

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;
    }

Cependant, la méthode make() s'appuie principalement sur EnsureClass() pour implémenter l'instanciation de classe. Analyse détaillée de cette méthode :

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);
        }
    }

Comme le montre le code ci-dessus, l'ajout de la méthode __make() à une classe sera appelée en premier lorsque la classe est instanciée. La méthode la plus intéressante à mentionner ci-dessus est la méthode bindParams() :

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;
}

Et parmi celles-ci, la méthode la plus intéressante à mentionner est la méthode getObjectParam() :

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;
}

la méthode getObjectParam() est à nouveau glorieuse. make() pour instancier une classe, et cette classe est exactement le paramètre extrait du constructeur Http, et ce paramètre est exactement une instance d'une classe - une instance de la classe App. À ce stade, le programme instancie non seulement la classe Http via la classe de réflexion de PHP, mais instancie également la classe App dépendante de la classe Http. Si la classe App dépend de la classe C et que la classe C dépend de la classe D... Quel que soit le nombre de couches, les classes dont dépend toute la chaîne de dépendances peuvent être instanciées.

En général, tout le processus est comme ceci : vous devez instancier la classe Http ==> Extraire le constructeur et constater qu'il dépend de la classe App ==> Commencez à instancier la classe App (le cas échéant sont trouvés, puis continuez à extraire jusqu'à la fin des temps) ==> Passez la dépendance instanciée (une instance de la classe App) dans la classe Http pour instancier la classe Http.

Ce processus, si vous lui donnez un nom prétentieux, il s’appelle « Injection de dépendances », si vous lui donnez un nom déroutant, il s’appelle « Inversion de contrôle ».

Si nous retournons aux temps anciens pour instancier la classe Http, ce processus serait probablement implémenté comme ceci (s'il y a plusieurs couches de dépendances) :

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

Comme cela doit être fatiguant. Quant au PHP moderne, laissez-le au "conteneur". Les conteneurs ont également de nombreuses fonctions, qui seront expliquées en détail plus tard.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer