>백엔드 개발 >PHP 튜토리얼 >PHP 인터뷰 질문 4: 자동 로드 구현

PHP 인터뷰 질문 4: 자동 로드 구현

不言
不言원래의
2018-04-18 09:40:563347검색

이 기사의 내용은 PHP 인터뷰 질문 4의 자동 로드 구현에 관한 것입니다. 이제 특정 참조 가치가 있으므로 도움이 필요한 친구들이 참조할 수 있습니다. 매우 효율적입니다. 그렇습니다

진짜 "시간이 많이 걸리는 로딩"

, 무엇이 그렇게 특별한가요? 오늘 소스 코드를 연구한 결과 실제로 코드 수준에서 "경로 캐시" 레이어가 추가되는 것을 발견했습니다. Yii2의 자동 로딩 원리

우리는 자신만의 자동 로딩 메소드를 구현하려면 spl_autoload_register() 함수를 사용하여

autoload 메소드

를 등록해야 한다는 것을 알고 있습니다. code>YiiBase: :autoload(), 이 메서드의 논리는 나중에 설명하겠습니다. 또한 Yii는 일반적으로 Yii::import($pathAlias, $forceInclude=false)를 사용하여 해당 클래스를 로드합니다(이 메서드는 YiiBase::import()를 직접 호출함). , 이 메서드를 YiiBase::autoload()와 함께 사용하여 "시간이 많이 걸리는 로딩"을 달성할 수 있습니다. spl_autoload_register()函数注册一个autoload方法,Yii注册的这个方法是YiiBase::autoload(),稍后再讲解这个方法的逻辑。另外,Yii一般都用Yii::import($pathAlias, $forceInclude=false)来加载相应的类(这个方法直接调用了YiiBase::import() ),这个方法配合YiiBase::autoload()就能实现“用时加载”了。

先说import的大致逻辑:
1、检查self::$_imports数组是否存在相应的$pathAlias,如果有说明已经加载过了,直接返回类名或者目录名;否则继续第2步;
2、根据路径别名获得实际的路径名,并根据路径别名最后一部分是否是“*”可以知道要加载的路径别名是否是一个文件,如果是文件,去第3步;否则去第4步;
3、如果是$forceInclude是true,则立即require这个文件,并在$_imports数组中增加一项$alias => $className;否则在数组$classMap中缓存一项$className => $realPath
4、对于路径,会在数组$_includePaths中缓存这个路径,并且在$_imports数组中增加一项$alias => $realPath
5、结束。
因为$forceInclude默认都为false,所以import不会立即加载相应的类,等到使用时才真正加载,这是YiiBase::autoload的工作。

autoload的大致逻辑:
1、检查类名是否已缓存在$classMap$_coreClasses数组中,如果是则直接require相应的文件路径,$_coreClasses是框架自有类的映射表;否则去第2步;
2、检测YiiBase::$enableIncludePath是否为false,如果是则去第3步,否则直接include($className . '.php')
3、遍历$includePaths数组,将目录名拼接上类名,检查是否为合法的php文件,如果是则include,然后跳出循环
4、结束。
需要注意的是,文档指出:如果要与其他类库一起使用,必须将$enableIncludePath置为false,以便在Yii::autoload()失败时,其他类库的autoload方法有机会执行。
//$enableIncludePath 是否要依靠PHP包含路径到自动加载类文件。默认为true. 如果你的宿主环境不允许你改变PHP包含路径,可以设置为false, 或者你想添加另外的自动加载器到默认的Yii 自动加载器.

官方描述文档

在Yii中,所有类、接口、Traits都可以使用类的自动加载机制实现在调用前自动加载。Yii借助了PHP的类自动加载机制高效实现了类的定位、导入,这一机制兼容 PSR-4 的标准。在Yii中,类仅在调用时才会被加载,特别是核心类,其定位非常快,这也是Yii高效高性能的一个重要体现。

自动加载机制的实现
Yii的类自动加载,依赖于PHP的 spl_autoload_register() , 注册一个自己的自动加载函数(autoloader),并插入到自动加载函数栈的最前面,确保Yii的autoloader会被最先调用。

类自动加载的这个机制的引入要从入口文件 index.php 开始说起:

