深入理解Yii2.0 类自动加载机制


深入理解Yii2.0 类自动加载机制


正文

你如果不使用框架,那你可能了解如何加载类的相关知识点,毕竟这是代码运行的基础。 但如果经常使用框架开发,又没有深究过框架原理,那就对文件加载会比较模糊。 当你在代码开发中,在类上用了use classPath\className,然后在类中就可以使用classPath\className的方法了, 一方面你会觉得这很方便也很神奇,另一方面你会觉得这个框架做得实在是太好了。 如果仅仅停留在这个层面上,你的技术是不会得到进步的,我们就是要挖掘里面的原因。

下面就讲讲Yii2的类自动加载机制。类自动加载就是自动include()通过文件地址引入文件。

文件举例

project/admin/controllers/SiteController.php 内容:

namespace admin\controllers;

use common\logics\user\UserLogic;
use Yii;
use yii\web\Controller;

class SiteController extends Controller
{
    public function actionIndex()
    {
        echo UserLogic::getDefaultName();
    }
}

project/common/logics/user/UserLogic.php 内容:

namespace common\logics\user;

use Yii;

class UserLogic 
{
    public static function getDefaultName()
    {
        return 'Tom';
    }
}

当我们通过网址访问site/index时,输出 Tom 。

SiteController是Yii加载解析的,我们理解;但project/common/logics/user/UserLogic.php却是我们随便乱写的, 包括目录结构,我们既然没有include这个文件,怎么可以use引入使用呢?

原理解析

PHP碰到没有定义的类就看有没有定义spl_autoload_register()注册的堆栈,如果有,就通过定义路径include加载文件; 如果没有定义spl_autoload_register(),就看__autoload()是否定义,如果有就按照__autoload()函数运行, 如果没有则说明无法使用类方法,报错。 spl_autoload_register() 的作用具体看PHP spl_autoload_register()相关

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

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

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

project/admin/web/index.php 内容:

<?php
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');

// 这个是第三方的autoloader
require __DIR__ . '/../../vendor/autoload.php';  
// 这个是Yii的Autoloader,放在最后面,确保其插入的autoloader会放在最前面
require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php';  
// 后面不应再有autoloader了

require __DIR__ . '/../../common/config/bootstrap.php';
require __DIR__ . '/../config/bootstrap.php';

$config = yii\helpers\ArrayHelper::merge(
    require __DIR__ . '/../../common/config/main.php',
    require __DIR__ . '/../../common/config/main-local.php',
    require __DIR__ . '/../config/main.php',
    require __DIR__ . '/../config/main-local.php'
);

(new yii\web\Application($config))->run();

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

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

<?php
require(__DIR__ . '/BaseYii.php');

class Yii extends \yii\BaseYii
{
}

// 重点看这个 spl_autoload_register
spl_autoload_register(['Yii', 'autoload'], true, true);
// 下面的语句读取了一个映射表
Yii::$classMap = require(__DIR__ . '/classes.php');

// 依赖注入,这里不用关心
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文件的映射关系,比如:

