首頁 > 後端開發 > php教程 > php面試題四實現autoload

php面試題四實現autoload

不言
發布: 2023-03-24 09:20:01
原創
3349 人瀏覽過

這篇文章介紹的內容是關於php面試題四之實現autoload,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下

Yii框架宣稱自己的類別加載方式很高效,是真正的「用時載入」,那究竟特別在哪裡?今天研究了一下原始碼,發現其實是在程式碼層級加了一層「路徑快取」

Yii2 的自動載入原則

我們知道,要實作自己的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 中,儲存了一個映射表。

在上面的代码中,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面試題四實現autoload的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板