Yii2 的自動加載分兩部分,一部分是 Composer 的自動加載機制,另一部分是 Yii2 框架自身的自動加載機制。
對於庫的自動加載信息,Composer 生成了一個 vendor/autoload.php 文件。你可以簡單的引入這個文件,你會得到一個自動加載的支持。
在之前的文章,入口文件的介紹中,我們可以看到如下內容:
// 引入 vendor 中的 autoload.php 文件,會基於 composer 的機制自動加載類 require(__DIR__ . '/../vendor/autoload.php');
因為這個系列主要是關於 Yii2 的,所以有關 Composer 自動加載機制就不在這裡詳細說明了。
可查閱資料:
Yii2 框架的自動加載是通過 spl_autoload_register 方法實現的。
在之前的文章,入口文件的介紹中,我們可以看到如下內容:
// 引入 Yii 框架的文件 Yii.php require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');
Yii.php 裡究竟是什麼內容?如何實現了自動加載?
下面我們來看一下,Yii.php 的內容如下:
<?php
/**
* Yii bootstrap file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
require(__DIR__ . '/BaseYii.php');
/**
* Yii is a helper class serving common framework functionalities.
*
* It extends from [[\yii\BaseYii]] which provides the actual implementation.
* By writing your own Yii class, you can customize some functionalities of [[\yii\BaseYii]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Yii extends \yii\BaseYii
{
}
/**
* spl_autoload_register — 注冊給定的函數作為 __autoload 的實現
*
* bool spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] )
*
* 將函數注冊到SPL __autoload函數隊列中。如果該隊列中的函數尚未激活,則激活它們。
* 如果在你的程序中已經實現了__autoload()函數,它必須顯式注冊到__autoload()隊列中。
* 因為 spl_autoload_register()函數會將Zend Engine中的__autoload()函數取代為spl_autoload()或spl_autoload_call()。
* 如果需要多條 autoload 函數,spl_autoload_register() 滿足了此類需求。
* 它實際上創建了 autoload 函數的隊列,按定義時的順序逐個執行。
* 相比之下, __autoload() 只可以定義一次。
*
* autoload_function
* 欲注冊的自動裝載函數。如果沒有提供任何參數,則自動注冊 autoload 的默認實現函數spl_autoload()。
*
* throw
* 此參數設置了 autoload_function 無法成功注冊時, spl_autoload_register()是否拋出異常。
*
* prepend
* 如果是 true,spl_autoload_register() 會添加函數到隊列之首,而不是隊列尾部。
*
* Yii 注冊了 Yii 的 autoload 函數,實現自動加載, 其實現在 \yii\BaseYii 中
*/
spl_autoload_register(['Yii', 'autoload'], true, true);
// 定義 Yii 核心的 class 的類名與文件地址的 Map
Yii::$classMap = require(__DIR__ . '/classes.php');
// 創建 Yii 的依賴注入的容器
Yii::$container = new yii\di\Container();
其主要內容就是引入了 BaseYii.php 文件,然後聲明了類 Yii,繼承了 BaseYii,然後注冊了 Yii (其實是 BaseYii)的 autoload 方法,去實現自動加載。之後又引入了Yii 核心類名與文件地址一一對應的 Map,存儲到 Yii::$classMap 中。最後創建了一個 yii\di\Container 的實例,存儲到 Yii::$container 中。
可以看出實現自動加載的關鍵代碼是:
spl_autoload_register(['Yii', 'autoload'], true, true);
下面我們來看一下 BaseYii 中 autoload 方法的實現,其內容如下:
/**
* Class autoload loader.
* This method is invoked automatically when PHP sees an unknown class.
* The method will attempt to include the class file according to the following procedure:
*
* 1. Search in [[classMap]];
* 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt
* to include the file associated with the corresponding path alias
* (e.g. `@yii/base/Component.php`);
*
* This autoloader allows loading classes that follow the [PSR-4 standard](http://www.php-fig.org/psr/psr-4/)
* and have its top-level namespace or sub-namespaces defined as path aliases.
*
* Example: When aliases `@yii` and `@yii/bootstrap` are defined, classes in the `yii\bootstrap` namespace
* will be loaded using the `@yii/bootstrap` alias which points to the directory where bootstrap extension
* files are installed and all classes from other `yii` namespaces will be loaded from the yii framework directory.
*
* Also the [guide section on autoloading](guide:concept-autoloading).
*
* @param string $className the fully qualified class name without a leading backslash "\"
* @throws UnknownClassException if the class does not exist in the class file
*/
public static function autoload($className)
{
// 自動加載類
if (isset(static::$classMap[$className])) {
// 如果 $classMap 中存在該類,就直接使用
$classFile = static::$classMap[$className];
// 如果第一個字符串為'@',就意味著對應的文件地址是別名,就將它轉化成真實的文件地址
if ($classFile[0] === '@') {
$classFile = static::getAlias($classFile);
}
} elseif (strpos($className, '\\') !== false) {
// 如果存在'\\',就意味著含有 namespace,可以拼成別名,再根據別名獲取真實的文件地址
$classFile = static::getAlias('@' . str_replace('\\', '/', $className) . '.php', false);
// 沒取到真是文件地址或者獲取的地址不是一個文件,就返回空
if ($classFile === false || !is_file($classFile)) {
return;
}
} else {
return;
}
// 引入該類的文件
include($classFile);
// 如果是調試模式,而且 $className 即不是類,不是接口,也不是 trait,就拋出異常
if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && !trait_exists($className, false)) {
throw new UnknownClassException("Unable to find '$className' in file: $classFile. Namespace missing?");
}
}
其中,大家可能不太清楚 getAlias 方法,這個方法其實就是將 Yii2 中的別名轉化成真實的文件地址,關於該方法的具體內容,之後會詳細講解。
舉幾個例子,幫助大家理解一下。
如果 Yii::$classMap 的值如下:
Yii::$classMap = [
'app/test/Test' => '/var/www/basic/webtest/Test.php'
];
當你使用 ‘app/test/Test’ 類時,就會自動引入 '/var/www/basic/webtest/Test.php' 文件,項目中的內容當然不是這個樣子的,這只是個簡單的例子,便於大家理解。
在繼續上面的例子,如果你使用了‘yii\base\Component’ 類,它就會轉變成 ‘@yii/base/Component.php’ 別名,然後在根據別名獲取到它的文件地址,引入進來。
以上就是 Yii2 的自動加載機制的基本內容~~
對 Yii2 源碼有興趣的同學可以關注項目 yii2-2.0.3-annotated,現在在上面已經添加了不少關於 Yii2 源碼的注釋,之後還會繼續添加~
有興趣的同學也可以參與進來,提交 Yii2 源碼的注釋。