<?php
return [
  'yii\base\Action' => YII2_PATH . '/base/Action.php',
  'yii\base\ActionEvent' => YII2_PATH . '/base/ActionEvent.php',
  'yii\base\ActionFilter' => YII2_PATH . '/base/ActionFilter.php',
  'yii\base\Application' => YII2_PATH . '/base/Application.php',
  'yii\base\ArrayAccessTrait' => YII2_PATH . '/base/ArrayAccessTrait.php',
  'yii\base\Arrayable' => YII2_PATH . '/base/Arrayable.php',
  'yii\base\ArrayableTrait' => YII2_PATH . '/base/ArrayableTrait.php',
  'yii\base\Behavior' => YII2_PATH . '/base/Behavior.php',
  'yii\base\BootstrapInterface' => YII2_PATH . '/base/BootstrapInterface.php',
  'yii\base\Component' => YII2_PATH . '/base/Component.php',
  'yii\base\Configurable' => YII2_PATH . '/base/Configurable.php',
  'yii\base\Controller' => YII2_PATH . '/base/Controller.php',
  'yii\base\DynamicModel' => YII2_PATH . '/base/DynamicModel.php',
  'yii\base\ErrorException' => YII2_PATH . '/base/ErrorException.php',
  'yii\base\ErrorHandler' => YII2_PATH . '/base/ErrorHandler.php',
  'yii\base\Event' => YII2_PATH . '/base/Event.php',
  'yii\base\Exception' => YII2_PATH . '/base/Exception.php',
  'yii\base\ExitException' => YII2_PATH . '/base/ExitException.php',
  'yii\base\InlineAction' => YII2_PATH . '/base/InlineAction.php',
  'yii\base\InvalidCallException' => YII2_PATH . '/base/InvalidCallException.php',
  'yii\base\InvalidConfigException' => YII2_PATH . '/base/InvalidConfigException.php',
  'yii\base\InvalidParamException' => YII2_PATH . '/base/InvalidParamException.php',
  'yii\base\InvalidRouteException' => YII2_PATH . '/base/InvalidRouteException.php',
  'yii\base\InvalidValueException' => YII2_PATH . '/base/InvalidValueException.php',
  'yii\base\Model' => YII2_PATH . '/base/Model.php',
  'yii\base\ModelEvent' => YII2_PATH . '/base/ModelEvent.php',
  'yii\base\Module' => YII2_PATH . '/base/Module.php',
  'yii\base\NotSupportedException' => YII2_PATH . '/base/NotSupportedException.php',
  'yii\base\Object' => YII2_PATH . '/base/Object.php',
  'yii\base\Request' => YII2_PATH . '/base/Request.php',
  'yii\base\Response' => YII2_PATH . '/base/Response.php',
  'yii\base\Security' => YII2_PATH . '/base/Security.php',
  'yii\base\Theme' => YII2_PATH . '/base/Theme.php',
  'yii\base\UnknownClassException' => YII2_PATH . '/base/UnknownClassException.php',
  'yii\base\UnknownMethodException' => YII2_PATH . '/base/UnknownMethodException.php',
  'yii\base\UnknownPropertyException' => YII2_PATH . '/base/UnknownPropertyException.php',
  'yii\base\UserException' => YII2_PATH . '/base/UserException.php',
  'yii\base\View' => YII2_PATH . '/base/View.php',
  'yii\base\ViewContextInterface' => YII2_PATH . '/base/ViewContextInterface.php',
  'yii\base\ViewEvent' => YII2_PATH . '/base/ViewEvent.php',
  'yii\base\ViewNotFoundException' => YII2_PATH . '/base/ViewNotFoundException.php',
  'yii\base\ViewRenderer' => YII2_PATH . '/base/ViewRenderer.php',
  'yii\base\Widget' => YII2_PATH . '/base/Widget.php',
  'yii\base\WidgetEvent' => YII2_PATH . '/base/WidgetEvent.php',
  'yii\behaviors\AttributeBehavior' => YII2_PATH . '/behaviors/AttributeBehavior.php',
  'yii\behaviors\AttributeTypecastBehavior' => YII2_PATH . '/behaviors/AttributeTypecastBehavior.php',
  'yii\behaviors\BlameableBehavior' => YII2_PATH . '/behaviors/BlameableBehavior.php',
  'yii\behaviors\SluggableBehavior' => YII2_PATH . '/behaviors/SluggableBehavior.php',
  'yii\behaviors\TimestampBehavior' => YII2_PATH . '/behaviors/TimestampBehavior.php',
  'yii\caching\ApcCache' => YII2_PATH . '/caching/ApcCache.php',
  'yii\caching\ArrayCache' => YII2_PATH . '/caching/ArrayCache.php',
  'yii\caching\Cache' => YII2_PATH . '/caching/Cache.php',
  'yii\caching\ChainedDependency' => YII2_PATH . '/caching/ChainedDependency.php',
  'yii\caching\DbCache' => YII2_PATH . '/caching/DbCache.php',
  'yii\caching\DbDependency' => YII2_PATH . '/caching/DbDependency.php',
  'yii\caching\Dependency' => YII2_PATH . '/caching/Dependency.php',
  'yii\caching\DummyCache' => YII2_PATH . '/caching/DummyCache.php',
  'yii\caching\ExpressionDependency' => YII2_PATH . '/caching/ExpressionDependency.php',
  'yii\caching\FileCache' => YII2_PATH . '/caching/FileCache.php',
  'yii\caching\FileDependency' => YII2_PATH . '/caching/FileDependency.php',
  'yii\caching\MemCache' => YII2_PATH . '/caching/MemCache.php',
  'yii\caching\MemCacheServer' => YII2_PATH . '/caching/MemCacheServer.php',
  'yii\caching\TagDependency' => YII2_PATH . '/caching/TagDependency.php',
  'yii\caching\WinCache' => YII2_PATH . '/caching/WinCache.php',
  'yii\caching\XCache' => YII2_PATH . '/caching/XCache.php',
  'yii\caching\ZendDataCache' => YII2_PATH . '/caching/ZendDataCache.php',
  'yii\captcha\Captcha' => YII2_PATH . '/captcha/Captcha.php',
  'yii\captcha\CaptchaAction' => YII2_PATH . '/captcha/CaptchaAction.php',
  'yii\captcha\CaptchaAsset' => YII2_PATH . '/captcha/CaptchaAsset.php',
  'yii\captcha\CaptchaValidator' => YII2_PATH . '/captcha/CaptchaValidator.php',
  'yii\data\ActiveDataProvider' => YII2_PATH . '/data/ActiveDataProvider.php',
  'yii\data\ArrayDataProvider' => YII2_PATH . '/data/ArrayDataProvider.php',
  'yii\data\BaseDataProvider' => YII2_PATH . '/data/BaseDataProvider.php',
  'yii\data\DataProviderInterface' => YII2_PATH . '/data/DataProviderInterface.php',
  'yii\data\Pagination' => YII2_PATH . '/data/Pagination.php',
  'yii\data\Sort' => YII2_PATH . '/data/Sort.php',
  'yii\data\SqlDataProvider' => YII2_PATH . '/data/SqlDataProvider.php',
  'yii\db\ActiveQuery' => YII2_PATH . '/db/ActiveQuery.php',
  'yii\db\ActiveQueryInterface' => YII2_PATH . '/db/ActiveQueryInterface.php',
  'yii\db\ActiveQueryTrait' => YII2_PATH . '/db/ActiveQueryTrait.php',
  'yii\db\ActiveRecord' => YII2_PATH . '/db/ActiveRecord.php',
  'yii\db\ActiveRecordInterface' => YII2_PATH . '/db/ActiveRecordInterface.php',
  'yii\db\ActiveRelationTrait' => YII2_PATH . '/db/ActiveRelationTrait.php',
  'yii\db\AfterSaveEvent' => YII2_PATH . '/db/AfterSaveEvent.php',
  'yii\db\BaseActiveRecord' => YII2_PATH . '/db/BaseActiveRecord.php',
  'yii\db\BatchQueryResult' => YII2_PATH . '/db/BatchQueryResult.php',
  'yii\db\ColumnSchema' => YII2_PATH . '/db/ColumnSchema.php',
  'yii\db\ColumnSchemaBuilder' => YII2_PATH . '/db/ColumnSchemaBuilder.php',
  'yii\db\Command' => YII2_PATH . '/db/Command.php',
  'yii\db\Connection' => YII2_PATH . '/db/Connection.php',
  'yii\db\DataReader' => YII2_PATH . '/db/DataReader.php',
  'yii\db\Exception' => YII2_PATH . '/db/Exception.php',
  'yii\db\Expression' => YII2_PATH . '/db/Expression.php',
  'yii\db\IntegrityException' => YII2_PATH . '/db/IntegrityException.php',
  'yii\db\Migration' => YII2_PATH . '/db/Migration.php',
  'yii\db\MigrationInterface' => YII2_PATH . '/db/MigrationInterface.php',
  'yii\db\Query' => YII2_PATH . '/db/Query.php',
  'yii\db\QueryBuilder' => YII2_PATH . '/db/QueryBuilder.php',
  'yii\db\QueryInterface' => YII2_PATH . '/db/QueryInterface.php',
  'yii\db\QueryTrait' => YII2_PATH . '/db/QueryTrait.php',
  'yii\db\Schema' => YII2_PATH . '/db/Schema.php',
  'yii\db\SchemaBuilderTrait' => YII2_PATH . '/db/SchemaBuilderTrait.php',
  'yii\db\StaleObjectException' => YII2_PATH . '/db/StaleObjectException.php',
  'yii\db\TableSchema' => YII2_PATH . '/db/TableSchema.php',
  'yii\db\Transaction' => YII2_PATH . '/db/Transaction.php',
  'yii\db\cubrid\ColumnSchemaBuilder' => YII2_PATH . '/db/cubrid/ColumnSchemaBuilder.php',
  'yii\db\cubrid\QueryBuilder' => YII2_PATH . '/db/cubrid/QueryBuilder.php',
  'yii\db\cubrid\Schema' => YII2_PATH . '/db/cubrid/Schema.php',
  'yii\db\mssql\PDO' => YII2_PATH . '/db/mssql/PDO.php',
  'yii\db\mssql\QueryBuilder' => YII2_PATH . '/db/mssql/QueryBuilder.php',
  'yii\db\mssql\Schema' => YII2_PATH . '/db/mssql/Schema.php',
  'yii\db\mssql\SqlsrvPDO' => YII2_PATH . '/db/mssql/SqlsrvPDO.php',
  'yii\db\mssql\TableSchema' => YII2_PATH . '/db/mssql/TableSchema.php',
  'yii\db\mysql\ColumnSchemaBuilder' => YII2_PATH . '/db/mysql/ColumnSchemaBuilder.php',
  'yii\db\mysql\QueryBuilder' => YII2_PATH . '/db/mysql/QueryBuilder.php',
  'yii\db\mysql\Schema' => YII2_PATH . '/db/mysql/Schema.php',
  'yii\db\oci\ColumnSchemaBuilder' => YII2_PATH . '/db/oci/ColumnSchemaBuilder.php',
  'yii\db\oci\QueryBuilder' => YII2_PATH . '/db/oci/QueryBuilder.php',
  'yii\db\oci\Schema' => YII2_PATH . '/db/oci/Schema.php',
  'yii\db\pgsql\QueryBuilder' => YII2_PATH . '/db/pgsql/QueryBuilder.php',
  'yii\db\pgsql\Schema' => YII2_PATH . '/db/pgsql/Schema.php',
  'yii\db\sqlite\ColumnSchemaBuilder' => YII2_PATH . '/db/sqlite/ColumnSchemaBuilder.php',
  'yii\db\sqlite\QueryBuilder' => YII2_PATH . '/db/sqlite/QueryBuilder.php',
  'yii\db\sqlite\Schema' => YII2_PATH . '/db/sqlite/Schema.php',
  'yii\di\Container' => YII2_PATH . '/di/Container.php',
  'yii\di\Instance' => YII2_PATH . '/di/Instance.php',
  'yii\di\NotInstantiableException' => YII2_PATH . '/di/NotInstantiableException.php',
  'yii\di\ServiceLocator' => YII2_PATH . '/di/ServiceLocator.php',
  'yii\filters\AccessControl' => YII2_PATH . '/filters/AccessControl.php',
  'yii\filters\AccessRule' => YII2_PATH . '/filters/AccessRule.php',
  'yii\filters\ContentNegotiator' => YII2_PATH . '/filters/ContentNegotiator.php',
  'yii\filters\Cors' => YII2_PATH . '/filters/Cors.php',
  'yii\filters\HostControl' => YII2_PATH . '/filters/HostControl.php',
  'yii\filters\HttpCache' => YII2_PATH . '/filters/HttpCache.php',
  'yii\filters\PageCache' => YII2_PATH . '/filters/PageCache.php',
  'yii\filters\RateLimitInterface' => YII2_PATH . '/filters/RateLimitInterface.php',
  'yii\filters\RateLimiter' => YII2_PATH . '/filters/RateLimiter.php',
  'yii\filters\VerbFilter' => YII2_PATH . '/filters/VerbFilter.php',
  'yii\filters\auth\AuthInterface' => YII2_PATH . '/filters/auth/AuthInterface.php',
  'yii\filters\auth\AuthMethod' => YII2_PATH . '/filters/auth/AuthMethod.php',
  'yii\filters\auth\CompositeAuth' => YII2_PATH . '/filters/auth/CompositeAuth.php',
  'yii\filters\auth\HttpBasicAuth' => YII2_PATH . '/filters/auth/HttpBasicAuth.php',
  'yii\filters\auth\HttpBearerAuth' => YII2_PATH . '/filters/auth/HttpBearerAuth.php',
  'yii\filters\auth\QueryParamAuth' => YII2_PATH . '/filters/auth/QueryParamAuth.php',
  'yii\grid\ActionColumn' => YII2_PATH . '/grid/ActionColumn.php',
  'yii\grid\CheckboxColumn' => YII2_PATH . '/grid/CheckboxColumn.php',
  'yii\grid\Column' => YII2_PATH . '/grid/Column.php',
  'yii\grid\DataColumn' => YII2_PATH . '/grid/DataColumn.php',
  'yii\grid\GridView' => YII2_PATH . '/grid/GridView.php',
  'yii\grid\GridViewAsset' => YII2_PATH . '/grid/GridViewAsset.php',
  'yii\grid\RadioButtonColumn' => YII2_PATH . '/grid/RadioButtonColumn.php',
  'yii\grid\SerialColumn' => YII2_PATH . '/grid/SerialColumn.php',
  'yii\helpers\ArrayHelper' => YII2_PATH . '/helpers/ArrayHelper.php',
  'yii\helpers\BaseArrayHelper' => YII2_PATH . '/helpers/BaseArrayHelper.php',
  'yii\helpers\BaseConsole' => YII2_PATH . '/helpers/BaseConsole.php',
  'yii\helpers\BaseFileHelper' => YII2_PATH . '/helpers/BaseFileHelper.php',
  'yii\helpers\BaseFormatConverter' => YII2_PATH . '/helpers/BaseFormatConverter.php',
  'yii\helpers\BaseHtml' => YII2_PATH . '/helpers/BaseHtml.php',
  'yii\helpers\BaseHtmlPurifier' => YII2_PATH . '/helpers/BaseHtmlPurifier.php',
  'yii\helpers\BaseInflector' => YII2_PATH . '/helpers/BaseInflector.php',
  'yii\helpers\BaseJson' => YII2_PATH . '/helpers/BaseJson.php',
  'yii\helpers\BaseMarkdown' => YII2_PATH . '/helpers/BaseMarkdown.php',
  'yii\helpers\BaseStringHelper' => YII2_PATH . '/helpers/BaseStringHelper.php',
  'yii\helpers\BaseUrl' => YII2_PATH . '/helpers/BaseUrl.php',
  'yii\helpers\BaseVarDumper' => YII2_PATH . '/helpers/BaseVarDumper.php',
  'yii\helpers\Console' => YII2_PATH . '/helpers/Console.php',
  'yii\helpers\FileHelper' => YII2_PATH . '/helpers/FileHelper.php',
  'yii\helpers\FormatConverter' => YII2_PATH . '/helpers/FormatConverter.php',
  'yii\helpers\Html' => YII2_PATH . '/helpers/Html.php',
  'yii\helpers\HtmlPurifier' => YII2_PATH . '/helpers/HtmlPurifier.php',
  'yii\helpers\Inflector' => YII2_PATH . '/helpers/Inflector.php',
  'yii\helpers\Json' => YII2_PATH . '/helpers/Json.php',
  'yii\helpers\Markdown' => YII2_PATH . '/helpers/Markdown.php',
  'yii\helpers\ReplaceArrayValue' => YII2_PATH . '/helpers/ReplaceArrayValue.php',
  'yii\helpers\StringHelper' => YII2_PATH . '/helpers/StringHelper.php',
  'yii\helpers\UnsetArrayValue' => YII2_PATH . '/helpers/UnsetArrayValue.php',
  'yii\helpers\Url' => YII2_PATH . '/helpers/Url.php',
  'yii\helpers\VarDumper' => YII2_PATH . '/helpers/VarDumper.php',
  'yii\i18n\DbMessageSource' => YII2_PATH . '/i18n/DbMessageSource.php',
  'yii\i18n\Formatter' => YII2_PATH . '/i18n/Formatter.php',
  'yii\i18n\GettextFile' => YII2_PATH . '/i18n/GettextFile.php',
  'yii\i18n\GettextMessageSource' => YII2_PATH . '/i18n/GettextMessageSource.php',
  'yii\i18n\GettextMoFile' => YII2_PATH . '/i18n/GettextMoFile.php',
  'yii\i18n\GettextPoFile' => YII2_PATH . '/i18n/GettextPoFile.php',
  'yii\i18n\I18N' => YII2_PATH . '/i18n/I18N.php',
  'yii\i18n\MessageFormatter' => YII2_PATH . '/i18n/MessageFormatter.php',
  'yii\i18n\MessageSource' => YII2_PATH . '/i18n/MessageSource.php',
  'yii\i18n\MissingTranslationEvent' => YII2_PATH . '/i18n/MissingTranslationEvent.php',
  'yii\i18n\PhpMessageSource' => YII2_PATH . '/i18n/PhpMessageSource.php',
  'yii\log\DbTarget' => YII2_PATH . '/log/DbTarget.php',
  'yii\log\Dispatcher' => YII2_PATH . '/log/Dispatcher.php',
  'yii\log\EmailTarget' => YII2_PATH . '/log/EmailTarget.php',
  'yii\log\FileTarget' => YII2_PATH . '/log/FileTarget.php',
  'yii\log\Logger' => YII2_PATH . '/log/Logger.php',
  'yii\log\SyslogTarget' => YII2_PATH . '/log/SyslogTarget.php',
  'yii\log\Target' => YII2_PATH . '/log/Target.php',
  'yii\mail\BaseMailer' => YII2_PATH . '/mail/BaseMailer.php',
  'yii\mail\BaseMessage' => YII2_PATH . '/mail/BaseMessage.php',
  'yii\mail\MailEvent' => YII2_PATH . '/mail/MailEvent.php',
  'yii\mail\MailerInterface' => YII2_PATH . '/mail/MailerInterface.php',
  'yii\mail\MessageInterface' => YII2_PATH . '/mail/MessageInterface.php',
  'yii\mutex\DbMutex' => YII2_PATH . '/mutex/DbMutex.php',
  'yii\mutex\FileMutex' => YII2_PATH . '/mutex/FileMutex.php',
  'yii\mutex\Mutex' => YII2_PATH . '/mutex/Mutex.php',
  'yii\mutex\MysqlMutex' => YII2_PATH . '/mutex/MysqlMutex.php',
  'yii\mutex\OracleMutex' => YII2_PATH . '/mutex/OracleMutex.php',
  'yii\mutex\PgsqlMutex' => YII2_PATH . '/mutex/PgsqlMutex.php',
  'yii\rbac\Assignment' => YII2_PATH . '/rbac/Assignment.php',
  'yii\rbac\BaseManager' => YII2_PATH . '/rbac/BaseManager.php',
  'yii\rbac\CheckAccessInterface' => YII2_PATH . '/rbac/CheckAccessInterface.php',
  'yii\rbac\DbManager' => YII2_PATH . '/rbac/DbManager.php',
  'yii\rbac\Item' => YII2_PATH . '/rbac/Item.php',
  'yii\rbac\ManagerInterface' => YII2_PATH . '/rbac/ManagerInterface.php',
  'yii\rbac\Permission' => YII2_PATH . '/rbac/Permission.php',
  'yii\rbac\PhpManager' => YII2_PATH . '/rbac/PhpManager.php',
  'yii\rbac\Role' => YII2_PATH . '/rbac/Role.php',
  'yii\rbac\Rule' => YII2_PATH . '/rbac/Rule.php',
  'yii\rest\Action' => YII2_PATH . '/rest/Action.php',
  'yii\rest\ActiveController' => YII2_PATH . '/rest/ActiveController.php',
  'yii\rest\Controller' => YII2_PATH . '/rest/Controller.php',
  'yii\rest\CreateAction' => YII2_PATH . '/rest/CreateAction.php',
  'yii\rest\DeleteAction' => YII2_PATH . '/rest/DeleteAction.php',
  'yii\rest\IndexAction' => YII2_PATH . '/rest/IndexAction.php',
  'yii\rest\OptionsAction' => YII2_PATH . '/rest/OptionsAction.php',
  'yii\rest\Serializer' => YII2_PATH . '/rest/Serializer.php',
  'yii\rest\UpdateAction' => YII2_PATH . '/rest/UpdateAction.php',
  'yii\rest\UrlRule' => YII2_PATH . '/rest/UrlRule.php',
  'yii\rest\ViewAction' => YII2_PATH . '/rest/ViewAction.php',
  'yii\test\ActiveFixture' => YII2_PATH . '/test/ActiveFixture.php',
  'yii\test\ArrayFixture' => YII2_PATH . '/test/ArrayFixture.php',
  'yii\test\BaseActiveFixture' => YII2_PATH . '/test/BaseActiveFixture.php',
  'yii\test\DbFixture' => YII2_PATH . '/test/DbFixture.php',
  'yii\test\Fixture' => YII2_PATH . '/test/Fixture.php',
  'yii\test\FixtureTrait' => YII2_PATH . '/test/FixtureTrait.php',
  'yii\test\InitDbFixture' => YII2_PATH . '/test/InitDbFixture.php',
  'yii\validators\BooleanValidator' => YII2_PATH . '/validators/BooleanValidator.php',
  'yii\validators\CompareValidator' => YII2_PATH . '/validators/CompareValidator.php',
  'yii\validators\DateValidator' => YII2_PATH . '/validators/DateValidator.php',
  'yii\validators\DefaultValueValidator' => YII2_PATH . '/validators/DefaultValueValidator.php',
  'yii\validators\EachValidator' => YII2_PATH . '/validators/EachValidator.php',
  'yii\validators\EmailValidator' => YII2_PATH . '/validators/EmailValidator.php',
  'yii\validators\ExistValidator' => YII2_PATH . '/validators/ExistValidator.php',
  'yii\validators\FileValidator' => YII2_PATH . '/validators/FileValidator.php',
  'yii\validators\FilterValidator' => YII2_PATH . '/validators/FilterValidator.php',
  'yii\validators\ImageValidator' => YII2_PATH . '/validators/ImageValidator.php',
  'yii\validators\InlineValidator' => YII2_PATH . '/validators/InlineValidator.php',
  'yii\validators\IpValidator' => YII2_PATH . '/validators/IpValidator.php',
  'yii\validators\NumberValidator' => YII2_PATH . '/validators/NumberValidator.php',
  'yii\validators\PunycodeAsset' => YII2_PATH . '/validators/PunycodeAsset.php',
  'yii\validators\RangeValidator' => YII2_PATH . '/validators/RangeValidator.php',
  'yii\validators\RegularExpressionValidator' => YII2_PATH . '/validators/RegularExpressionValidator.php',
  'yii\validators\RequiredValidator' => YII2_PATH . '/validators/RequiredValidator.php',
  'yii\validators\SafeValidator' => YII2_PATH . '/validators/SafeValidator.php',
  'yii\validators\StringValidator' => YII2_PATH . '/validators/StringValidator.php',
  'yii\validators\UniqueValidator' => YII2_PATH . '/validators/UniqueValidator.php',
  'yii\validators\UrlValidator' => YII2_PATH . '/validators/UrlValidator.php',
  'yii\validators\ValidationAsset' => YII2_PATH . '/validators/ValidationAsset.php',
  'yii\validators\Validator' => YII2_PATH . '/validators/Validator.php',
  'yii\web\Application' => YII2_PATH . '/web/Application.php',
  'yii\web\AssetBundle' => YII2_PATH . '/web/AssetBundle.php',
  'yii\web\AssetConverter' => YII2_PATH . '/web/AssetConverter.php',
  'yii\web\AssetConverterInterface' => YII2_PATH . '/web/AssetConverterInterface.php',
  'yii\web\AssetManager' => YII2_PATH . '/web/AssetManager.php',
  'yii\web\BadRequestHttpException' => YII2_PATH . '/web/BadRequestHttpException.php',
  'yii\web\CacheSession' => YII2_PATH . '/web/CacheSession.php',
  'yii\web\CompositeUrlRule' => YII2_PATH . '/web/CompositeUrlRule.php',
  'yii\web\ConflictHttpException' => YII2_PATH . '/web/ConflictHttpException.php',
  'yii\web\Controller' => YII2_PATH . '/web/Controller.php',
  'yii\web\Cookie' => YII2_PATH . '/web/Cookie.php',
  'yii\web\CookieCollection' => YII2_PATH . '/web/CookieCollection.php',
  'yii\web\DbSession' => YII2_PATH . '/web/DbSession.php',
  'yii\web\ErrorAction' => YII2_PATH . '/web/ErrorAction.php',
  'yii\web\ErrorHandler' => YII2_PATH . '/web/ErrorHandler.php',
  'yii\web\ForbiddenHttpException' => YII2_PATH . '/web/ForbiddenHttpException.php',
  'yii\web\GoneHttpException' => YII2_PATH . '/web/GoneHttpException.php',
  'yii\web\GroupUrlRule' => YII2_PATH . '/web/GroupUrlRule.php',
  'yii\web\HeaderCollection' => YII2_PATH . '/web/HeaderCollection.php',
  'yii\web\HtmlResponseFormatter' => YII2_PATH . '/web/HtmlResponseFormatter.php',
  'yii\web\HttpException' => YII2_PATH . '/web/HttpException.php',
  'yii\web\IdentityInterface' => YII2_PATH . '/web/IdentityInterface.php',
  'yii\web\JqueryAsset' => YII2_PATH . '/web/JqueryAsset.php',
  'yii\web\JsExpression' => YII2_PATH . '/web/JsExpression.php',
  'yii\web\JsonParser' => YII2_PATH . '/web/JsonParser.php',
  'yii\web\JsonResponseFormatter' => YII2_PATH . '/web/JsonResponseFormatter.php',
  'yii\web\Link' => YII2_PATH . '/web/Link.php',
  'yii\web\Linkable' => YII2_PATH . '/web/Linkable.php',
  'yii\web\MethodNotAllowedHttpException' => YII2_PATH . '/web/MethodNotAllowedHttpException.php',
  'yii\web\MultiFieldSession' => YII2_PATH . '/web/MultiFieldSession.php',
  'yii\web\MultipartFormDataParser' => YII2_PATH . '/web/MultipartFormDataParser.php',
  'yii\web\NotAcceptableHttpException' => YII2_PATH . '/web/NotAcceptableHttpException.php',
  'yii\web\NotFoundHttpException' => YII2_PATH . '/web/NotFoundHttpException.php',
  'yii\web\RangeNotSatisfiableHttpException' => YII2_PATH . '/web/RangeNotSatisfiableHttpException.php',
  'yii\web\Request' => YII2_PATH . '/web/Request.php',
  'yii\web\RequestParserInterface' => YII2_PATH . '/web/RequestParserInterface.php',
  'yii\web\Response' => YII2_PATH . '/web/Response.php',
  'yii\web\ResponseFormatterInterface' => YII2_PATH . '/web/ResponseFormatterInterface.php',
  'yii\web\ServerErrorHttpException' => YII2_PATH . '/web/ServerErrorHttpException.php',
  'yii\web\Session' => YII2_PATH . '/web/Session.php',
  'yii\web\SessionIterator' => YII2_PATH . '/web/SessionIterator.php',
  'yii\web\TooManyRequestsHttpException' => YII2_PATH . '/web/TooManyRequestsHttpException.php',
  'yii\web\UnauthorizedHttpException' => YII2_PATH . '/web/UnauthorizedHttpException.php',
  'yii\web\UnprocessableEntityHttpException' => YII2_PATH . '/web/UnprocessableEntityHttpException.php',
  'yii\web\UnsupportedMediaTypeHttpException' => YII2_PATH . '/web/UnsupportedMediaTypeHttpException.php',
  'yii\web\UploadedFile' => YII2_PATH . '/web/UploadedFile.php',
  'yii\web\UrlManager' => YII2_PATH . '/web/UrlManager.php',
  'yii\web\UrlNormalizer' => YII2_PATH . '/web/UrlNormalizer.php',
  'yii\web\UrlNormalizerRedirectException' => YII2_PATH . '/web/UrlNormalizerRedirectException.php',
  'yii\web\UrlRule' => YII2_PATH . '/web/UrlRule.php',
  'yii\web\UrlRuleInterface' => YII2_PATH . '/web/UrlRuleInterface.php',
  'yii\web\User' => YII2_PATH . '/web/User.php',
  'yii\web\UserEvent' => YII2_PATH . '/web/UserEvent.php',
  'yii\web\View' => YII2_PATH . '/web/View.php',
  'yii\web\ViewAction' => YII2_PATH . '/web/ViewAction.php',
  'yii\web\XmlResponseFormatter' => YII2_PATH . '/web/XmlResponseFormatter.php',
  'yii\web\YiiAsset' => YII2_PATH . '/web/YiiAsset.php',
  'yii\widgets\ActiveField' => YII2_PATH . '/widgets/ActiveField.php',
  'yii\widgets\ActiveForm' => YII2_PATH . '/widgets/ActiveForm.php',
  'yii\widgets\ActiveFormAsset' => YII2_PATH . '/widgets/ActiveFormAsset.php',
  'yii\widgets\BaseListView' => YII2_PATH . '/widgets/BaseListView.php',
  'yii\widgets\Block' => YII2_PATH . '/widgets/Block.php',
  'yii\widgets\Breadcrumbs' => YII2_PATH . '/widgets/Breadcrumbs.php',
  'yii\widgets\ContentDecorator' => YII2_PATH . '/widgets/ContentDecorator.php',
  'yii\widgets\DetailView' => YII2_PATH . '/widgets/DetailView.php',
  'yii\widgets\FragmentCache' => YII2_PATH . '/widgets/FragmentCache.php',
  'yii\widgets\InputWidget' => YII2_PATH . '/widgets/InputWidget.php',
  'yii\widgets\LinkPager' => YII2_PATH . '/widgets/LinkPager.php',
  'yii\widgets\LinkSorter' => YII2_PATH . '/widgets/LinkSorter.php',
  'yii\widgets\ListView' => YII2_PATH . '/widgets/ListView.php',
  'yii\widgets\MaskedInput' => YII2_PATH . '/widgets/MaskedInput.php',
  'yii\widgets\MaskedInputAsset' => YII2_PATH . '/widgets/MaskedInputAsset.php',
  'yii\widgets\Menu' => YII2_PATH . '/widgets/Menu.php',
  'yii\widgets\Pjax' => YII2_PATH . '/widgets/Pjax.php',
  'yii\widgets\PjaxAsset' => YII2_PATH . '/widgets/PjaxAsset.php',
  'yii\widgets\Spaceless' => YII2_PATH . '/widgets/Spaceless.php',
];

