PHP 的 autoload 机制,能够在应用一个未导入的类时动静加载该类,从而实现提早加载和治理依赖类文件的目标。
一、没有 composer 时 PHP 是怎么做的
__autoload 主动加载器
PHP 中想要应用一个类,必须通过 require
(指代 require_once, include_once 等) 的形式在文件结尾申明要应用的类。当我的项目中类较多时,一个个申明加载显然不可行。
在 PHP5 版本,PHP 反对通过 __autoload
定义一个主动加载器,尝试加载未定义的类。 如:
// we've writen this code where we need
function __autoload($classname) {
$filename = "./". $classname .".php";
include_once($filename);
}
// we've called a class ***
$obj = new myClass();
但 __autoload
函数毛病比拟显著:他只能定义一次,这样就会耦合所有依赖的类的主动加载逻辑,通通写到这个办法里,这时候就须要用到 spl_autoload_register
函数了。
应用 spl_autoload_register 注册多个主动加载器
spl 是 standard php library 的缩写。spl_autoload_register
最大的特点是反对注册多个主动加载器,这样就能实现将各个类库的主动加载逻辑离开,本人解决本人的加载逻辑。
function my_autoloader($class) {
var_dump("my_autoloader", $class);
}
spl_autoload_register('my_autoloader');
// 静态方法
class MyClass1 {
public static function autoload($className) {
var_dump("MyClass1 autoload", $className);
}
}
spl_autoload_register(array('MyClass1', 'autoload'));
// 非静态方法
class MyClass2 {
public function autoload($className) {
var_dump("MyClass2 autoload", $className);
}
}
$instance = new MyClass2();
spl_autoload_register(array($instance, 'autoload'));
new \NotDefineClassName();
/*
输入
string(32) "my_autoloader NotDefineClassName"
string(36) "MyClass1 autoload NotDefineClassName"
string(36) "MyClass2 autoload NotDefineClassName"
*/
二、PSR 标准
PSR 即 PHP Standards Recommendation 是一个社区组织:https://www.php-fig.org/psr/,申明一系列标准来对立开发格调,缩小互不兼容的困扰。标准中的 PSR-4 代表:Autoloading Standard,即主动加载标准。
PSR-4
其中规定:一个类的残缺类名应该遵循一下标准:
\<命名空间>(\<子命名空间>)*\<类名>
,即:
- 残缺的类名必须要有一个顶级命名空间,被称为 “vendor namespace”;
- 残缺的类名能够有一个或多个子命名空间;
- 残缺的类名必须有一个最终的类名;
- 残缺的类名中任意一部分中的下滑线都是没有非凡含意的;
- 残缺的类名能够由任意大小写字母组成;
- 所有类名都必须是大小写敏感的。
看看例子:
利用的成果简略来说就是:将命名空间前缀 Namespace Prefix 替换成 Base Directory 目录,并将 \ 替换成 / 。一句话,命名空间能够表明类具体的寄存地位。
三、Composer 主动加载的过程
联合 spl_auto_register
和 PSR-4 的命名空间标准,能够设想,咱们能够通过类的命名空间,来找到具体类的寄存地位,而后通过 require 将其加载进来失效,composer 就是这么干的。
接下来咱们分两步看 composer 是怎么做的。
第一步,建设类的命名空间和类寄存地位的映射关系
首先看 vendor 目录下的 autoload.php 文件,所有我的项目启动必然要先 require 这个文件。
// autoload.php @generated by Composer
// vendor/autoload.php
require_once __DIR__ . '/composer/autoload_real.php';
// 返回了autoload_real文件中的类办法
return ComposerAutoloaderInit7e421c277f7e8f810a19524f0d771cdb::getLoader();
/* ------------- */
// vendor/composer/autoload_real.php
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
// P0 初始化ClassLoader
spl_autoload_register(array('ComposerAutoloaderInit7e421c277f7e8f810a19524f0d771cdb', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit7e421c277f7e8f810a19524f0d771cdb', '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';
// P1 向ClassLoader中set命名空间和文件门路映射关系
call_user_func(\Composer\Autoload\ComposerStaticInit7e421c277f7e8f810a19524f0d771cdb::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);
}
}
// P2 将ClassLoader中的loadClass办法,注册为加载器
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit7e421c277f7e8f810a19524f0d771cdb::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire7e421c277f7e8f810a19524f0d771cdb($fileIdentifier, $file);
}
return $loader;
}
在代码 P0 处,上来先实例化一个 \Composer\Autoload\ClassLoader
类,这个类外面保护了所有命名空间到类具体寄存地位的映射关系。
接下来在 P1 处,依据 PHP 版本和运行环境,如是否运行在 HHVM 环境下,来辨别如何向 ClassLoader 中载入映射关系。
autoload_static.php 文件中定义的映射关系有三种:
public static $prefixLengthsPsr4 = array (
'p' =>
array (
'phpDocumentor\\Reflection\\' => 25,
),
'W' =>
array (
'Webmozart\\Assert\\' => 17,
),
'S' =>
array (
'Symfony\\Polyfill\\Ctype\\' => 23,
),
'R' =>
array (
'RefactoringGuru\\' => 16,
),
'P' =>
array (
'Prophecy\\' => 9,
),
'D' =>
array (
'Doctrine\\Instantiator\\' => 22,
'DeepCopy\\' => 9,
),
);
public static $prefixDirsPsr4 = array (
'phpDocumentor\\Reflection\\' =>
array (
0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
1 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',
2 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',
),
'Webmozart\\Assert\\' =>
array (
0 => __DIR__ . '/..' . '/webmozart/assert/src',
),
'Symfony\\Polyfill\\Ctype\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
),
'RefactoringGuru\\' =>
array (
0 => __DIR__ . '/../..' . '/',
),
'Prophecy\\' =>
array (
0 => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy',
),
'Doctrine\\Instantiator\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator',
),
'DeepCopy\\' =>
array (
0 => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy',
),
);
public static $classMap = array (
'File_Iterator' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Iterator.php',
'File_Iterator_Facade' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Facade.php',
'File_Iterator_Factory' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Factory.php',
'PHPUnit\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Exception.php',
...
);
classMap 是残缺映射关系,prefixLengthsPsr4 和 prefixDirsPsr4 是当通过残缺命名空间找不到时,通过在指标类名后加上 .php 再次寻找用。
到此,建设命名空间到类寄存门路的关系曾经实现了。
第二步,如何找到类并加载
在下面代码中,将 ClassLoader 的 loadClass 办法注册成加载器:
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
function includeFile($file)
{
include $file;
}
其中 findFile 办法,就是通过类名,去寻找文件理论的地位,如果找到了,就通过 includeFile 将文件加载进来。次要看看 findFile 中的逻辑:
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;
}
对于类的加载非常简略,间接去 classmap 中取。如果取不到,则将指标类名追加 .php 后缀,去$prefixLengthsPsr4 和 $prefixDirsPsr4 中查找。
第三步,如何加载全局函数
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit7e421c277f7e8f810a19524f0d771cdb::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire7e421c277f7e8f810a19524f0d771cdb($fileIdentifier, $file);
}
return $loader;
还是通过 autoload_static.php 中定义的数据去加载:
// autoload_static.php
public static $files = array (
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'6124b4c8570aa390c21fafd04a26c69f' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
);
// vendor/symfony/polyfill-ctype/bootstrap.php
if (!function_exists('ctype_alnum')) {
function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); }
}
if (!function_exists('ctype_alpha')) {
function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); }
}
至此 composer 主动加载的逻辑根本就过了一遍。
四、composer 的 ClassLoader 中的 classMap 是怎么生成进去的?
答案就在 composer 的源码中:https://github.com/composer/composer/blob/d0aac44ed210e13ec4a4370908a5b36553a2f16c/src/Composer/Autoload/AutoloadGenerator.php
扫描所有包中的类,而后生成一个 php 文件,例如:getStaticFile 办法
参考:
https://segmentfault.com/a/1190000014948542
PHP官网对类主动加载的阐明:https://www.php.net/manual/zh/language.oop5.autoload.php
本文由mdnice多平台公布
发表回复