Skip to main content

13 posts tagged with "php"

View All Tags

composer的psr4

· 10 min read

我这次主要是要描述composer的psr4自动加载相关内容.php有很多历史的包袱,所以需要做很多妥协,而namespace 以及自动加载也是.

include 和 require的大坑

例子

include 和require的区别什么的可能还是某些面试官的问题之一,但是include和require都有一个致命的大坑,include和require一个相对路径是相对于工作目录的.

举个例子.

当前在index.php 的目录中

# tree

test
   ├── index.php
   ├── relative.php
   └── subdir
   ├── a.php
   └── relative.php

index.php 的代码很简单,就是包含一个路径

<?php
include "./subdir/a.php";

两个relative.php 文件分别输出自己的路径 subdir/relative.php文件:

<?php
echo "test/subdir/relative.php"

relative.php文件:

<?php
echo "test/relative.php";

那么如果与index.php 同目录下会include哪一个呢?

答案是:

# php index.php 
test/relative.php

include了与index.php 同一个目录下的relative.php 文件

而如果你在index.php的上一层目录执行,也就是test目录它甚至会报错

php test/index.php 
PHP Warning: include(./subdir/a.php): failed to open stream: No such file or directory in /root/test/index.php on line 2
PHP Warning: include(): Failed opening './subdir/a.php' for inclusion (include_path='.:/usr/share/php') in /root/test/index.php on line 2

这一切都是因为当是相对路径的时候,调用了getcwd()来获取工作目录,如果你使用shell的pwd话也可以看自己的工作目录.

由于这个比较坑的特性,php的代码如果手工使用include并且还使用了相对路径,那之后就非常难以维护了.所以我们需要尽量减少使用include相对路径,因为你知道的原因,你一旦写了一个相对路径,总会有后人copy and paste你的代码,然后把这个include也复制进去了,而这就是下一个屎坑的开始.

所以,自动加载可以减缓这种大坑的产生,因为他可以减少手工include相对路径的风险,因为他们往往会这样include文件include __DIR__ . 'aaa/bbb/ccc.php',由于不是相对路径,所以会好很多.

CLI模式与CGI/FASTCGI工作目录的不同

CLI SAPI 不会将当前目录改为已运行的脚本所在的目录。

以下范例显示了本模块与 CGI SAPI 模块之间的不同:

<?php
// 名为 test.php 的简单测试程序
echo getcwd(), "\n";
?>

在使用 CGI 版本时,其输出为

$ pwd
/tmp

$ php-cgi -f another_directory/test.php
/tmp/another_directory

明显可以看到 PHP 将当前目录改成了刚刚运行过的脚本所在的目录。

使用 CLI SAPI 模式,得到:

$ pwd
/tmp

$ php -q another_directory/test.php
/tmp

include 和require 的opcode和getcwd

require 和include 词法分析和语法分析后,生成opcode是73,ZEND_INCLUDE_OR_EVAL,在include或者require之后,如果是相对路径

最后会调用

VCWD_GETCWD(cwd, MAXPATHLEN)

这个最后就是调用glibc 下面的getcwd

getcwd 系统调用

每个进程task_struct会有fs_struct 结构,这个结构体会含有pwdroot,如果使用getcwd()这个函数,通过glibc会通过系统调用读取fs_struct的pwd属性并返回

static void get_fs_root_and_pwd_rcu(struct fs_struct *fs, struct path *root,
struct path *pwd)
{
...
*root = fs->root;
*pwd = fs->pwd;
...
}
SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
{
int error;
struct path pwd, root;
char *page = __getname();

if (!page)
return -ENOMEM;

rcu_read_lock();
get_fs_root_and_pwd_rcu(current->fs, &root, &pwd); // 每个进程会关联一个fs_struct结构,fs_struct 结构有两个属性root和pwd描述了root目录和pwd目录

char *cwd = page + PATH_MAX;
int buflen = PATH_MAX;

prepend(&cwd, &buflen, "\0", 1);
error = prepend_path(&pwd, &root, &cwd, &buflen);
...
copy_to_user(buf, cwd, len) // 将处理后的pwd 返回到用户态
...

}

include和require总结

