>  기사  >  백엔드 개발  >  PHP DI 컨테이너를 수동으로 생성하는 방법을 가르쳐주세요.

PHP DI 컨테이너를 수동으로 생성하는 방법을 가르쳐주세요.

藏色散人
藏色散人앞으로
2021-12-01 14:23:063433검색

종속성 주입과 관련하여 모든 사람이 자주 접했거나 적어도 들어본 적이 있어야 한다고 생각합니다. Java의 Spring, PHP의 Laravel, Symfony 등과 같이 더 잘 알려진 프레임워크는 모두 종속성 주입을 지원합니다. 이제 간단한 DI 컨테이너를 수동으로 구현해 보겠습니다.

운전부터 시작하세요

먼저 자동차를 운전해보고 예를 들어보겠습니다.

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

Driver와 Car라는 두 가지 클래스가 있습니다. 기존 드라이버 Driver는 호출할 때 전체 차량이 먼저 있어야 합니다. $ 차를 타고 출발합니다. 대부분의 학생들은 이 코드나 유사한 코드를 작성했습니다. 이 코드에는 아무런 문제가 없으며 매우 정상입니다. 하지만 차를 바꾸려고 하면 일반 차로는 여자를 끌 수 없습니다.

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

이때 다소 역겨운 조작도 해야하고, 숙련된 운전자의 코드도 바꿔야 합니다. (늙은 운전자 : 내가 뭘 잘못했나요? 차 바꾸면 운전면허를 다시 배워야 하는데...) 따라서 우리는 자동차를 외부 세계에 주입하고 운전자와 자동차를 분리하여 숙련된 운전자가 운전할 때 더 이상 자신의 자동차를 만들 필요가 없도록 해야 합니다. 그래서 우리는 다음과 같은 결과를 얻었습니다

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

이제 Driver와 Car 클래스는 분리되었으며, 이 두 클래스의 종속성은 상위 계층 코드에 의해 관리됩니다. 이때 베테랑 드라이버는 다음과 같이 "드라이브"합니다.

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

이때 Driver 종속성 인스턴스를 생성하여 주입합니다. 위의 예에서는 의존성 주입을 구현했지만 수동이었고 여전히 작성하기 불편했습니다. 이렇게 힘든 작업을 어떻게 수동으로 수행할 수 있습니까? 프로그램이 스스로 수행하도록 해야 합니다. 그 이후로 DI 컨테이너가 탄생했습니다.

종속성 주입 컨테이너

종속성 주입은 IoC 모드 및 팩토리 모드와 유사하며 호출자와 호출 수신자 간의 종속성 결합 관계를 해결하는 모드입니다. 개체 간의 종속 관계를 해결하여 개체가 IoC/DI 컨테이너에만 의존하고 더 이상 서로 직접적으로 종속되지 않도록 하며 느슨한 결합을 달성한 다음 개체가 생성될 때 IoC/DI 컨테이너가 종속 항목(종속성)을 주입합니다. ) 객체(주입)를 사용하면 최대의 느슨한 결합을 달성할 수 있습니다. 직설적으로 말하면 종속성 주입은 컨테이너가 특정 클래스가 의존하는 다른 클래스의 인스턴스를 이 클래스의 인스턴스에 주입하는 것을 의미합니다.

이 구절은 다소 추상적일 수 있습니다. 방금 예로 돌아가 보겠습니다. 방금 종속성 주입을 수동으로 완료했는데, 이는 꽤 번거로운 일입니다. 대규모 프로젝트에서 수행된다면 확실히 매우 번거롭고 충분히 우아하지 않을 것입니다. 그러므로 우리를 위해 이 일을 해 줄 청지기가 필요하며 이 청지기는 담는 그릇이다. 클래스의 모든 종속성 관리는 컨테이너에 맡겨집니다. 따라서 일반적으로 컨테이너는 모든 사람이 공유하는 전역 개체입니다.

나만의 DI 컨테이너 만들기

함수를 작성하려면 먼저 문제를 분석해야 하기 때문에 먼저 코드 작성과 직접적으로 관련된 간단한 DI 컨테이너에 어떤 기능이 필요한지 이해해야 합니다. 간단한 컨테이너의 경우 최소한 다음 사항을 충족해야 합니다.

  • 필수 클래스의 인스턴스 생성

  • 완전한 종속성 관리(DI)

  • 싱글톤 인스턴스를 얻을 수 있습니다

  • 전역적으로 고유함

요약하면 컨테이너 클래스는 다음과 같습니다.

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

일반적인 뼈대가 결정된 다음 핵심 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);
  }
}

컨테이너를 더 쉽게 사용하기 위해 , 몇 가지 개선 사항을 적용했습니다.

  • 싱글톤 인스턴스를 배열을 통해 직접 얻을 수 있도록 ArrayAccess 인터페이스를 구현합니다. 인스턴스가 존재하지 않으면 생성합니다.

  • 더 편리하게 __get 메서드를 다시 작성합니다.

최종 버전:

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

이제 컨테이너의 도움으로 위의 코드를 작성해 보겠습니다.

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

매우 간단합니다. 숙련된 운전자가 자동차의 시동을 걸 수 있습니다. 여기서 기본 주입은 Car 인스턴스입니다. Mercedes-Benz를 운전해야 하는 경우 다음만 수행하면 됩니다.

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

PSR-11의 요구 사항에 따라 종속성 주입 컨테이너는 PsrContainerContainerInterface 인터페이스를 구현해야 합니다. 이것은 단지 데모일 뿐이고 구현되지 않았습니다. 왜냐하면 Psr을 도입해야 하기 때문입니다. 라이브러리에 의존하는 것은 더 번거롭지만 실제로는 매우 간단합니다. 관심이 있다면 배울 수 있습니다. PSR-11(포털)의 요구 사항을 스스로 충족해야 합니다.

이것은 매우 간단한 DI 컨테이너를 구현한 것입니다. 실제로 고려해야 할 사항이 많지만 여기서 컨테이너 기능은 여전히 ​​매우 간단합니다. 순환 종속성을 처리하는 방법 및 지연된 로딩 메커니즘 등 아직 해결되지 않은 몇 가지 함정이 있습니다...

이것은 단지 제 무료 주말 연습 기록일 뿐입니다. 관심이 있으시면 읽어보실 수 있습니다. 아래 Laravel 또는 Symfony 컨테이너의 소스 코드를 참조하거나 자세히 알아보세요. Spring의 컨테이너를 살펴보겠습니다. 시간이 나면 계속해서 개선해 나가겠습니다. [추천 학습: "PHP 비디오 튜토리얼"]

위 내용은 PHP DI 컨테이너를 수동으로 생성하는 방법을 가르쳐주세요.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 csdn.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제