tp源码分析-composer加载

之前写过一篇文章是关于composer 的基本用法,这篇文章是composer源码的简单阅读和thinkphp5.1的自动加载。

先来熟悉一个函数,get_declared_classes, 这个函数是干嘛的?是获取当前脚本中已经加载的类的,但凡这个返回结果中有的类,我们都可以直接生成,默认在一个空脚本中

1
2
<?php
var_dump(get_declared_classes());

是当前php中包含的所有扩展类,那如果没有包含在这其中的类我们应该怎么生成实例对象呢?远古时代是通过 require_once,想想添加每个新脚本的时候都要在上面写个require, 这样太不智能了,php给我们提供了一个函数spl_autoload_register, 通过不断的调用这个函数,可以绑定自定义的函数到一个队列中,如果我们生的对象的类在内存中找不到,他就会执行这个队列中的所有自定义方法,直到找到为止, composer 本质上也是基于这个函数。

1
2
3
4
5
6
来自composer ClassLoader.php
<?php
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}

当我们在github上安装一个包(或者叫库), 我们先composer require, 安装完了之后再在我们的脚本中加上 vendor/autoload.php, 然后就能用了,注意一定要require_once vendor/autoload.php 这个文件,否则你只是下载了包,当生成对象的时候,内存中还是没有这个类的存在(以klein举例)

那我们开看一下这个autoload .php

1
2
3
4
5
6
7
<?php

// autoload.php @generated by Composer

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

return ComposerAutoloaderInit23a88103aab8adc5d1263de38cceb378::getLoader();

调用的是composer 包中autoload_real 中的getLoader 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}

spl_autoload_register(array('ComposerAutoloaderInit23a88103aab8adc5d1263de38cceb378', 'loadClassLoader'), true, true); // 这个为了能让生成classLoader 的实例对象
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit23a88103aab8adc5d1263de38cceb378', 'loadClassLoader'));

$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); // true
if ($useStaticLoader) { // 走的这个逻辑
require_once __DIR__ . '/autoload_static.php'; // 包含这个文件是因为要调用他的getInitialize()方法,也就是这个方法

call_user_func(\Composer\Autoload\ComposerStaticInit23a88103aab8adc5d1263de38cceb378::getInitializer($loader)); // 这个方法的作用就是让一个闭包复制给ClassLoad类(核心类)上,让这个闭包可以访问classLoad 的私有变量 prefixLengthsPsr4, prefixDirsPsr4, classMap (psr0 过时了,不看)。classMap 文件就是composer.json 中classMap, 他会把一个文件夹中的文件中包含的类和文件名对应出来,自动加载的时候通过类名作为key, 去寻找这个文件的位置。想laravel thinkphp 生成类和文件的对应关系,也是基于这个原理
// 说说prefixlengthsPsr4 还有prefixDirsPsr4 吧,当我们在composer.json 中自定义composer autoload psr4 ,就是就是往这里面写对应关系,首先去类的第一个字母在prefixlengths 中寻找,找到之后再去prefixDirsPsr4中找具体的命名空间对应的文件夹,然后通过把命名空间依据psr4转换成文件名称,在之前找到的文件夹下面寻找这个文件,具体的验证方法可以看classLoad的 loadClass,也就是spl_autoload_register 中注册的
} 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);
}
}

$loader->register(true); //注册ClassLoader 的 loadClass 方法作为最终的spl_autoload 方法

if ($useStaticLoader) { // true ,走的这个逻辑
$includeFiles = Composer\Autoload\ComposerStaticInit23a88103aab8adc5d1263de38cceb378::$files; // autoload_static 中的文件,这个文件咋来的,composer.json 中的autload files 选项

} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire23a88103aab8adc5d1263de38cceb378($fileIdentifier, $file); //这个方法就是包含上面的文件,所以 composer.json 中的files 是我们包含了vendor 目录下的autload.php就直接包含进来了,不是后期通过spl_autoload_register 中的方法生成的。为了验证我们可以在这些files 中定义class, 然后不new生成对象,也就没有触发spl_autoload_register中方法,看内存中有没有这个类
}

return $loader;
}

好了,我们来总结一下composer 文件夹中的几个比较重要的文件,autoload_real.php 是注册Classload .php 成为spl_autoload_register中的方法,autoload_static.php类似一个配置文件,把他包含的类对应文件夹的属性都绑定到ClassLoad 上,ClassLoad 调用自己的loadClass 方法,根据类名,去自己的属性上找对应的类所在文件位置。

现在抛出问题,我们如何不通过composer require去安装一个库文件?