这个映射表以类名为键,以实际类文件为值,Yii所有的核心类都已经写入到这个 classes.php 文件中, 所以,核心类的加载是最便捷,最快的。

现在,来看看这个关键先生 BaseYii::autoload()

public static function autoload($className)
{
    if (isset(static::$classMap[$className])) {
        $classFile = static::$classMap[$className];
        if ($classFile[0] === '@') {
            $classFile = static::getAlias($classFile);
        }
    } elseif (strpos($className, '\\') !== false) {
        $classFile = static::getAlias('@' . str_replace('\\', '/', $className) . '.php', 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 '$className' 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() 将类文件加载进来,实现类的加载。

路径别名

我们再看一下getAlias()解析路径别名、setAlias()设置路径别名 :

public static function getAlias($alias, $throwException = true)
{
    if (strncmp($alias, '@', 1)) {
        // not an alias
        return $alias;
    }

    $pos = strpos($alias, '/');
    $root = $pos === false ? $alias : substr($alias, 0, $pos);

    if (isset(static::$aliases[$root])) {
        if (is_string(static::$aliases[$root])) {
            return $pos === false ? static::$aliases[$root] : static::$aliases[$root] . substr($alias, $pos);
        }

        foreach (static::$aliases[$root] as $name => $path) {
            if (strpos($alias . '/', $name . '/') === 0) {
                return $path . substr($alias, strlen($name));
            }
        }
    }

    if ($throwException) {
        throw new InvalidParamException("Invalid path alias: $alias");
    }

    return false;
}

public static function setAlias($alias, $path)
{
    if (strncmp($alias, '@', 1)) {
        $alias = '@' . $alias;
    }
    $pos = strpos($alias, '/');
    $root = $pos === false ? $alias : substr($alias, 0, $pos);
    if ($path !== null) {
        $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path);
        if (!isset(static::$aliases[$root])) {
            if ($pos === false) {
                static::$aliases[$root] = $path;
            } else {
                static::$aliases[$root] = [$alias => $path];
            }
        } elseif (is_string(static::$aliases[$root])) {
            if ($pos === false) {
                static::$aliases[$root] = $path;
            } else {
                static::$aliases[$root] = [
                    $alias => $path,
                    $root => static::$aliases[$root],
                ];
            }
        } else {
            static::$aliases[$root][$alias] = $path;
            krsort(static::$aliases[$root]);
        }
    } elseif (isset(static::$aliases[$root])) {
        if (is_array(static::$aliases[$root])) {
            unset(static::$aliases[$root][$alias]);
        } elseif ($pos === false) {
            unset(static::$aliases[$root]);
        }
    }
}

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

看到现在还是没找到common文件夹中文件是怎么载入的啊!那我们再看一下下面这个文件的内容:

project/common/config/bootstrap.php 内容

<?php
Yii::setAlias('@common', dirname(__DIR__));
Yii::setAlias('@admin', dirname(dirname(__DIR__)) . '/admin');
Yii::setAlias('@console', dirname(dirname(__DIR__)) . '/console');

有没有找到感觉。

这里只是知道了Yii2自动加载一部分的内容,但是上面并没有找到@common所在文件被加载的地方, 也没有找到我们经常使用的 Yii::属性名 相关的依据,不过可以推测有一个@yii的参数定义, 应该是框架初始化加载的时候Yii::setAlias()的,这部分内容放到别名笔记中分析。

vendor自动加载分析

这部分可以参阅 https://ibaiyang.github.io/blog/composer/2023/04/07/Composer-原理.html

vendor目录是composer引入的第三方库文件所在根位置。

在入口脚本中,除了Yii自己的autoloader,还有一个第三方的autoloader:

// 这个是第三方的autoloader
require __DIR__ . '/../../vendor/autoload.php';  

我们看一下/vendor/autoload.php这个文件的内容:

<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInitf9c5993908f68dcde48f483fcf1c6e6d::getLoader();

这个文件是Composer生成的,每次我们用Composer进行install/update时都自动重新生成, 是为了引入ComposerAutoloaderInitf9c5993908f68dcde48f483fcf1c6e6d这个随机类。 

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

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

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

至于Composer如何自动加载类文件,这里就不过多的占用篇幅了。可以看看 Composer的文档 。

再看一下/vendor/composer/autoload_real.php的文件内容:

<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInitf9c5993908f68dcde48f483fcf1c6e6d
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        // 如果传入的类名是Composer\Autoload\ClassLoader,则加载当前相同目录下的ClassLoader.php
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        // spl_autoload_register注册类文件自动加载函数
        spl_autoload_register(array('ComposerAutoloaderInitf9c5993908f68dcde48f483fcf1c6e6d', 'loadClassLoader'), true, true);
        // self::$loader $loader 赋值
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        // spl_autoload_unregister取消类文件自动加载函数
        spl_autoload_unregister(array('ComposerAutoloaderInitf9c5993908f68dcde48f483fcf1c6e6d', 'loadClassLoader'));

        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
        if ($useStaticLoader) {
            require_once __DIR__ . '/autoload_static.php';

            call_user_func(\Composer\Autoload\ComposerStaticInitf9c5993908f68dcde48f483fcf1c6e6d::getInitializer($loader));
        } else {
            $map = require __DIR__ . '/autoload_namespaces.php';
            foreach ($map as $namespace => $path) {
                $loader->set($namespace, $path);
            }

            $map = require __DIR__ . '/autoload_psr4.php';
            foreach ($map as $namespace => $path) {
                $loader->setPsr4($namespace, $path);
            }

            $classMap = require __DIR__ . '/autoload_classmap.php';
            if ($classMap) {
                $loader->addClassMap($classMap);
            }
        }

        // spl_autoload_register再次注册类文件自动加载函数
        $loader->register(true);

        if ($useStaticLoader) {
            $includeFiles = Composer\Autoload\ComposerStaticInitf9c5993908f68dcde48f483fcf1c6e6d::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequiref9c5993908f68dcde48f483fcf1c6e6d($fileIdentifier, $file);
        }

        return $loader;
    }
}

