乐趣区

关于php:分享一些-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();

var_dump((! $A instanceof A));

// 其实不必放心,因为 instanceof 的优先级要高于 ! , 你能够释怀的应用,// 不用增加括号,让他们看起来是一个表达式,然而在简单的状况下例外。var_dump(! $A instanceof A);

在你须要对 instanceof 运算的后果做取反运算时,因为取反运算符 ! 的优先级低于 instanceof 所以,你不用再它们里面再加上一个圆括号来表明这是一组表达式,然而再简单状况下例外。

array_map 的乏味用法

通常,我会应用 array_map 来解决一个数组,让他返回一个新的数组,当然,它的用途就是这样的,然而除了这种根底的用法,它其实还有一些乏味的用法,并且,这些用法都存在于 PHP 的手册中。

多个 array 用法

通常你会这样应用它。

<?php

$arr1 = ['apple', 'orange', 'mango', 'pear'];
$newArr1 = array_map('strtoupper',$arr1);

这只是一个简略的🌰,它会把所有的值转为大写的。那么看看上面的用法,猜猜会打印什么?

<?php

$arr1 = ['apple', 'orange', 'mango', 'pear'];
$arr2 = ['cauliflower', 'lettuce', 'kale', 'romaine'];

function map1($a, $b){var_dump($a, $b);
  // apple   cauliflower
  // orange  lettuce
  // mango   kale
  // pear    romaine
}

array_map('map1', $arr1, $arr2);

如上 map1 办法所示,将会程序遍历 $arr1 , $arr2 中的值,并且传递给 map1,依据手册所定义:如果多个数组的长度不一,即短的数组将会被填充空,至长的数组一样

原生函数使用不当的话会比你设想的要慢

array_unique、array_merge 等,如果应用办法不正确,会比你想想的要慢,甚至是慢很多,远不如 foreach。

在上面这个答复中,列举了 PHP 中一些 array_* 办法的工夫复杂度
performance – List of Big-O for PHP functions – Stack Overflow

小心代码中的比拟

上面的比拟将会返回 true,是不是不敢相信?

因为两个 md5 值都有开始 ’0e’,所以 PHP 类型了解这些字符串是迷信符号。依据定义,0 的任何次方都是 0,所以在这里会成立‎,所以当你确定一个变量的类型时,你最好应用 ===(恒等于)进行比拟。

<?php

$a = md5('240610708');// 0e462097431906509019562988736854
$b = md5('QNKCDZO'); // 0e830400451993494058024219903391

var_dump($a == $b); // true

留神,当你在思考应用 md5 存储明码时,你应该放弃这个想法,应该改用为 password_hash 系列办法。

来自:https://www.php.net/manual/zh/function.md5.php#123392

禁用 PHP 中不平安的 eval 办法

家喻户晓,在 php 中,eval 办法能够执行任意 PHP 代码,如果没有做好解决,被用户利用了,就有可能会造成安全漏洞,所以最好想方法禁用它,谈到禁用 php 函数,你应该想到了 php.ini 中的 disable_functions参数,能够用来禁用 PHP 函数,一些集成环境中也会禁用一些高风险函数来升高危险。

然而,这个配置项,却禁用不了 eval 函数,因为依据官网文档的定义,eval 不是一个函数,他如同 echo、这些非凡办法一样,他是一个语法结构,所以不能应用 disable_functions进行禁用,除此之外,还有 require、list、array、print、unset、include、isset、empty、die、exit 等,这些都是语法结构,不是函数,如果你应用 function_exists判断,他们都会返回 false

如果你真的须要禁用 eval,你得装置一些第三方扩大来实现,比方 mk-j/PHP_diseval_extension

参考:https://www.php.net/manual/zh/functions.variable-functions.php#functions.variable-functions

将任意类型转换为 null

听起来没什么用然而你的确能够这样做。

<?php

