分享一些 PHP 中有用的知识和坑
Posted 唯一丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分享一些 PHP 中有用的知识和坑相关的知识,希望对你有一定的参考价值。
前言
在一次偶然查看 php 文档的时候,发现了一些有趣的内容,随着阅读的增加,越发觉得有趣的内容或者说时坑越来越多,所以我决定记录下来,分享出去,下文中一些内容摘录自一些优秀的博客、PHP 文档的用户笔记,或者文档原文。
尤其是文档原文,我发现很多人不会去读,很多东西也不会去注意(是的,我也是这样,所以借着这次机会,一起来学习一下。)
我忘了PHP函数的参数顺序,它们是随机的吗?
PHP is a glue that brings together hundreds of external libraries, so sometimes this gets messy. However, a simple rule of thumb is as follows:
Array functionparameters are ordered as " needle, haystack" whereas String functionsare the opposite, so " haystack, needle".
译:数组相关方法的参数顺序为,「needle, haystack」,字符串相关方法则是相反的 「haystack, needle」,
来源: https://www.php.net/manual/zh/faq.using.php#faq.using.parameterorder
我应该如何保存“盐”?
当使用 password_hash() 或者 crypt() 函数时, “盐”会被作为生成的散列值的一部分返回。 你可以直接把完整的返回值存储到数据库中, 因为这个返回值中已经包含了足够的信息, 可以直接用在 password_verify() 或 crypt() 函数来进行密码验证。
下图展示了 crypt() 或 password_hash() 函数返回值的结构。 如你所见,算法的信息以及“盐”都已经包含在返回值中, 在后续的密码验证中将会用到这些信息。
来源: https://www.php.net/manual/zh/faq.passwords.php#faq.password.storing-salts
下面代码怎么没有分成两行显示?
<pre>
<?php echo "This should be the first line."; ?>
<?php echo "This should show up after the new line above."; ?>
</pre>
在 PHP 中,一段代码的结束标记要么是“?>”要么是“?>\\n”(\\n 表示换行)。因此在上面的例子中,输出的句子将显示在同一行中,因为 PHP 忽略了代码结束标记后面的换行。这意味着如果要输出一个换行符,需要在每段 PHP 代码的结束标记后面多加一个换行。
PHP 为什么这么做呢?因为在格式化正常的 html 时,这样通常会更容易。假如输出了换行而你不需要这个换行时,就不得不用一个非常长的行来达到这样的效果,或者让产生的 HTML 页面的源文件的格式很难读。
来源: https://www.php.net/manual/zh/faq.using.php#faq.using.newlines
字符串连接操作符的优先级问题
如果你运行下面的代码,他将会输出一个警告
和结果 3
,因为字符串连接操作符 .
和 数学运算符 +
、 -
的优先级时一样的,它们将从左往右执行。 Result:
会被强转成数组 0
。如果你在低版本的 PHP 中运行,会告诉你 中边不是一个数字,如果你在 7.4 中运行,会告诉你,在 PHP 8 中 +
、 -
的优先级将会被提高。如果你使用了 PHPSTORM 中的 EA 插件,将会提醒你这个问题。
<php
$var = 3;
echo "Result: " . $var + 3;
如果你不希望这样,那么最好使用括号把它包裹起来,就像下面那样。
<?php
$var = 3;
echo "Result: " . ($var + 3);
来源: https://www.php.net/manual/zh/language.operators.string.php#41950
字符串连接操作符与数字
运行下面代码,尤其是第三行,请注意,如果 .
左右存在空格,那么即使是一个数字,也将会作用成字符串连接。
<?php
echo "thr"."ee"; //prints the string "three"
echo "twe" . "lve"; //prints the string "twelve"
echo 1 . 2; //prints the string "12"
echo 1.2; //prints the number 1.2
echo 1+2; //prints the number 3
来源: https://www.php.net/manual/zh/language.operators.string.php#41950
使用 http_build_query
NULL 的值将会被会略
<?php
$arr = array(\'test\' => null, \'test2\' => 1);
// test2=1
echo http_build_query($arr);
来源: https://www.php.net/manual/zh/function.http-build-query.php#60523
True 和 False 将会被转换成数字
<?php
$a = [teste1= true,teste2=false];
// teste1=1&teste2=0
echo http_build_query($a)
来源: https://www.php.net/manual/zh/function.http-build-query.php#122232
空的数组不会出现在结果中
<?php
$post_data = array(\'name\'=>\'miller\', \'address\'=>array(\'address_lines\'=>array()), \'age\'=>23);
// name=miller&age=23
echo http_build_query($post_data);
来源: https://www.php.net/manual/zh/function.http-build-query.php#109466
简述 OpCache 的原理
PHP执行这段代码会经过如下4个步骤(确切的来说,应该是PHP的语言引擎Zend)
- 1. Scanning(Lexing) ,将PHP代码转换为语言片段(Tokens)
- 2. Parsing, 将Tokens转换成简单而有意义的表达式
- 3. Compilation, 将表达式编译成Opocdes
- 4. Execution, 顺次执行Opcodes,每次一条,从而实现PHP脚本的功能。
现在有的Cache比如APC,可以使得PHP缓存住Opcodes,这样,每次有请求来临的时候,就不需要重复执行前面3步,从而能大幅的提高PHP的执行速度。
来源: https://www.laruence.com/2008/06/18/221.html
var_dump(1...9)输出什么?
<?php
// 10.9
var_dump(1...9);
输出10.9, 乍一看这个var_dump的输出很奇怪是不是? 为什么呢?
这里教大家,如果看到一段PHP代码感觉输出很奇怪,第一反应是看下这段代码生成的opcodes是啥,虽然这个问题其实是词法分析阶段的问题,不过还是用phpdbg分析下吧(一般为了防止opcache的影响,会传递-n):
phpdbg -n -p /tmp/1.php
function name: (null)
L1-35 {main}() /tmp/1.php - 0x7f56d1a63460 + 4 ops
L2 #0 INIT_FCALL<1> 96 "var_dump"
L2 #1 SEND_VAL "10.9" 1
L2 #2 DO_ICALL
L35 #3 RETURN<-1> 1
所以这么看来,早在生成opcode之前,1...9就变成了常量10.9,考虑到这是字面量,我们现在去看看zend_language_scanner.l, 找到这么一行:
DNUM ({LNUM}?"."{LNUM})|({LNUM}"."{LNUM}?)
这个是词法分析定义的浮点数的格式,到这里也就恍然大悟了:
1...9 会被依次接受为: 1. (浮点数1), 然后是 . (字符串连接符号) 然后是.9(浮点数0.9)
所以在编译阶段就会直接生成 “1” . “0.9” -> 字符串的字面量”10.9”
来源: https://www.laruence.com/2020/02/23/1990.html
HTTPOXY 漏洞
这里有一个核心的背景是, 长久一来我们习惯了使用一个名为"http_proxy"的环境变量来设置我们的请求代理。
http_proxy=127.0.0.1:9999 wget http://www.laruence.com/
如何形成?
在CGI(RFC 3875)的模式的时候, 会把请求中的Header, 加上HTTP_ 前缀, 注册为环境变量, 所以如果你在Header中发送一个Proxy:xxxxxx, 那么 PHP 就会把他注册为HTTP_PROXY环境变量, 于是getenv("HTTP_PROXY")就变成可被控制的了. 那么如果你的所有类似的请求, 都会被代理到攻击者想要的地址,之后攻击者就可以伪造,监听,篡改你的请求了
如何影响?
所以, 这个漏洞要影响你, 有几个核心前提是:
- 你的服务会对外请求资源
- 你的服务使用了HTTP_PROXY(大写的)环境变量来代理你的请求(可能是你自己写,或是使用一些有缺陷的类库)
- 你的服务跑在PHP的CGI模式下(cgi, php-fpm)
如何处理?
以nginx为例, 在配置中加入:
fastcgi_param HTTP_PROXY "";
所以建议, 即使你不受此次漏洞影响, 也应该加入这个配置.
而如果你是一个类库的作者,或者你因为什么原因没有办法修改服务配置, 那么你就需要在代码中加入对sapi的判断, 除非是cli模式, 否则永远不要相信http_proxy环境变量,
<?php
if (php_sapi_name() == \'cli\' && getenv(\'HTTP_PROXY\')) {
//只有CLI模式下, HTTP_PROXY环境变量才是可控的
}
补充: 从PHP5.5.38开始, getenv增加了第二个参数, local_only = false, 如果这个参数为true, 则只会从系统本地的环境变量表中获取, 从而修复这个问题, 并且默认的PHP将拦截HTTP_PROXY:fix
HTTPOXY漏洞说明 - 风雪之隅
https://www.laruence.com/2016/07/19/3101.html
运算符优先级
&& 和 and 在赋值运算中的问题
运行下面的代码,第一个 $bool
将打印为 false
,预期如此,但是第二个 $bool
将打印 true
。这是因为 =
的优先级高于 and
运算符,所以,第二个 $bool
将会被当成 ($bool = true) and false
执行。
<?php
$bool = true && false;
// false
var_dump($bool);
$bool = true and false;
// true
var_dump($bool);
来源: https://www.php.net/manual/zh/language.operators.precedence.php#117390
instanceof 运算符
你是否曾经写过下面这样的代码?
<?php
class A {
}
$A = new A();
// 1
var_dump(! $A instanceof A); // false
// 2
var_dump((! $A instanceof A)); // false
// 3
var_dump(!( $A instanceof A)); // false
// 其实不用担心,因为 instanceof 的优先级要高于 ! ,你可以放心的使用,
// 不必添加括号,让他们看起来是一个表达式,但是在复杂的情况下例外。
var_dump(! $A instanceof A);
在你需要对 instanceof
运算的结果做取反运算时,因为取反运算符 !
的优先级低于 instanceof
所以,你不必再它们外面再加上一个圆括号来表明这是一组表达式,但是再复杂情况下例外。
感谢评论区 @Mr_houzi 提出的 第 3 个「反例」
array_map 的有趣用法
通常,我会使用 array_map
来处理一个数组,让他返回一个新的数组,当然,它的用处就是这样的,但是除了这种基础的用法,它其实还有一些有趣的用法,并且,这些用法都存在于 PHP 的手册中。
多个 array 用法
通常你会这样使用它。
<?php
$arr1 = [\'apple\', \'orange\', \'mango\', \'pear\'];
$newArr1 = array_map(\'strtoupper\',$arr1);
这只是一个简单的
以上是关于分享一些 PHP 中有用的知识和坑的主要内容,如果未能解决你的问题,请参考以下文章