其实很简单,composer require 做了两个工作,首先git clone 那个库到你本地vendor目录,再接着配置autoload_static.php 文件中的两个属性,prefixLengthsPsr4, prefixDirsPsr4,第一个属性是命名空间的首字母对应命名空间,第二个是命名空间对应类的文件位置,配置好了之后,我们就可以试着new class了,看会不会报错,一般情况下不会,除非这个类库中除了psr4 还有classMap 或者files 或者psr0,这样我们一点点的添加,就能完成。

接着我们看看thinkphp5.1 的自动加载机制,thinkphp的自动加载其实就是composer的翻版,他为了兼容composer, composer 中的文件没有动,只是他没有require vendor 下面的autoload.php, 转而是通过think 库 base.php ->load.php,这个load.php 作用类似composer 的autoload_real 和 CLassLoader,

load.php 中的register 方法通过获取composer 的autload_static 中的prefixLengthsPsr4’, ‘prefixDirsPsr4 属性,绑定到自身,获取了composer 中通过psr4 加载的类,顺便把自己的think 和 trait也加入其中.

对于classMap, 他获取的自己runtime 文件夹下的classMap.php 文件中的内容,该内容需要调用命令生成,就是thinkphp 自身的类和对应文件位置的集合。

最后是自动加载目录,他的功能类似psr4,就是把自动加载的目录当做根目录,类文件按照psr4,由这个相对根目录展开。

thinkphp 还有个classAlias 功能,classAlias 利用的是class_alias 这个方法,让两个类一模一样,除了类名,这样可以简写我们的类名长度,注意使用的时候一定要加上根命名空间\,否则可能会在当前命名空间下寻找改类,这个在laravel中用的也很多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 注册类库别名
Loader::addClassAlias([
'App' => facade\App::class,
'Build' => facade\Build::class,
'Cache' => facade\Cache::class,
'Config' => facade\Config::class,
'Cookie' => facade\Cookie::class,
'Db' => Db::class,
'Debug' => facade\Debug::class,
'Env' => facade\Env::class,
'Facade' => Facade::class,
'Hook' => facade\Hook::class,
'Lang' => facade\Lang::class,
'Log' => facade\Log::class,
'Request' => facade\Request::class,
'Response' => facade\Response::class,
'Route' => facade\Route::class,
'Session' => facade\Session::class,
'Url' => facade\Url::class,
'Validate' => facade\Validate::class,
'View' => facade\View::class,
]);

好了,大致就这么多了,所以我们可以猜测一波(没有仔细看啦)

composer dump-autoload,主要是因为我们修改了composer.json文件中的autoload.psr4选项,没有及时修改autload_static 文件,

composer require 1. git clone 2.修改autoload_static 文件

实战:如何用命令行的方式运行tp,测试tp中的函数

首先因为tp重写了composer 的加载方式(但没有修改composer 的文件, 为了方便composer update, install ), 所以我们用不了vendor 中的_autoload.php, tp起到相同作用的是 Loader::register 方法,这个方法中 对app 文件夹,和 think\composer 进行了psr4 加载,同时把thinkphp 核心文件夹library 下的think 和traits 也进行psr4 进行加载,这样我们就能访问app , think\composer, thinkphp\libraray\think和thinkphp\libraray\traits 下的所有文件了。同时他还通过loader 的addAutoLoadDir 把extend 文件夹做成classmap 进行加载,所以为了测试我建立了cy文件夹,也需要Load::addAutoLoadDir(realpath(‘cy’))。 当然如果我们还可能为了方便使用系统的一些类,需要class_alias。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
/**
* Created by PhpStorm.
* User: cy
* Date: 2019/8/4
* Time: 13:08
*/

//require_once '../vendor/autoload.php';
require_once '../thinkphp/library/think/Loader.php'; // 为了使用Load中的方法
require_once '../thinkphp/helper.php'; // 为了使用助手函数

$nowDir = realpath(__DIR__); //获取当前文件夹的绝对位置,方便函数调用

//var_dump($nowDir);

\think\Loader::register(); // 把composer 的自动加载接管过来;
\think\Loader::addAutoLoadDir(realpath(__DIR__)); // 加载当前脚本下的类进入psr4

// 注册类库别名,方便直接调用,比如 \Config
\think\Loader::addClassAlias([
'Config' => \think\facade\Config::class,
'Env' => \think\facade\Env::class

]);

其实为什么要遵循ps4规范,就是为了能够通过命名空间方便查找文件的具体位置,然后require, 当我们主动require 了文件(比如文件数量过少的情况),我们的命名空间可以随便定义的,并没有任何硬性规定一定要和文件夹名称相同,你甚至可以在一个根目录下定义一长串的命名空间前缀,没有任何语法错误的。

坚持原创技术分享,您的支持将鼓励我继续创作!
-------------本文结束感谢您的阅读-------------