include以及require如果引入相对路径的文件,那么这个相对路径都是相对于getcwd(),也就是当前工作目录.

而cgi和cli模式又有不同

  • cli模式下的当前路径就是shell pwd的值
  • 而cgi 这个SAPI和cli这个CLI SAPI不一样的地方在于他会帮你切换一次工作目录到第一次运行的php文件的当前目录作为工作目录.

命名空间

命名空间是什么?

其实就是一堆限定符.

为什么要有命名空间?
因为我们要复用别人的代码,你想引用别人的一个库,别人库里写了个hello函数,你也写了个hello函数.这就麻烦了,所以引入命名空间,只要保证大家的命名空间不一样,那样就算大家都有相同的函数名,也不会冲突了.

自动加载

开始说到自动加载了,自动加载.什么是自动加载呢?

其实就是动态include,或者叫做运行时include.

平时我们怎么include文件的呢?

就是手工include一堆文件,就像我刚才上面的例子一样.这样至少有两个风险:

  • 新手使用了相对路径include
  • 得手工引入,但是include会重复引入文件,得使用include_once 或者require_once

就风险而言,新手使用相对路径引入的危险是非常大的.重复引入只是会校验多一点有一点性能影响而言.

spl_autoload_register

spl_autoload_*这一类的函数都是php自动加载的核心函数,实现自动加载则是依赖spl_autoload_register


/* {{{ proto bool spl_autoload_register([mixed autoload_function [, bool throw [, bool prepend]]])
Register given function as __autoload() implementation */
PHP_FUNCTION(spl_autoload_register)
{

...

if (zend_hash_add_mem(SPL_G(autoload_functions), lc_name, &alfi, sizeof(autoload_func_info)) == NULL) {
...
}
...
} /* }}} */

然后相关的调用会在zend_hash_exists(EG(class_table), lc_name) 判断是否在全局的EG(class_table) 里面
下面的spl_autoload_call是一个例子

PHP_FUNCTION(spl_autoload_call)
{

if (SPL_G(autoload_functions)) { // spl_autoload_register 放进去的 SPL_G(autoload_functions)
int l_autoload_running = SPL_G(autoload_running);
SPL_G(autoload_running) = 1;
lc_name = zend_string_alloc(Z_STRLEN_P(class_name), 0);
zend_str_tolower_copy(ZSTR_VAL(lc_name), Z_STRVAL_P(class_name), Z_STRLEN_P(class_name));
zend_hash_internal_pointer_reset_ex(SPL_G(autoload_functions), &pos);
while (zend_hash_get_current_key_ex(SPL_G(autoload_functions), &func_name, &num_idx, &pos) == HASH_KEY_IS_STRING) { // 循环回调函数
alfi = zend_hash_get_current_data_ptr_ex(SPL_G(autoload_functions), &pos);
zend_call_method(Z_ISUNDEF(alfi->obj)? NULL : &alfi->obj, alfi->ce, &alfi->func_ptr, ZSTR_VAL(func_name), ZSTR_LEN(func_name), retval, 1, class_name, NULL); // 调用注册的回调函数

if (zend_hash_exists(EG(class_table), lc_name)) { // 回调找到了类名,则跳出循环

break;
}
zend_hash_move_forward_ex(SPL_G(autoload_functions), &pos);
}
...
}
..
} /* }}} */

自动加载流程其实很简单 自动加载的例子

<?php
// test.php
spl_autoload_register(function ($class) {
include "$class" . '.php';
});
$obj = new ClassA();

以及类ClassA.php

<?php
class ClassA{}

下面是堆栈