<?php
defined(&#39;YII_DEBUG&#39;) or define(&#39;YII_DEBUG&#39;, false);
defined(&#39;YII_ENV&#39;) or define(&#39;YII_ENV&#39;, &#39;prod&#39;);
// 这个是第三方的
autoloaderrequire(__DIR__ . &#39;/../../vendor/autoload.php&#39;);
// 这个是Yii的Autoloader,放在最后面,确保其插入的autoloader会放在最前面
require(__DIR__ . &#39;/../../vendor/yiisoft/yii2/Yii.php&#39;);
// 后面不应再有
autoloader了require(__DIR__ . &#39;/../../common/config/aliases.php&#39;);
$config = yii\helpers\ArrayHelper::merge(    
require(__DIR__ . &#39;/../../common/config/main.php&#39;),    
require(__DIR__ . &#39;/../../common/config/main-local.php&#39;),    
require(__DIR__ . &#39;/../config/main.php&#39;),    
require(__DIR__ . &#39;/../config/main-local.php&#39;)
);
$application = new yii\web\Application($config);
$application->run();

这个文件主要看点在于第三方autoloader与Yii 实现的autoloader的顺序。不管第三方的代码是如何使用 spl_autoload_register() 来注册自己的autoloader的,只要Yii 的代码在最后面,就可以确保其可以将自己的autoloader插入到整个autoloder 栈的最前面,从而在需要时最先被调用。

接下来,看看Yii是如何调用 spl_autoload_register() 注册autoloader的, 这要看 Yii.php 里发生了些什么:

<?php
require(__DIR__ . &#39;/BaseYii.php&#39;);
class Yii extends \yii\BaseYii{}
// 重点看这个 
spl_autoload_registerspl_autoload_register([&#39;Yii&#39;, &#39;autoload&#39;], true, true);
// 下面的语句读取了一个映射表
Yii::$classMap = include(__DIR__ . &#39;/classes.php&#39;);

Yii::$container = new yii\di\Container;

这段代码,调用了 spl_autoload_register(['Yii', 'autoload', true, true]) ,将 Yii::autoload() 作为autoloader插入到栈的最前面了。并将 classes.php 读取到 Yii::$classMap

🎜먼저 가져오기의 일반적인 논리에 대해 이야기해 보겠습니다. 🎜
1. self::$_imports 배열에 해당 $pathAlias가 포함되어 있는지 확인하세요. 설명이 있으면 로드된 것입니다. 그렇지 않으면 2단계로 진행하여 경로 별칭에 따라 🎜실제 경로 이름🎜을 가져옵니다. 경로 별칭의 마지막 부분이 "*"인지 여부🎜 로드된 경로 별칭이 파일인지 여부, 파일인 경우 3단계로 이동, 그렇지 않으면 4단계로 이동 $forceInclude가 true이면 즉시 파일을 요구하고 $_imports 배열에 항목 $alias => $className;을 추가합니다. 그렇지 않으면 항목을 캐시합니다. $classMap 배열의 $className => $realPath
4. 경로의 경우 이 경로는 $_includePaths 및 <code>$_imports 배열에 $alias => $realPath; 항목을 추가합니다.
5.
$forceInclude는 기본적으로 false이므로 import는 해당 클래스를 즉시 로드하지 않으며, 사용될 때까지 실제로 로드하지 않습니다. 이것이 YiiBase::autoload입니다. 코드> 일. 🎜🎜🎜자동 로드의 일반 논리: 🎜 <br>1. 클래스 이름이 <code>$classMap 또는 $_coreClasses 배열에 캐시되어 있는지 확인하세요. 해당 파일 경로를 직접 요구합니다. $_coreClasses는 프레임워크 자체 클래스의 매핑 테이블입니다. 그렇지 않으면
2단계로 이동합니다. /code>가 false이면 3단계로 이동하고, 그렇지 않으면 직접 include($className . '.php')
3. 디렉토리 이름을 배열하고 연결합니다. 클래스 이름을 입력하고, 유효한 PHP 파일인지 확인하고, 그렇다면 포함시킨 후 루프에서 빠져나옵니다.
4.
문서에는 다음과 같이 명시되어 있습니다. 다른 클래스 라이브러리와 함께 사용하려면 $enableIncludePath를 false로 설정하여 Yii::autoload()에서 실패하면 다른 클래스 라이브러리의 autoload 메서드를 실행할 기회가 있습니다.
//$enableIncludePath 클래스 파일을 자동으로 로드하기 위해 PHP 포함 경로를 사용할지 여부입니다. 기본값은 true입니다. 호스팅 환경에서 PHP 포함 경로 변경을 허용하지 않거나 기본 Yii 오토로더에 다른 오토로더를 추가하려는 경우 🎜🎜공식 설명 문서🎜🎜Yii에서 모두 클래스, 인터페이스 및 특성은 클래스의 자동 로딩 메커니즘을 사용하여 호출하기 전에 자동으로 로드될 수 있습니다. Yii는 PHP의 자동 클래스 로딩 메커니즘을 사용하여 클래스 위치 지정 및 가져오기를 효율적으로 구현합니다. 이 메커니즘은 PSR-4 표준과 호환됩니다. Yii에서는 클래스가 호출될 때만 로드됩니다. 특히 핵심 클래스는 매우 빠르게 배치되며 이는 Yii의 높은 효율성과 고성능을 보여주는 중요한 표현이기도 합니다. 🎜🎜자동 로딩 메커니즘 구현
Yii의 자동 클래스 로딩은 PHP의 spl_autoload_register()에 의존합니다. 자체 자동 로딩 함수(자동 로더)를 등록하고 이를 자동 로딩 함수 스택 앞에 삽입하여 Yii가 자동 로더가 먼저 호출됩니다. 🎜🎜이 자동 클래스 로딩 메커니즘의 도입은 index.php 항목 파일로 시작됩니다: 🎜
return [  &#39;yii\base\Action&#39; => YII2_PATH . &#39;/base/Action.php&#39;,  &#39;yii\base\ActionEvent&#39; => YII2_PATH . &#39;/base/ActionEvent.php&#39;,  ... ...

  &#39;yii\widgets\PjaxAsset&#39; => YII2_PATH . &#39;/widgets/PjaxAsset.php&#39;,  &#39;yii\widgets\Spaceless&#39; => YII2_PATH . &#39;/widgets/Spaceless.php&#39;,
];
🎜이 파일의 주요 요점은 타사 자동 로더와 Yii가 구현하는 자동 로더의 순서입니다. 타사 코드가 자체 오토로더를 등록하기 위해 spl_autoload_register()를 어떻게 사용하더라도 Yii의 코드가 끝에 있는 한 자체 오토로더를 시스템 전면에 삽입할 수 있음을 보장할 수 있습니다. 전체 autoloder 스택. 따라서 필요할 때 먼저 호출됩니다. 🎜🎜다음으로 Yii가 spl_autoload_register()를 호출하여 오토로더를 등록하는 방법을 살펴보겠습니다. 이는 Yii.php에서 발생하는 상황에 따라 다릅니다. 🎜
public static function autoload($className){
    if (isset(static::$classMap[$className])) {        
    $classFile = static::$classMap[$className];        if ($classFile[0] === &#39;@&#39;) {            $classFile = static::getAlias($classFile);
        }
    } elseif (strpos($className, &#39;\&#39;) !== false) {        
    $classFile = static::getAlias(&#39;@&#39; . str_replace(&#39;\&#39;, &#39;/&#39;,            $className) . &#39;.php&#39;, false);        
    if ($classFile === false || !is_file($classFile)) {            return;
        }
    } else {        
    return;
    }    
    include($classFile);    
    if (YII_DEBUG && !class_exists($className, false) &&
        !interface_exists($className, false) && !trait_exists($className,        false)) {        
        throw new UnknownClassException(        
        "Unable to find &#39;$className&#39; in file: $classFile. Namespace missing?"
        );
    }
}
🎜이 코드는 spl_autoload_register(['Yii', 'autoload', true, true]), 스택 앞에 autoloaderYii::autoload()를 삽입합니다. 그리고 class.php를 Yii::$classMap으로 읽어서 매핑 테이블을 저장하세요. 🎜

在上面的代码中,Yii类是里面没有任何代码,并未对 BaseYii::autoload() 进行重载,所以,这个 spl_autoload_register() 实际上将 BaseYii::autoload() 注册为autoloader。如果,你要实现自己的autoloader,可以在 Yii 类的代码中,对 autoload() 进行重载。

在调用 spl_autoload_register() 进行autoloader注册之后,Yii将 calsses.php 这个文件作为一个映射表保存到 Yii::$classMap 当中。这个映射表,保存了一系列的类名与其所在PHP文件的映射关系,比如:

return [  &#39;yii\base\Action&#39; => YII2_PATH . &#39;/base/Action.php&#39;,  &#39;yii\base\ActionEvent&#39; => YII2_PATH . &#39;/base/ActionEvent.php&#39;,  ... ...

  &#39;yii\widgets\PjaxAsset&#39; => YII2_PATH . &#39;/widgets/PjaxAsset.php&#39;,  &#39;yii\widgets\Spaceless&#39; => YII2_PATH . &#39;/widgets/Spaceless.php&#39;,
];

这个映射表以类名为键,以实际类文件为值,Yii所有的核心类都已经写入到这个 classes.php 文件中,所以,核心类的加载是最便捷,最快的。现在,来看看这个关键先生 BaseYii::autoload()

public static function autoload($className){
    if (isset(static::$classMap[$className])) {        
    $classFile = static::$classMap[$className];        if ($classFile[0] === &#39;@&#39;) {            $classFile = static::getAlias($classFile);
        }
    } elseif (strpos($className, &#39;\\&#39;) !== false) {        
    $classFile = static::getAlias(&#39;@&#39; . str_replace(&#39;\\&#39;, &#39;/&#39;,            $className) . &#39;.php&#39;, false);        
    if ($classFile === false || !is_file($classFile)) {            return;
        }
    } else {        
    return;
    }    
    include($classFile);    
    if (YII_DEBUG && !class_exists($className, false) &&
        !interface_exists($className, false) && !trait_exists($className,        false)) {        
        throw new UnknownClassException(        
        "Unable to find &#39;$className&#39; in file: $classFile. Namespace missing?"
        );
    }
}

从这段代码来看Yii类自动加载机制的运作原理:

检查 $classMap[$className] 看看是否在映射表中已经有拟加载类的位置信息;

如果有,再看看这个位置信息是不是一个路径别名,即是不是以 @ 打头, 是的话,将路径别名解析成实际路径。 如果映射表中的位置信息并非一个路径别名,那么将这个路径作为类文件的所在位置。 类文件的完整路径保存在 $classFile ;

如果 $classMap[$className] 没有该类的信息, 那么,看看这个类名中是否含有 \ , 如果没有,说明这是一个不符合规范要求的类名,autoloader直接返回。 PHP会尝试使用其他已经注册的autoloader进行加载。 如果有 \ ,认为这个类名符合规范,将其转换成路径形式。 即所有的 \ 用 / 替换,并加上 .php 的后缀。

将替换后的类名,加上 @ 前缀,作为一个路径别名,进行解析。 从别名的解析过程我们知道,如果根别名不存在,将会抛出异常。 所以,类的命名,必须以有效的根别名打头:

// 有效的类名,因为@yii是一个已经预定义好的别名use yii\base\Application;// 无效的类名,因为没有 @foo 或 @foo/bar 的根别名,要提前定义好use foo\bar\SomeClass;

使用PHP的 include() 将类文件加载进来,实现类的加载。

从其运作原理看,最快找到类的方式是使用映射表。 其次,Yii中所有的类名,除了符合规范外,还需要提前注册有效的根别名。

运用自动加载机制
在入口脚本中,除了Yii自己的autoloader,还有一个第三方的autoloader:

require(__DIR__ . &#39;/../../vendor/autoload.php&#39;);

这个其实是Composer提供的autoloader。Yii使用Composer来作为包依赖管理器,因此,建议保留Composer的autoloader,尽管Yii的autoloader也能自动加载使用Composer安装的第三方库、扩展等,而且更为高效。但考虑到毕竟是人家安装的,人家还有一套自己专门的规则,从维护性、兼容性、扩展性来考虑,建议保留Composer的autoloader。

如果还有其他的autoloader,一定要在Yii的autoloader注册之前完成注册,以保证Yii的autoloader总是最先被调用。

如果你有自己的autoloader,也可以不安装Yii的autoloaer,只是这样未必能有Yii的高效,且还需要遵循一套类似的类命名和加载的规则。就个人的经验而言,Yii的autoloader完全够用,没必要自己重复造轮子。

相关推荐:

php面试题三之yii2和yii的不一样的地方

php面试题二之用到过的传输协议

php面试题一之线程和进程的区别(顺带提下协程)

위 내용은 PHP 인터뷰 질문 4: 자동 로드 구현의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.