$a = 'Hi!';
// 在 PHP 7.2 以下,这行代码会返回 null,7.2 ~ 7.4 会返回 NULL,然而会提醒被遗弃,// 8.0 开始,将不再反对
var_dump((unset)$a);
var_dump($a);

除此之外,你还能够用 settype 函数
参考:https://www.php.net/settype

参考:https://www.php.net/manual/zh/function.unset.php#example-5601

isset 和 unset 同时反对多个参数

unset 反对多个参数,相比大多数人是晓得的,然而 isset 也反对哟。

<?php

var_dump(isset($a, $b, $c));

unset($a, $b, $c);

你不须要放心这几个变量没有被设置,他们在这里都是平安的,不会报错,在 isset 多个变量时,必须要所有变量都不为 null时,才会返回 true,当遇到一个不存在时,将会立刻返回。

参考:https://www.php.net/isset

疾速查问一个函数或者类或语法的参考

当你要查问一个 php 办法或者对象或者语法时,你不须要关上 php 手册进行搜寻,你只须要在 https://php.net前面跟上办法、语法、对象的名字即可,并且不须要关怀大小 i 额,比方像上面这些链接。

  • https://php.net/curlfile
  • https://php.net/isset
  • https://php.net/if

应用反射调用 protected 或者 private 的类办法

如果想防止一个办法被内部可见或者子类可见,能够采纳 protected 或者 private 关键字来批改这些类,然而咱们有时候又想在内部调用这些办法,应该怎么办呢?只能改成 public 吗?如果这是咱们本人的代码,当然能够这样做,然而如果是引入的内部代码的话,可能就不太好间接批改了。

当初,咱们能够在内部应用 反射 来调用这些办法,当初咱们来定义一个 Lisa 类

<?php

class Lisa
{public function name()
    {return 'Lisa';}

    protected function age()
    {return 22;}

    private function weight()
    {return 95;}
  
    private static function eat(){return 1;}
}

通常状况下,咱们是没有方法间接调用 age 和 weight 办法的,当初,咱们应用反射来调用。

<?php
// ...
$reflectionClass = new ReflectionClass('Lisa');
$ageMethod = $reflectionClass->getMethod('age'); // 获取 age 办法
$ageMethod->setAccessible(true); // 设置可见性
// 调用这个办法,须要传入对象作为上下文
$age = $ageMethod->invoke($reflectionClass->newInstance());
var_dump($age);// 22

下面的代码看起来有些繁琐,还有一个更简略的方法。

<?php
// ...
$reflectionClass = new ReflectionClass('Lisa');
$weightMethod = $reflectionClass->getMethod('weight');// 获取 weight 办法
// 获取一个闭包,而后调用,同样须要传入对象作为上下文,前面调用的中央就能够传入参数
$weight = $weightMethod->getClosure($reflectionClass->newInstance())();
var_dump($weight);

调用静态方法

<?php
// ...
$reflectionClass = new ReflectionClass('Lisa');
$eatMethod = $reflectionClass->getMethod('eat');
$eatMethod->setAccessible(true);
$eat = $eatMethod->invoke(null); // 如果是一个静态方法,你能够传递一个 null
var_dump($eat);

同样,类成员也能够应用反射进行批改。
参考:https://www.php.net/manual/zh/class.reflectionproperty.php

实例化一个类,然而绕过他的构造方法

有没有这样想过?实例化一个类,然而却不想调用他的构造方法(__construct),这里也能够用反射做到。

<?php

class Dog
{
    private $name;

    public function __construct($name)
    {$this->name = $name;}

    public function getName()
    {return $this->name;}
}

$dogClass = new ReflectionClass('Dog');
// 创立一个新实例,然而不调用构造方法
$dogInstance = $dogClass->newInstanceWithoutConstructor();
var_dump($dogInstance->getName()); // null

如果你的环境不能应用反射,你还能够试试另一个很 cool 的办法,就是应用反序列化,能够参考包 doctrine/instantiator – Packagist

参考:https://www.php.net/manual/zh/reflectionclass.newinstancewithoutconstructor.php