(gdb) bt
#0 zif_spl_autoload_call (execute_data=0x7fffef61e0a0, return_value=0x7fffffffa2f0) at /home/dinosaur/Downloads/php-7.2.2/ext/spl/php_spl.c:393
#1 0x0000000000932807 in zend_call_function (fci=0x7fffffffa330, fci_cache=0x7fffffffa300) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_execute_API.c:833
#2 0x0000000000933000 in zend_lookup_class_ex (name=0x7fffe6920b58, key=0x7fffe70e63f0, use_autoload=1) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_execute_API.c:990
#3 0x0000000000933dbd in zend_fetch_class_by_name (class_name=0x7fffe6920b58, key=0x7fffe70e63f0, fetch_type=512) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_execute_API.c:1425
#4 0x00000000009b7e46 in ZEND_NEW_SPEC_CONST_HANDLER () at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_vm_execute.h:3211
#5 0x0000000000a380a4 in execute_ex (ex=0x7fffef61e030) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_vm_execute.h:59929
#6 0x0000000000a3d0ab in zend_execute (op_array=0x7fffef683300, return_value=0x0) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_vm_execute.h:63760
#7 0x000000000094cd22 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend.c:1496
#8 0x00000000008b0b4a in php_execute_script (primary_file=0x7fffffffcaa0) at /home/dinosaur/Downloads/php-7.2.2/main/main.c:2590
#9 0x0000000000a3fd23 in do_cli (argc=2, argv=0x1441a60) at /home/dinosaur/Downloads/php-7.2.2/sapi/cli/php_cli.c:1011
#10 0x0000000000a40ee0 in main (argc=2, argv=0x1441a60) at /home/dinosaur/Downloads/php-7.2.2/sapi/cli/php_cli.c:1404

所以整个自动加载的核心流程就是在查找类的时候会去调用spl_autoload_call,这个函数则会回调注册的自动加载函数,直到遍历所有的回调函数都没有找到或者在某个遍历的时候找到了直接返回。

psr规范与psr4

psrPHP Standards Recommendations的简称,而psr4和psr0有都是和自动加载相关的内容.

其实就是规定了一个简单的替换

\Aura\Web\Response\Status	Aura\Web	/path/to/aura-web/src/	/path/to/aura-web/src/Response/Status.php

psr4规定了我们如何去加载一个文件: 将完全限定名用前缀地址替换,后面则是后面的文件. 举个例子: 你要加载的类是:

\Aura\Web\Response\Status

那么你可以使用Aura\Web 映射/path/to/aura-web/src/,那么类\Aura\Web\Response\Status就会去/path/to/aura-web/src/Response/Status.php文件找

可以说有点像nginx的路由配置: 下面是nginx的配置

location ^~ /images/ {
    # 匹配任何已 /images/ 开头的任何查询并且停止搜索。任何正则表达式将不会被测试。
}

那么上面的\Aura\Web\Response\Status的psr4 有点像这样:

location ^~ /Aura/Web/ {
    root /path/to/aura-web/src/;
}

相关阅读

php7 异常、错误以及相关坑

· 3 min read

php 的坑非常之多,有高低版本的,有历史包袱类的。也有与其他语言不一致导致的知识迁移导致的坑。

前置知识

throwable

PHP 7 changes how most errors are reported by PHP. Instead of reporting errors through the traditional error reporting mechanism used by PHP 5, most errors are now reported by throwing Error exceptions.

(人肉机翻)php 7 改变了php大多数的errors的警告提示方式。和php 5 传统的error reporting 机制不同,php 的大多数错误通过抛出错误异常来警告提示。

填坑开始

例子1 

  • php 版本7,除以0的错误会变成异常
<?php
// test.php
try {
echo 1%0;
} catch (DivisionByZeroError $e) {
echo "bbb";
}
?>

然后执行

php test.php 
bbb

输出bbb ,也就是被try catch 住了。

那么我们先看php 是怎么catch 住这个错误的

堆栈如下:

