Maison  >  Article  >  développement back-end  >  Vous apprendre à créer manuellement un conteneur PHP DI

Vous apprendre à créer manuellement un conteneur PHP DI

藏色散人
藏色散人avant
2021-12-01 14:23:063376parcourir

Concernant l'injection de dépendances, je pense que tout le monde devrait y être exposé fréquemment ou au moins en avoir entendu parler. Les frameworks les plus connus prennent en charge l'injection de dépendances, comme Spring de Java, Laravel de PHP, Symfony, etc. Maintenant, permettez-moi de commencer à implémenter manuellement un simple conteneur DI.

Commencez par conduire

Conduisons d'abord une voiture et donnons-nous un exemple :

class Driver{    public function drive()
    {
        $car = new Car();        echo '老司机正在驾驶', $car->getCar(), PHP_EOL;
    }
}class Car{    protected $name = '普通汽车';    public function getCar()
    {        return $this->name;
    }
}

Il existe deux classes, Driver et Car. L'ancien conducteur Driver a une méthode driver Lors de l'appel, la voiture entière doit d'abord être. $ voiture, puis partez. La plupart des étudiants ont écrit ce code ou un code similaire. Il n'y a rien de mal avec ce code et c'est tout à fait normal. Cependant, si je veux changer de voiture, je ne pourrai pas attirer les filles avec une voiture ordinaire.

class Benz extends Car{    protected $name = '奔驰';
}