获取类一个类的所有父类

应用 class_parents能够获取一个类的所有父类,并且反对主动加载。

<?php

class A{}
class B extends A{}
class C extends B{}
class D extends C{}

var_dump(class_parents('D'));
/*
array(3) {
  'C' =>
  string(1) "C"
  'B' =>
  string(1) "B"
  'A' =>
  string(1) "A"
}
*/

参考:https://www.php.net/manual/zh/function.class-parents.php

乏味的递增和递加

递增递加不能作用域 bool 值

递增、递加不能应用在 false 下面,然而 +=-= 能够

<?php

$a = false;

++$a;

var_dump($a);// false

$a++;

var_dump($a);// false

--$a;

var_dump($a);// false

$a--;

var_dump($a);// false

$a-= 1;

var_dump($a);// -1

$a+= 1;// 因为后面扭转了,变成了 -1,所以上面是 0,请不要在这里纳闷

var_dump($a);// 0

递增能够作用域 NULL,然而递加不能够

<?php

$a = null;
++$a;
var_dump($a); //1

$a = null;
--$a;
var_dump($a); // null

递增能够作用于字母,然而递加不能够

a-y 递增时字母都将向后减少一个,然而当 z 的时候,就将会回到 aa,循环如此,然而只能递增,不能递加

<?php

$a = 'a';
++$a;
var_dump($a); // b

$a = 'z';
++$a;
var_dump($a); // aa

$a = 'A';
++$a;
var_dump($a); // B

$a = 'Z';
++$a;
var_dump($a); // AA

混合递增数字和字母

当初你还能够把字母和数字混合起来,就像这样:

>>> $a = 'A1'
=> "A1"
>>> ++$a
=> "A2"
>>> ++$a
=> "A3"
>>> $a = '001A'
=> "001A"
>>> ++$a
=> "001B"
>>> ++$a
=> "001C"
>>> $a = 'A001'
=> "A001"
>>> ++$a
=> "A002"
>>> ++$a
=> "A003"

然而请留神一些意外状况,比方这样。

>>> $a = '9E0'
=> "9E0"
>>> ++$a
=> 10.0

这是因为 9E0 被当作成了浮点数的字符串示意,被 PHP 当成了 9*10^0,被评估成了 9,而后在执行的递增。

参考起源:https://www.php.net/manual/zh…

请留神你的嵌套强制类型转换,否则他会发生意外

<?php

var_dump(TRUE === (boolean) (array) (int) FALSE);// true

var_dump((array) (int) FALSE);

因为当把 FALSE 转为数字是,他是 0,再转为数组后,就成了,[0],所以再转为 boolean 时,将会返回 true,因为数组不为空,并且 [0] != []

参考:https://www.php.net/manual/zh/language.types.type-juggling.php#115373

高版本中的数字与字符串进行比拟

自 PHP 8.0 开始。

数字与非数字模式的字符串之间的非严格比拟当初将首先将数字转为字符串,而后比拟这两个字符串。数字与数字模式的字符串之间的比拟依然像之前那样进行。请留神,这意味着 0 == “not-a-number” 当初将被认为是 false。

Comparison Before After
0 == “0” true true
0 == “0.0” true true
0 == “foo” true false
0 == “” true false
42 == ” 42″ true true
42 == “42foo” true false

参考:https://www.php.net/manual/zh/migration80.incompatible.php#migration80.incompatible.core

数组也能够间接比拟

你能够间接应用 == 比拟两个数组有雷同的键值对,如果这不是一个关联数组,那么就要保障值的程序绝对应,如果时一个关联数组,你就能够不必放心。

>>> $b = [1,2,3,4]
=> [
     1,
     2,
     3,
     4,
   ]
>>> $a = [1,2,3,4]
=> [
     1,
     2,
     3,
     4,
   ]
>>> $a == $b
=> true

// 留神,他不会比拟类型。>>> $a = [0,1,2,3,4]
=> [
     0,
     1,
     2,
     3,
     4,
   ]