function composerRequiref9c5993908f68dcde48f483fcf1c6e6d($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}

可以看到上面的随机类名就在这里。这里点到为止,就不继续分析了,是Composer/PSR4支持的内容。

/vendor/composer/ClassLoader.php 文件内容

<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    https://www.php-fig.org/psr/psr-0/
 * @see    https://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    /** @var ?string */
    private $vendorDir;

    // PSR-4
    /**
     * @var array[]
     * @psalm-var array<string, array<string, int>>
     */
    private $prefixLengthsPsr4 = array();
    /**
     * @var array[]
     * @psalm-var array<string, array<int, string>>
     */
    private $prefixDirsPsr4 = array();
    /**
     * @var array[]
     * @psalm-var array<string, string>
     */
    private $fallbackDirsPsr4 = array();

    // PSR-0
    /**
     * @var array[]
     * @psalm-var array<string, array<string, string[]>>
     */
    private $prefixesPsr0 = array();
    /**
     * @var array[]
     * @psalm-var array<string, string>
     */
    private $fallbackDirsPsr0 = array();

    /** @var bool */
    private $useIncludePath = false;

    /**
     * @var string[]
     * @psalm-var array<string, string>
     */
    private $classMap = array();

    /** @var bool */
    private $classMapAuthoritative = false;

    /**
     * @var bool[]
     * @psalm-var array<string, bool>
     */
    private $missingClasses = array();

    /** @var ?string */
    private $apcuPrefix;

    /**
     * @var self[]
     */
    private static $registeredLoaders = array();

    /**
     * @param ?string $vendorDir
     */
    public function __construct($vendorDir = null)
    {
        $this->vendorDir = $vendorDir;
    }

    /**
     * @return string[]
     */
    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
        }

        return array();
    }

    /**
     * @return array[]
     * @psalm-return array<string, array<int, string>>
     */
    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    /**
     * @return array[]
     * @psalm-return array<string, string>
     */
    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    /**
     * @return array[]
     * @psalm-return array<string, string>
     */
    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    /**
     * @return string[] Array of classname => path
     * @psalm-return array<string, string>
     */
    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param string[] $classMap Class to filename map
     * @psalm-param array<string, string> $classMap
     *
     * @return void
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix, either
     * appending or prepending to the ones previously set for this prefix.
     *
     * @param string          $prefix  The prefix
     * @param string[]|string $paths   The PSR-0 root directories
     * @param bool            $prepend Whether to prepend the directories
     *
     * @return void
     */
    public function add($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr0 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr0
                );
            } else {
                $this->fallbackDirsPsr0 = array_merge(
                    $this->fallbackDirsPsr0,
                    (array) $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
            $this->prefixesPsr0[$first][$prefix] = (array) $paths;

            return;
        }
        if ($prepend) {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                (array) $paths,
                $this->prefixesPsr0[$first][$prefix]
            );
        } else {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $this->prefixesPsr0[$first][$prefix],
                (array) $paths
            );
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string          $prefix  The prefix/namespace, with trailing '\\'
     * @param string[]|string $paths   The PSR-4 base directories
     * @param bool            $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function addPsr4($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr4
                );
            } else {
                $this->fallbackDirsPsr4 = array_merge(
                    $this->fallbackDirsPsr4,
                    (array) $paths
                );
            }
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                (array) $paths,
                $this->prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $this->prefixDirsPsr4[$prefix],
                (array) $paths
            );
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string          $prefix The prefix
     * @param string[]|string $paths  The PSR-0 base directories
     *
     * @return void
     */
    public function set($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string          $prefix The prefix/namespace, with trailing '\\'
     * @param string[]|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

    /**
     * Turns on searching the include path for class files.
     *
     * @param bool $useIncludePath
     *
     * @return void
     */
    public function setUseIncludePath($useIncludePath)
    {
        $this->useIncludePath = $useIncludePath;
    }

    /**
     * Can be used to check if the autoloader uses the include path to check
     * for classes.
     *
     * @return bool
     */
    public function getUseIncludePath()
    {
        return $this->useIncludePath;
    }

    /**
     * Turns off searching the prefix and fallback directories for classes
     * that have not been registered with the class map.
     *
     * @param bool $classMapAuthoritative
     *
     * @return void
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Should class lookup fail if not found in the current class map?
     *
     * @return bool
     */
    public function isClassMapAuthoritative()
    {
        return $this->classMapAuthoritative;
    }

    /**
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
     *
     * @param string|null $apcuPrefix
     *
     * @return void
     */
    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
    }

    /**
     * The APCu prefix in use, or null if APCu caching is not enabled.
     *
     * @return string|null
     */
    public function getApcuPrefix()
    {
        return $this->apcuPrefix;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     *
     * @return void
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);

        if (null === $this->vendorDir) {
            return;
        }

        if ($prepend) {
            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
        } else {
            unset(self::$registeredLoaders[$this->vendorDir]);
            self::$registeredLoaders[$this->vendorDir] = $this;
        }
    }

    /**
     * Unregisters this instance as an autoloader.
     *
     * @return void
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));

        if (null !== $this->vendorDir) {
            unset(self::$registeredLoaders[$this->vendorDir]);
        }
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return true|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            includeFile($file);

            return true;
        }

        return null;
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    /**
     * Returns the currently registered loaders indexed by their corresponding vendor directories.
     *
     * @return self[]
     */
    public static function getRegisteredLoaders()
    {
        return self::$registeredLoaders;
    }

    /**
     * @param  string       $class
     * @param  string       $ext
     * @return string|false
     */
    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            $subPath = $class;
            while (false !== $lastPos = strrpos($subPath, '\\')) {
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath . '\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        if (file_exists($file = $dir . $pathEnd)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }

        return false;
    }
}

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 *
 * @param  string $file
 * @return void
 * @private
 */
