PHP/Composer是如何加载一个类的

PHP/composer开发中,我们只需要require 'vendor/autoload.php',然后就可以直接使用各种类了。那么这些类是如何加载的呢?其中有没有什么可以优化的点呢?

概览

PHP/composer下,类的加载主要到如下部分(还没有包括各个部分的初始化逻辑):

1
2
3
4
5
6
7
8
PHP中zend_lookup_class_ex
|-> EG(class_table)
|-> spl_autoload_call
|-> ComposerAutoloadClassLoader::loadClass
|-> findFile
|-> class map lookup
|-> PSR-4 lookup
|-> PSR-0 lookup

PHP的类加载

首先,PHP在运行的时候,需要一个类,是通过zend_lookup_class_ex来找到这个类的相关信息的。

zend_lookup_class_ex查找类的主要逻辑如下(假设类名字放到变量lc_name中):

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
48
49
50
51
52
53
54
55
ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, const zval *key, int use_autoload) /* {{{ */
{
// 1. 类名字转化为小写
if (ZSTR_VAL(name)[0] == '') {
lc_name = zend_string_alloc(ZSTR_LEN(name) - 1, 0);
zend_str_tolower_copy(ZSTR_VAL(lc_name), ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1);
} else {
lc_name = zend_string_tolower(name);
}

// 2. 直接在class_table中查找
ce = zend_hash_find_ptr(EG(class_table), lc_name);
if (ce) {
if (!key) {
zend_string_release(lc_name);
}
return ce;
}
// 3. 如果没有autoload_func,则注册默认的__autoload
if (!EG(autoload_func)) {
zend_function *func = zend_hash_str_find_ptr(EG(function_table), ZEND_AUTOLOAD_FUNC_NAME, sizeof(ZEND_AUTOLOAD_FUNC_NAME) - 1);
if (func) {
EG(autoload_func) = func;
} else {
if (!key) {
zend_string_release(lc_name);
}
return NULL;
}

}

// 4. 加载ACLASS的过程中,又加载ACLASS,递归加载,直接找不到类
if (zend_hash_add_empty_element(EG(in_autoload), lc_name) == NULL) {
if (!key) {
zend_string_release(lc_name);
}
return NULL;
}

// 5. 调用autoload_func
ZVAL_STR_COPY(&fcall_info.function_name, EG(autoload_func)->common.function_name);
fcall_info.symbol_table = NULL;

zend_exception_save();
if ((zend_call_function(&fcall_info, &fcall_cache) == SUCCESS) && !EG(exception)) {
ce = zend_hash_find_ptr(EG(class_table), lc_name);
}
zend_exception_restore();

if (!key) {
zend_string_release(lc_name);
}
return ce;
}
  1. lc_name转化成小写(这说明PHP中类名字不区分大小写)
  2. 然后在EG(class_table)找,如果找到,直接返回(我们自己注册的类,扩展注册的类都是这样找到的)
  3. 然后查看EG(autoload_func),如果没有则将__autoload注册上(值得注意的是,如果注册了EG(autoload_func),则不会走__autoload)
  4. 通过EG(in_autoload)判断是否递归加载了(EG(in_autoload)是一个栈,记载了那些类正在被autoload加载)
  5. 然后调用EG(autoload_func),并返回类信息

SPL扩展注册

阅读更多

Laravel Migration 类名重复分析

开发者在开发中一般都会为Migration起类名,最常见的就是AlterUserTable这种名字,但是如果后续的开发者第二次修改表,就有了两个类名相同的Migration了。 这样有什么问题吗? 首先,这个Migration如果和之前类名相同的Migration不在同一批次的话,是可以执行成功的。 接下来看看为什么。

Migration的执行

从Migration的执行来看,\Illuminate\Database\Migrations\Migrator::getMigrationFiles拿到所有的Migration列表,通过和执行过的Migration array_diff之后,获取没有执行的Migration列表,在执行这些Migration的时候会在\Illuminate\Database\Migrations\Migrator::requireFiles方法中require对应的Migration文件,如果恰好两个类名相同的Migration需要执行,那么就会出现错误 Cannot declare class AlterTestTable, because the name is already in use in ./database/migrations/2016_07_20_081952_alter_test_table.php on line 31 当然,如果这些Migration都在不同批次中,那么永远也不会有这个错误发生了嘛。

Migration的回退

\Illuminate\Database\Migrations\MigrationRepositoryInterface::getLast获取最后一次执行的Migration文件名列表,然后在\Illuminate\Database\Migrations\Migrator::runDown方法中将Migration名字resolve到类名(比如 2016_07_20_081952_alter_test_tableAlterTestTable 类,这时候就通过new这个类,运行down方法来回退。 同样的,在这种情况下,如果有两个一样名字的类,则autoload机制只会选择一个。 注意,在这种情况下,即使两个Migration不在同一个批次当中回退,那也会有一个Migration永远不能回退。


  • 这也解释了为什么在本地创建了migration后,运行后,然后rollback的时候提示找不到这个类,而必须要dumpautoload才可以。
  • 由于Migration up的时候是通过Laravel自己解析到代码的,而down的时候是通过composer解析的,这导致了Migration的up和down不对称,确实不怎么好看。
  • 作为一个合格的开发,显然不应该让Migration类名重复!
阅读更多

建立xdebug+eclipse的调试环境

几周了,终于让 eclipse 能够调试php代码了,期间找了许多资料,但是总不能如愿,于是记录如下。

安装xdebug

fedora 下直接安装 php-pecl-xdebug 包就算是配置好了 php 的 xdebug 扩展。 记得systemctl reload httpd.service 访问 phpinfo 页面时应该有 xdebug 的相关信息: Screenshot from 2013-04-03 16:01:51 然后打开 display_errors ,即 php.ini 里面有 display_errors= On ,那么打开一个有异常的页面会出现彩色提示。Screenshot from 2013-04-03 15:51:52

配置远程调试

这时可以配置远程调试。在 /etc/php.d/xdebug.ini 中写入如下几行:

[Xdebug]
xdebug.remote_autostart=On
xdebug.remote_enable=On
xdebug.remote_host=127.0.0.1
xdebug.remote_handler=dbgp

然后systemctl reload httpd.service ,在 phpinfo 页面确认上述参数已经生效。 然后用 debugclient 来测试xdebug是否正常工作。 运行 debugclient ,显示等待连接: Screenshot from 2013-04-03 16:10:25 然后访问一个会产生异常的页面,debugclient 会得到连接: Screenshot from 2013-04-03 17:06:43

配置eclipse

eclipse安装好相关的插件(PDT),创建项目,把项目目录配置为虚拟主机,然后设置项目的调试器。 有待完善啊。。

阅读更多