>>> $b = [false,1,2,3,4]
=> [
     false,
     1,
     2,
     3,
     4,
   ]
>>> $a == $b
=> true

// 如果你要比拟类型,你应该应用 ===

>>> $a === $b
=> false

无序的比拟:
上面的列表中,应用 == 将会返回 true,因为他们的值是相等的,只是程序不同,然而如果应用 === 将会返回类型,因为 === 的时候会思考键值程序和数据类型。

>>> $a = ['name'=>'Jack','sex'=>1,'age'=>18];
=> [
     "name" => "Jack",
     "sex" => 1,
     "age" => 18,
   ]
>>> $b = ['name'=>'Jack','age'=>18,'sex'=>1];
=> [
     "name" => "Jack",
     "age" => 18,
     "sex" => 1,
   ]
>>> $a == $b
=> true
>>> $a === $b
=> false
>>>

起源:PHP: 数组运算符 – Manual

合并数组

数组还能够相加 (+),用来合并数组,应用 array_merge 能够合并数组能够把两个数组相加,相比是都晓得的,然而其实 + 号也能够,尽管都是合并数组,这两个办法各有区别。+ 更像是替换。

1、应用 array_merge 合并非关联数组时,不会过滤反复我的项目,+ 会(更像是替换)

>>> $a = [1,2,3]
=> [
     1,
     2,
     3,
   ]
>>> $b = [2,3,4]
=> [
     2,
     3,
     4,
   ]
>>> array_merge($a,$b)
=> [
     1,
     2,
     3,
     2,
     3,
     4,
   ]
>>> $a + $b
=> [
     1,
     2,
     3,
   ]

2、应用 array_merge 合并关联数组时,如果键反复,将会保留最初一个数组的值,而应用 + 将会保留第一个键上面的值。

>>> $a = ['name'=>'Jack','sex'=>1,'age'=>18];
=> [
     "name" => "Jack",
     "sex" => 1,
     "age" => 18,
   ]
>>> $b = ['name'=>'Jack','age'=>'18','sex'=>'1'];
=> [
     "name" => "Jack",
     "age" => "18",
     "sex" => "1",
   ]
>>> array_merge($a, $b)
=> [
     "name" => "Jack",
     "sex" => "1",
     "age" => "18",
   ]
>>> $a + $b
=> [
     "name" => "Jack",
     "sex" => 1,
     "age" => 18,
   ]

3、当关联数组中存在数字键时,array_merge 会重置数字键,+ 则不会

>>> $a = ['name'=>'Jack','sex'=>1,'age'=>18];
=> [
     "name" => "Jack",
     "sex" => 1,
     "age" => 18,
   ]
>>> $b = ['name'=>'Jack','age'=>'18','sex'=>'1','10'=>'hi'];
=> [
     "name" => "Jack",
     "age" => "18",
     "sex" => "1",
     10 => "hi",
   ]
>>> array_merge($a,$b)
=> [
     "name" => "Jack",
     "sex" => "1",
     "age" => "18",
     0 => "hi",
   ]
>>> $a + $b
=> [
     "name" => "Jack",
     "sex" => 1,
     "age" => 18,
     10 => "hi",
   ]

上面用一张图来概括一下。

图片起源:array_merge vs array_replace vs + (plus aka union) in PHP | SOFTonSOFA

完结

  • 文章中大部分内容来自网络收集,我曾经尽所能去验证其真实性,但可能局部会有纰漏,如果有请不吝赐教。
  • 另外,如果文中的内容进犯到了你得权利,请与我分割解决。
  • 你还能够点击文章中的起源链接,理解更具体的内容。

参考

  • PHP: PHP 手册 – Manual
  • 风雪之隅 – Laruence 的博客
  • performance – List of Big-O for PHP functions – Stack Overflow
  • array_merge vs array_replace vs + (plus aka union) in PHP | SOFTonSOFA
退出移动版