A ce moment, vous devez faire une opération plutôt dégoûtante, et vous devez changer le code du chauffeur expérimenté. (Ancien conducteur : Qu'ai-je fait de mal ? Si je change de voiture, je dois réapprendre mon permis de conduire...). Par conséquent, nous devons injecter la voiture dans le monde extérieur et découpler le conducteur et la voiture, afin que les conducteurs expérimentés n'aient plus à construire leur propre voiture lorsqu'ils conduisent. Nous avons donc le résultat suivant

class Driver{    protected $car;    public function __construct(Car $car)
    {        $this->car = $car;
    }    public function drive()
    {        echo '老司机正在驾驶', $this->car->getCar(), PHP_EOL;
    }
}

A ce moment, les classes Driver et Car ont été découplées, et les dépendances de ces deux classes sont gérées par le code de la couche supérieure. À ce moment-là, le pilote vétéran "conduira" comme ceci :

$car = new Car();
$driver = new Driver($car);
$driver->drive();

À ce moment, nous créons une instance de la dépendance Driver et l'injectons. Dans l'exemple ci-dessus, nous avons implémenté l'injection de dépendances, mais c'était manuel et c'était toujours inconfortable à écrire. Comment un travail aussi lourd peut-il être effectué manuellement ? Nous devons laisser le programme le faire tout seul. Depuis, le conteneur DI est né.

Conteneur d'injection de dépendances

L'injection de dépendance est similaire au mode IoC et au mode usine. C'est un mode qui résout la relation de couplage de dépendances entre l'appelant et l'appelé. Il résout les dépendances entre les objets, de sorte que les objets dépendent uniquement du conteneur IoC/DI et ne dépendent plus directement les uns des autres, réalisant un couplage lâche. Ensuite, lorsque l'objet est créé, le conteneur IoC/DI injecte sa dépendance (dépendance). Objets (Injecter), cela peut obtenir un couplage lâche maximum. Pour parler franchement, l'injection de dépendances signifie que le conteneur injecte des instances d'autres classes dont dépend une certaine classe dans des instances de cette classe.

Ce paragraphe est peut-être un peu abstrait. Revenons à l'exemple de tout à l'heure. Je viens de terminer l'injection de dépendances manuellement, ce qui est assez gênant si cela est fait dans un grand projet, ce sera certainement très lourd et pas assez élégant. Par conséquent, nous avons besoin d’un intendant pour faire cela à notre place, et cet intendant est le conteneur. Toute la gestion des dépendances des classes est laissée au conteneur. De manière générale, un conteneur est donc un objet global partagé par tous.

Créez votre propre conteneur DI

Pour écrire une fonction, nous devons d'abord analyser le problème, nous devons donc d'abord comprendre quelles fonctions sont nécessaires pour un simple conteneur DI, qui est directement lié à l'écriture de notre code. Pour un conteneur simple, au moins les points suivants doivent être respectés :

  • Créer une instance de la classe requise

  • Gestion complète des dépendances (DI)

  • Vous pouvez obtenir une instance d'un singleton

  • Unique au monde

Pour résumer, notre classe conteneur ressemble à ceci :

class Container{    /**
     * 单例
     * @var Container
     */
    protected static $instance;    /**
     * 容器所管理的实例
     * @var array
     */
    protected $instances = [];    private function __construct(){}  
    private function __clone(){}    /**
     * 获取单例的实例
     * @param string $class
     * @param array ...$params
     * @return object
     */
    public function singleton($class, ...$params)
    {}    /**
     * 获取实例(每次都会创建一个新的)
     * @param string $class
     * @param array ...$params
     * @return object
     */
    public function get($class, ...$params)
    {}    /**
     * 工厂方法,创建实例,并完成依赖注入
     * @param string $class
     * @param array $params
     * @return object
     */
    protected function make($class, $params = [])
    {}    /**
     * @return Container
     */
    public static function getInstance()
    {        if (null === static::$instance) {            static::$instance = new static();
        }        return static::$instance;
    }
}

Le squelette général a été déterminé, puis nous entrons dans la méthode core make :

protected function make($class, $params = []){  //如果不是反射类根据类名创建
  $class = is_string($class) ? new ReflectionClass($class) : $class;  //如果传的入参不为空,则根据入参创建实例
  if (!empty($params)) {    return $class->newInstanceArgs($params);
  }  //获取构造方法
  $constructor = $class->getConstructor();  //获取构造方法参数
  $parameterClasses = $constructor ? $constructor->getParameters() : [];  if (empty($parameterClasses)) {    //如果构造方法没有入参,直接创建
    return $class->newInstance();
  } else {    //如果构造方法有入参,迭代并递归创建依赖类实例
    foreach ($parameterClasses as $parameterClass) {
      $paramClass = $parameterClass->getClass();
      $params[] = $this->make($paramClass);
    }    //最后根据创建的参数创建实例,完成依赖的注入
    return $class->newInstanceArgs($params);
  }
}

Afin de rendre le conteneur plus facile à utiliser , j'ai apporté quelques améliorations :

  • Implémentez l'interface ArrayAccess afin que l'instance singleton puisse être obtenue directement via le tableau. Si l'instance n'existe pas, créez-la

  • Réécrivez la méthode __get pour une acquisition plus pratique

    .

Version finale :

class Container implements ArrayAccess{    /**
     * 单例
     * @var Container
     */
    protected static $instance;    /**
     * 容器所管理的实例
     * @var array
     */
    protected $instances = [];    private function __construct(){}    private function __clone(){}    /**
     * 获取单例的实例
     * @param string $class
     * @param array  ...$params
     * @return object
     */
    public function singleton($class, ...$params)
    {        if (isset($this->instances[$class])) {            return $this->instances[$class];
        } else {            $this->instances[$class] = $this->make($class, $params);
        }        return $this->instances[$class];
    }    /**
     * 获取实例(每次都会创建一个新的)
     * @param string $class
     * @param array  ...$params
     * @return object
     */
    public function get($class, ...$params)
    {        return $this->make($class, $params);
    }    /**
     * 工厂方法,创建实例,并完成依赖注入
     * @param string $class
     * @param array  $params
     * @return object
     */
    protected function make($class, $params = [])
    {        //如果不是反射类根据类名创建
        $class = is_string($class) ? new ReflectionClass($class) : $class;        //如果传的入参不为空,则根据入参创建实例
        if (!empty($params)) {            return $class->newInstanceArgs($params);
        }        //获取构造方法
        $constructor = $class->getConstructor();        //获取构造方法参数
        $parameterClasses = $constructor ? $constructor->getParameters() : [];        if (empty($parameterClasses)) {            //如果构造方法没有入参,直接创建
            return $class->newInstance();
        } else {            //如果构造方法有入参,迭代并递归创建依赖类实例
            foreach ($parameterClasses as $parameterClass) {
                $paramClass = $parameterClass->getClass();
                $params[] = $this->make($paramClass);
            }            //最后根据创建的参数创建实例,完成依赖的注入
            return $class->newInstanceArgs($params);
        }
    }    /**
     * @return Container
     */
    public static function getInstance()
    {        if (null === static::$instance) {            static::$instance = new static();
        }        return static::$instance;
    }    public function __get($class)
    {        if (!isset($this->instances[$class])) {            $this->instances[$class] = $this->make($class);
        }        return $this->instances[$class];
    }    public function offsetExists($offset)
    {        return isset($this->instances[$offset]);
    }    public function offsetGet($offset)
    {        if (!isset($this->instances[$offset])) {            $this->instances[$offset] = $this->make($offset);
        }        return $this->instances[$offset];
    }    public function offsetSet($offset, $value)
    {
    }    public function offsetUnset($offset) {        unset($this->instances[$offset]);
    }
}

Maintenant, à l'aide du conteneur Écrivons le code ci-dessus :

$driver = $app->get(Driver::class);
$driver->drive();//output:老司机正在驾驶普通汽车复制代码

C'est aussi simple que cela, un conducteur expérimenté peut démarrer la voiture. L'injection par défaut ici est l'instance de Car. Si vous devez conduire une Mercedes-Benz, il vous suffit de faire ceci :

$benz = $app->get(Benz::class);
$driver = $app->get(Driver::class, $benz);
$driver->drive();//output:老司机正在驾驶奔驰复制代码

Selon les exigences du PSR-11, le conteneur d'injection de dépendances doit implémenter l'interface PsrContainerContainerInterface. Ceci n'est qu'une démonstration et n'a pas été implémenté, car cela nécessite l'introduction de Psr. S'appuyer sur des bibliothèques est plus compliqué, mais c'est en fait très simple. Il existe juste quelques méthodes supplémentaires. Si vous êtes intéressé, vous pouvez en apprendre davantage. exigences du PSR-11 (portail) par vous-même.

Il s'agit simplement d'un conteneur DI très simple implémenté. En pratique, il y a beaucoup de choses à considérer, et la fonction du conteneur ici est toujours très simple. Il y a encore quelques écueils qui n'ont pas été résolus, comme la façon de gérer les dépendances circulaires et le mécanisme de chargement retardé...

Ceci n'est qu'un compte rendu de ma pratique gratuite du week-end. Si vous êtes intéressé, vous pouvez le lire. le code source du conteneur Laravel ou Symfony ci-dessous, ou apprenez-en plus. Regardons le conteneur Spring. Je continuerai à l'améliorer quand j'aurai le temps. [Apprentissage recommandé : "Tutoriel vidéo PHP"]

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