Breakpoint 1, zend_throw_exception_ex (exception_ce=0x14cfe70, code=0, format=0x1087ea4 "Modulo by zero") at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_exceptions.c:913
913 {
(gdb) bt
#0 zend_throw_exception_ex (exception_ce=0x14cfe70, code=0, format=0x1087ea4 "Modulo by zero") at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_exceptions.c:913
#1 0x00000000009b9feb in ZEND_MOD_SPEC_CONST_CONST_HANDLER () at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_vm_execute.h:4270
#2 0x0000000000a381e4 in execute_ex (ex=0x7fffef61e030) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_vm_execute.h:59989
#3 0x0000000000a3d0ab in zend_execute (op_array=0x7fffef684300, return_value=0x0) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_vm_execute.h:63760
#4 0x000000000094cd22 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend.c:1496
#5 0x00000000008b0b4a in php_execute_script (primary_file=0x7fffffffca10) at /home/dinosaur/Downloads/php-7.2.2/main/main.c:2590
#6 0x0000000000a3fd23 in do_cli (argc=2, argv=0x1441f40) at /home/dinosaur/Downloads/php-7.2.2/sapi/cli/php_cli.c:1011
#7 0x0000000000a40ee0 in main (argc=2, argv=0x1441f40) at /home/dinosaur/Downloads/php-7.2.2/sapi/cli/php_cli.c:1404

相关阅读

例子二

php 版本7

<?php
try {
echo 1/0; // 取余改成了除法
} catch (DivisionByZeroError $e) {
echo "bbb";
}
?>

输出

Warning: Division by zero in /home/dinosaur/test/test.php on line 3
INF

发现了不一样了吗?

① 抛了warning 没有被try catch 住

② php 脚本继续执行,(并输出INF)

我们看看堆栈:

(gdb) bt
#0 zend_error (type=2, format=0x107dcfc "Division by zero") at /home/dinosaur/Downloads/php-7.2.2/Zend/zend.c:1105
#1 0x000000000093fb5b in div_function (result=0x7fffef61e090, op1=0x7fffe70e61c0, op2=0x7fffe70e61d0) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_operators.c:1173
#2 0x00000000009a82a0 in fast_div_function (result=0x7fffef61e090, op1=0x7fffe70e61c0, op2=0x7fffe70e61d0) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_operators.h:738
#3 0x00000000009b9f22 in ZEND_DIV_SPEC_CONST_CONST_HANDLER () at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_vm_execute.h:4251
#4 0x0000000000a381d4 in execute_ex (ex=0x7fffef61e030) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_vm_execute.h:59986
#5 0x0000000000a3d0ab in zend_execute (op_array=0x7fffef684300, return_value=0x0) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_vm_execute.h:63760
#6 0x000000000094cd22 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend.c:1496
#7 0x00000000008b0b4a in php_execute_script (primary_file=0x7fffffffca10) at /home/dinosaur/Downloads/php-7.2.2/main/main.c:2590
#8 0x0000000000a3fd23 in do_cli (argc=2, argv=0x1441f40) at /home/dinosaur/Downloads/php-7.2.2/sapi/cli/php_cli.c:1011
#9 0x0000000000a40ee0 in main (argc=2, argv=0x1441f40) at /home/dinosaur/Downloads/php-7.2.2/sapi/cli/php_cli.c:1404

zend_error 翻到最底下就是write 系统调用了

				if (Z_LVAL_P(op2) == 0) {
zend_error(E_WARNING, "Division by zero");
ZVAL_DOUBLE(result, ((double) Z_LVAL_P(op1) / (double) Z_LVAL_P(op2)));
return SUCCESS;
}

zend_error后就return 了,所以后面的程序可以继续执行

对比总结

1/0 不会被抛出异常,会有warning 并继续执行 

坑点在于:

  • 不是所有的error都能被catch
  • 没有被catch 住的话会继续执行

php隐式转换大坑

· 3 min read

php是弱类型语言,其中一个坑是隐式转换

什么时候会触发隐式转换

这个我只知道比较的时候如果有相应的隐式转换。

例子

下面有个例子

php 代码

<?php
var_dump('1abc'== 1);
// 返回 true

经过_is_numeric_string_ex转换后,将1abc转换成了1

(gdb) p *lval
$4 = 1

堆栈如下

(gdb) bt
#0 _is_numeric_string_ex (str=0x7fffef602b58 "1abc", length=4, lval=0x7fffffff99a0, dval=0x7fffffff99a0, allow_errors=1, oflow_info=0x0) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_operators.c:3001
#1 0x0000000000938c52 in is_numeric_string_ex (str=0x7fffef602b58 "1abc", length=4, lval=0x7fffffff99a0, dval=0x7fffffff99a0, allow_errors=1, oflow_info=0x0)
at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_operators.h:142
#2 0x0000000000938c94 in is_numeric_string (str=0x7fffef602b58 "1abc", length=4, lval=0x7fffffff99a0, dval=0x7fffffff99a0, allow_errors=1)
at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_operators.h:146
#3 0x000000000094502b in compare_function (result=0x7fffffff9b78, op1=0x7fffffff9aa8, op2=0x7fffffff9ac8) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_operators.c:2069
#4 0x0000000000945a32 in is_equal_function (result=0x7fffffff9b78, op1=0x7fffffff9aa8, op2=0x7fffffff9ac8) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_operators.c:2159
#5 0x00000000009274e3 in zend_try_ct_eval_binary_op (result=0x7fffffff9b78, opcode=17, op1=0x7fffffff9aa8, op2=0x7fffffff9ac8) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_compile.c:6880
#6 0x0000000000927a0d in zend_compile_binary_op (result=0x7fffffff9b70, ast=0x7fffef686090) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_compile.c:6999
#7 0x000000000092b8d1 in zend_compile_expr (result=0x7fffffff9b70, ast=0x7fffef686090) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_compile.c:8235
#8 0x000000000091b84f in zend_compile_args (ast=0x7fffef6860a8, fbc=0x167f050) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_compile.c:3202
#9 0x000000000091baaf in zend_compile_call_common (result=0x7fffffff9d20, args_ast=0x7fffef6860a8, fbc=0x167f050) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_compile.c:3282
#10 0x000000000091e44b in zend_compile_call (result=0x7fffffff9d20, ast=0x7fffef6860d8, type=0) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_compile.c:4009
#11 0x000000000092bc3e in zend_compile_var (result=0x7fffffff9d20, ast=0x7fffef6860d8, type=0) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_compile.c:8339
#12 0x000000000092b841 in zend_compile_expr (result=0x7fffffff9d20, ast=0x7fffef6860d8) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_compile.c:8217
#13 0x000000000092b513 in zend_compile_stmt (ast=0x7fffef6860d8) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_compile.c:8186
#14 0x000000000092b0de in zend_compile_top_stmt (ast=0x7fffef6860d8) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_compile.c:8072
#15 0x000000000092b0c0 in zend_compile_top_stmt (ast=0x7fffef686018) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend_compile.c:8067
#16 0x00000000008ec355 in zend_compile (type=2) at Zend/zend_language_scanner.l:601
#17 0x00000000008ec4e6 in compile_file (file_handle=0x7fffffffca10, type=8) at Zend/zend_language_scanner.l:635
#18 0x00000000007296f0 in phar_compile_file (file_handle=0x7fffffffca10, type=8) at /home/dinosaur/Downloads/php-7.2.2/ext/phar/phar.c:3320
#19 0x00007fffeeeca612 in opcache_compile_file (file_handle=0x7fffffffca10, type=8, key=0x7fffef16dd4c <accel_globals+556> "test.php:240416:240464", op_array_p=0x7fffffffa318)
at /home/dinosaur/Downloads/php-7.2.2/ext/opcache/ZendAccelerator.c:1600
#20 0x00007fffeeecb722 in persistent_compile_file (file_handle=0x7fffffffca10, type=8) at /home/dinosaur/Downloads/php-7.2.2/ext/opcache/ZendAccelerator.c:1941
#21 0x000000000094ccb4 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /home/dinosaur/Downloads/php-7.2.2/Zend/zend.c:1490
#22 0x00000000008b0b4a in php_execute_script (primary_file=0x7fffffffca10) at /home/dinosaur/Downloads/php-7.2.2/main/main.c:2590
#23 0x0000000000a3fd23 in do_cli (argc=2, argv=0x1441f40) at /home/dinosaur/Downloads/php-7.2.2/sapi/cli/php_cli.c:1011
#24 0x0000000000a40ee0 in main (argc=2, argv=0x1441f40) at /home/dinosaur/Downloads/php-7.2.2/sapi/cli/php_cli.c:1404


这是什么规则呢? 1 如果一个操作数是string ,一个是number ,会将string 转换成int ,如果转换发现不是数字就转换成0 然后他们就相等了

规则

操作数1操作数2规则
string,resource 或 numberstring,resource 或 number将字符串和资源转换成数字,按普通数学比较

相关阅读