function includeFile($file)
{
    include $file;
}

其他

include 和 require 语句用于在执行流中插入写在其他文件中的有用的代码。

include 和 require 除了处理错误的方式不同之外,在其他方面都是相同的:

require 生成一个致命错误(E_COMPILE_ERROR),在错误发生后脚本会停止执行。 include 生成一个警告(E_WARNING),在错误发生后脚本会继续执行。

因此,如果您希望继续执行,并向用户输出结果,即使包含文件已丢失, 那么请使用 include。否则,在框架、CMS 或者复杂的 PHP 应用程序编程中, 请始终使用 require 向执行流引用关键文件。 这有助于提高应用程序的安全性和完整性,在某个关键文件意外丢失的情况下。

提示:

require 一般放在 PHP 文件的最前面,程序在执行前就会先导入要引用的文件;
include 一般放在程序的流程控制中,当程序执行时碰到才会引用,简化程序的执行流程。
require 引入的文件有错误时,执行会中断,并返回一个致命错误;
include 引入的文件有错误时,会继续执行,并返回一个警告。






参考资料

深入理解Yii2.0 » Yii 约定 » Yii的类自动加载机制 http://www.digpage.com/autoload.html

Yii的类自动加载机制 https://www.kancloud.cn/kancloud/yii-in-depth/50795

PHP spl_autoload_register()相关 https://ibaiyang.github.io/blog/php/2019/04/01/PHP-spl_autoload_register()%E7%9B%B8%E5%85%B3.html

include 和 require 语句 http://www.runoob.com/php/php-includes.html

C 库函数 - strncmp() https://www.runoob.com/cprogramming/c-function-strncmp.html


返回