ThinkPHP6 소스 코드: Http 클래스의 인스턴스화에서 종속성 주입이 어떻게 구현되는지 확인하세요.

藏色散人
풀어 주다: 2019-10-09 17:56:06
앞으로
3653명이 탐색했습니다.

ThinkPHP6 소스 코드: Http 클래스의 인스턴스화에서 종속성 주입이 어떻게 구현되는지 확인하세요.

ThinkPHP 6은 Http 클래스를 원본 App 클래스에서 분리하여 애플리케이션 초기화 및 일정 관리 등을 담당합니다. . 함수인 반면, App 클래스는 컨테이너 관리에 중점을 두고 단일 책임 원칙을 따릅니다. App 类中分离出 Http 类,负责应用的初始化和调度等功能,而 App 类则专注于容器的管理,符合单一职责原则。

以下源码分析,我们可以从 AppHttp

다음 소스 코드 분석을 통해 AppHttp 클래스의 인스턴스화 프로세스에서 클래스가 자동 인스턴스화 및 종속성을 구현하는 방법을 배울 수 있습니다. 주입 어떻게 달성됩니까?

항목 파일에서 시작


ThinkPHP에서 구축한 사이트를 방문하면 프레임워크는 먼저 항목 파일부터 시작하고, 그런 다음 애플리케이션 초기화, 경로 확인, 컨트롤러 호출 및 응답 출력과 같은 작업입니다.

항목 파일의 주요 코드는 다음과 같습니다.

// 引入自动加载器,实现类的自动加载功能(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);
로그인 후 복사
앱 인스턴스화


Execute new App( )이 인스턴스화되고 생성자가 먼저 호출됩니다.

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);
}
로그인 후 복사

생성자는 프로젝트의 다양한 기본 경로 초기화를 구현하고, 공급자.php 파일을 읽고, 해당 클래스의 바인딩을 $bind 멤버 변수에 통합합니다. php 파일은 다음과 같습니다.

return [
    'think\Request'          => Request::class,
    'think\exception\Handle' => ExceptionHandle::class,
];
로그인 후 복사

병합 후 $bind 멤버 변수의 값은 다음과 같습니다.

ThinkPHP6 소스 코드: Http 클래스의 인스턴스화에서 종속성 주입이 어떻게 구현되는지 확인하세요.

The $bind의 값은 클래스에 대한 ID 매핑의 집합입니다. 또한 이 구현에서 우리는 공급자.php 파일의 클래스 매핑에 ID를 추가할 수 있을 뿐만 아니라 원래 매핑을 덮어쓸 수도 있습니다. 즉, 일부 핵심 클래스를 자체 정의된 클래스로 대체할 수도 있습니다.

static::setInstance($this) 구현된 함수는 그림과 같습니다.

ThinkPHP6 소스 코드: Http 클래스의 인스턴스화에서 종속성 주입이 어떻게 구현되는지 확인하세요.

$instance 멤버 변수 thinkApp 클래스의 thinkApp 클래스 인스턴스를 가리킵니다. 즉, 클래스 자체가 자신의 인스턴스를 저장합니다.

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;
}
로그인 후 복사

실행 결과는 아마도 다음과 같습니다:

ThinkPHP6 소스 코드: Http 클래스의 인스턴스화에서 종속성 주입이 어떻게 구현되는지 확인하세요.

#🎜🎜 #Http 클래스 인스턴스화 및 종속성 주입 원리


여기서 $http = (new App())->http, 전반부는 쉽습니다. 이해하세요, 후반부는 언뜻 보기에 약간 혼란스럽습니다. App 클래스에 http 멤버 변수가 없습니다. 존재하지 않는 것에 대한 굵은 호출이 있는 이유는 무엇입니까?

App 클래스는 Container 클래스를 상속하고, Container 클래스는 __get() 매직 메서드를 구현하는 것으로 나타났습니다. PHP에서는 액세스된 변수가 존재하지 않을 때 __get() 매직 메서드를 사용합니다. 트리거됩니다. 이 메소드의 구현은 다음과 같습니다:

public function __get($name)
{
    return $this->get($name);
}
로그인 후 복사

실제로는 get() 메소드를 호출합니다:

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

그러나 실제로는 주로 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;
    }
로그인 후 복사

그러나 make() 메서드는 클래스 인스턴스화를 구현하기 위해 주로 InvokeClass()에 의존합니다. 이 메서드에 대한 자세한 분석:

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);
        }
    }
로그인 후 복사

위 코드를 보면 __make() 메서드가 클래스에 추가되면 클래스가 인스턴스화될 때 먼저 호출된다는 것을 알 수 있습니다. 위에서 가장 주목할만한 것은 findParams() 메소드입니다:

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;
}
로그인 후 복사

이 중에서 가장 주목할만한 것은 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;
}
로그인 후 복사

getObjectParam() 메소드 다시 한 번 make() 메소드를 호출하여 클래스를 인스턴스화합니다. 이 클래스는 정확히 Http 생성자에서 추출된 매개변수이고 이 매개변수는 정확히 클래스의 인스턴스, 즉 App 클래스의 인스턴스입니다. 이 시점에서 프로그램은 PHP의 리플렉션 클래스를 통해 Http 클래스를 인스턴스화할 뿐만 아니라 Http 클래스의 종속 App 클래스도 인스턴스화합니다. App 클래스가 C 클래스에 종속되고 C 클래스가 D 클래스에 종속되는 경우... 레이어 수에 관계없이 전체 종속성 체인이 종속되는 클래스를 인스턴스화할 수 있습니다.

일반적으로 전체 프로세스는 대략 다음과 같습니다. Http 클래스를 인스턴스화해야 합니다 ==> 생성자를 추출하여 App 클래스에 종속되어 있는지 확인합니다. ==> App 클래스 인스턴스화를 시작합니다( 여전히 종속성이 있는 것으로 확인되면 시간이 끝날 때까지 계속 추출합니다. ==> 인스턴스화된 종속성(App 클래스의 인스턴스)을 Http 클래스에 전달하여 Http 클래스를 인스턴스화합니다.

이 프로세스를 허식적인 이름으로 지정하면 "의존성 주입"이라고 하고, 혼란스러운 이름으로 지정하면 "제어 역전"이라고 합니다.

이 프로세스에서 고대로 돌아가면 Http 클래스를 인스턴스화해야 합니다. 이는 아마도 다음과 같이 구현될 것입니다(종속성 레이어가 많은 경우):

.
.
.
$e = new E();
$d = new D($e);
$c = new D($d);
$app = new App($c);
$http = new Http($app);
.
.
.
로그인 후 복사
#🎜 🎜#이게 얼마나 피곤한가요? 최신 PHP의 경우 "컨테이너"에 그대로 두십시오. 컨테이너에는 많은 기능도 있는데 이에 대해서는 나중에 자세히 설명하겠습니다.

위 내용은 ThinkPHP6 소스 코드: Http 클래스의 인스턴스화에서 종속성 주입이 어떻게 구현되는지 확인하세요.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:learnku.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