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 <[email protected]> * @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 源碼的注釋。