PHP 代码审计 ¶

常见的导致文件包含的函数有:

  • PHP:include(),include_once(),require(),require_once(),fopen(),readfile() 等
  • JSP Servlet:ava.io.File(),java.io.FileReader() 等
  • ASP:includefile,includevirtual 等

当 PHP 包含一个文件时,会将该文件当做 PHP 代码执行,而不会在意文件时什么类型。

本地文件包含,Local File Inclusion,LFI。

上述代码存在本地文件包含,可用 %00 截断的方式读取 /etc/passwd 文件内容。

  • %00 截断

需要 magic_quotes_gpc=off,PHP 小于 5.3.4 有效。

  • 路径长度截断

Linux 需要文件名长于 4096,Windows 需要长于 256。

  • 点号截断

只适用 Windows,点号需要长于 256。

远程文件包含,Remote File Inclusion,RFI。

构造变量 basePath 的值。

最终的代码执行了

问号后的部分被解释为 URL 的 querystring,这也是一种「截断」。

  • 普通远程文件包含

需要 allow_url_fopen=On 并且 allow_url_include=On 。

  • 利用 PHP 流 input

需要 allow_url_include=On 。

  • 利用 PHP 流 filter

需要 allow_url_include=On 。

  • 利用 data URIs

需要 allow_url_include=On 。

  • 利用 XSS 执行

需要 allow_url_fopen=On,allow_url_include=On 并且防火墙或者白名单不允许访问外网时,先在同站点找一个 XSS 漏洞,包含这个页面,就可以注入恶意代码了。

文件上传漏洞是指用户上传了一个可执行脚本文件,并通过此文件获得了执行服器端命令的能力。在大多数情况下,文件上传漏洞一般是指上传 WEB 脚本能够被服务器解析的问题,也就是所谓的 webshell 问题。完成这一攻击需要这样几个条件,一是上传的文件能够被 WEB 容器执行,其次用户能从 WEB 问这个文件,最后,如果上传的文件被安全检查、格式化、图片压缩等功能改变了内容,则可能导致攻击失败。

  • 前端检查扩展名

抓包绕过即可。

  • Content-Type 检测文件类型

抓包修改 Content-Type 类型,使其符合白名单规则。

  • 服务端添加后缀

尝试 %00 截断。

  • 服务端扩展名检测

利用解析漏洞。

  • Apache 解析

Apache 对后缀解析是从右向左的

phpshell.php.rar.rar.rar.rar 因为 Apache 不认识 .rar 这个文件类型,所以会一直遍历后缀到 .php,然后认为这是一个 PHP 文件。

  • IIS 解析

IIS 6 下当文件名为 abc.asp;xx.jpg 时,会将其解析为 abc.asp。

  • PHP CGI 路径解析

当访问 http://www.a.com/path/test.jpg/notexist.php 时,会将 test.jpg 当做 PHP 解析, notexist.php 是不存在的文件。此时 Nginx 的配置如下

  • 其他方式

后缀大小写、双写、特殊后缀如 php5 等,修改包内容的大小写过 WAF 等。

变量如果未被初始化,且能够被用户所控制,那么很可能会导致安全问题。

示例

当 register_globals=ON 时,提交 test.php?auth=1,auth 变量将自动得到赋值。

extract() 函数能够将变量从数组导入到当前的符号表,其定义为

其中,第二个参数指定函数将变量导入符号表时的行为,最常见的两个值是 EXTR_OVERWRITE 和 EXTR_SKIP。

当值为 EXTR_OVERWRITE 时,在将变量导入符号表的过程中,如果变量名发生冲突,则覆盖所有变量;值为 EXTR_SKIP 则表示跳过不覆盖。若第二个参数未指定,则在默认情况下使用 EXTR_OVERWRITE。

当 extract() 函数从用户可以控制的数组中导出变量时,可能发生变量覆盖。

import_request_variables 将 GET、POST、Cookies 中的变量导入到全局,使用这个函数只用简单地指定类型即可。

import_request_variables(\”G\”) 指定导入 GET 请求中的变量,提交 test.php?auth=1 出现变量覆盖。

parse_str() 函数通常用于解析 URL 中的 querystring,但是当参数值可以被用户控制时,很可能导致变量覆盖。

与 parse_str() 类似的函数还有 mb_parse_str()。

PHP 中有不少可以直接执行代码的函数。

preg_replace() 的第一个参数如果存在 /e 模式修饰符,则允许代码执行。

如果没有 /e 修饰符,可以尝试 %00 截断。

preg_match 执行的是匹配正则表达式,如果匹配成功,则允许代码执行。

这道题是 xman 训练赛的时候,梅子酒师傅出的一道题。这一串代码描述是这样子,我们要绕过 A-Z、a-z、0-9 这些常规数字、字母字符串的传参,将非字母、数字的字符经过各种变换,最后能构造出 a-z 中任意一个字符,并且字符串长度小于 40 。然后再利用 PHP 允许动态函数执行的特点,拼接出一个函数名,这里我们是 getFlag,然后动态执行该代码即可。

那么,我们需要考虑的问题是如何通过各种变换,使得我们能够去成功读取到 getFlag 函数,然后拿到 webshell 。

在理解这个之前,我们首先需要大家了解的是 PHP 中异或 ^ 的概念。

我们先看一下下面这段代码:

运行结果如下:

我们可以看到,输出的结果是字符 ~。之所以会得到这样的结果,是因为代码中对字符 A 和字符 ? 进行了异或操作。在 PHP 中,两个变量进行异或时,先会将字符串转换成 ASCII 值,再将 ASCII 值转换成二进制再进行异或,异或完,又将结果从二进制转换成了 ASCII 值,再将 ASCII 值转换成字符串。异或操作有时也被用来交换两个变量的值。

比如像上面这个例子

A 的 ASCII 值是 65 ,对应的二进制值是 01000001

? 的 ASCII 值是 63 ,对应的二进制值是 00111111

异或的二进制的值是 ‭01111110‬ ,对应的 ASCII 值是 126 ,对应的字符串的值就是 ~ 了

我们都知道, PHP 是弱类型的语言,也就是说在 PHP 中我们可以不预先声明变量的类型,而直接声明一个变量并进行初始化或赋值操作。正是由于 PHP 弱类型的这个特点,我们对 PHP 的变量类型进行隐式的转换,并利用这个特点进行一些非常规的操作。如将整型转换成字符串型,将布尔型当作整型,或者将字符串当作函数来处理,下面我们来看一段代码:

代码执行结果如下:

我们一起来分析一下上面这段代码:

1、$_++; 这行代码的意思是对变量名为 \”_\” 的变量进行自增操作,在 PHP 中未定义的变量默认值 null ,null==false==0 ,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字。

2、$__=\”?\” ^ \”}\”; 对字符 ? 和 } 进行异或运算,得到结果 B 赋给变量名为 __ (两个下划线) 的变量

3、$ __ (); 通过上面的赋值操作,变量 $__ 的值为 B ,所以这行可以看作是 B() ,在 PHP 中,这行代码表示调用函数 B ,所以执行结果为 Hello Angel_Kitty 。在 PHP 中,我们可以将字符串当作函数来处理。

看到这里,相信大家如果再看到类似的 PHP 后门应该不会那么迷惑了,你可以通过一句句的分析后门代码来理解后门想实现的功能。

我们希望使用这种后门创建一些可以绕过检测的并且对我们有用的字符串,如 _POST , system , call_user_func_array,或者是任何我们需要的东西。

下面是个非常简单的非数字字母的 PHP 后门:

在这里我说明下, .= 是字符串的连接,具体参看 PHP 语法

我们甚至可以将上面的代码合并为一行,从而使程序的可读性更差,代码如下:

我们回到 xman 训练赛的那题来看,我们的想法是通过构造异或来去绕过那串字符,那么我们该如何构造这个字串使得长度小于 40 呢?

我们最终是要读取到那个 getFlag 函数,我们需要构造一个 _GET 来去读取这个函数,我们最终构造了如下字符串:

可能很多小伙伴看到这里仍然无法理解这段字符串是如何构造的吧,我们就对这段字符串进行段分析。

首先我们得知道 _GET 由什么异或而来的,经过我的尝试与分析,我得出了下面的结论:

这段代码一大坨是啥意思呢?因为 40 个字符长度的限制,导致以前逐个字符异或拼接的 webshell 不能使用。这里可以使用 php 中可以执行命令的反引号 ` 和 Linux 下面的通配符 ?

  • ? 代表匹配一个字符
  • ` 表示执行命令
  • \” 对特殊字符串进行解析

由于 ? 只能匹配一个字符,这种写法的意思是循环调用,分别匹配。我们将其进行分解来看:

输出结果为:

输出结果为:

输出结果为:

所以我们可以知道, _GET 就是这么被构造出来的啦!

我们又该如何获取 _GET 参数呢?咱们可以构造出如下字串:

根据前面构造的来看, $_ 已经变成了 _GET 。顺理成章的来讲, $_ = _GET 。我们构建 $_GET[__] 是为了要获取参数值。

此时我们只需要去调用 getFlag 函数获取 webshell 就好了,构造如下:

所以把参数全部连接起来,就可以了。

结果如下:

于是我们就成功地读取到了 flag!

用户自定义的函数可以导致代码执行。

PHP 的 Curly Syntax 也能导致代码执行,它将执行花括号间的代码,并将结果替换回去。

很多函数都可以执行回调函数,当回调函数用户可控时,将导致代码执行。

攻击 payload

如果 unserialize() 在执行时定义了 __destruct() 或 __wakeup() 函数,则有可能导致代码执行。

攻击 payload

php 不会严格检验传入的变量类型,也可以将变量自由的转换类型。

比如在 $a == $b 的比较中

然而,PHP 内核的开发者原本是想让程序员借由这种不需要声明的体系,更加高效的开发,所以在几乎所有内置函数以及基本结构中使用了很多松散的比较和转换,防止程序中的变量因为程序员的不规范而频繁的报错,然而这却带来了安全问题。

在进行比较运算时,如果遇到了 0e\\d+ 这种字符串,就会将这种字符串解析为科学计数法。所以上面例子中 2 个数的值都是 0 因而就相等了。如果不满足 0e\\d+ 这种模式就不会相等。

当其中的一个字符串是 0x 开头的时候,PHP 会将此字符串解析成为十进制然后再进行比较,0x1240 解析成为十进制就是 123456,所以与 int 类型和 string 类型的 123456 比较都是相等。

常见的转换主要就是 int 转换为 string,string 转换为 int。

int 转 string

string 转 int:intval() 函数。

对于这个函数,可以先看 2 个例子。

说明 intval() 转换的时候,会从字符串的开始进行转换直到遇到一个非数字的字符。即使出现无法转换的字符串, intval() 不会报错而是返回 0。

同时,程序员在编程的时候也不应该使用如下的这段代码:

这个时候 $a 的值有可能是 1002 union。

内置函数的松散性说的是,调用函数时给函数传递函数无法接受的参数类型。解释起来有点拗口,还是直接通过实际的例子来说明问题,下面会重点介绍几个这种函数。

md5()

PHP 手册中的 md5()函数的描述是 string md5 ( string $str [, bool $raw_output = false ] ),md5() 中的需要是一个 string 类型的参数。但是当你传递一个 array 时,md5() 不会报错,只是会无法正确地求出 array 的 md5 值,这样就会导致任意 2 个 array 的 md5 值都会相等。

strcmp()

strcmp() 函数在 PHP 官方手册中的描述是 intstrcmp ( string $str1 , string $str2 ),需要给 strcmp() 传递 2 个 string 类型的参数。如果 str1 小于 str2,返回 -1,相等返回 0,否则返回 1。strcmp() 函数比较字符串的本质是将两个变量转换为 ASCII,然后进行减法运算,然后根据运算结果来决定返回值。

如果传入给出 strcmp() 的参数是数字呢?

switch()

如果 switch() 是数字类型的 case 的判断时,switch 会将其中的参数转换为 int 类型。如下:

这个时候程序输出的是 i is less than 3 but not negative ,是由于 switch() 函数将 $i 进行了类型转换,转换结果为 2。

in_array()

在 PHP 手册中, in_array() 函数的解释是 bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ) , 如果 strict 参数没有提供,那么 in_array 就会使用松散比较来判断 $needle 是否在 $haystack 中。当 strict 的值为 true 时, in_array() 会比较 needls 的类型和 haystack 中的类型是否相同。

可以看到上面的情况返回的都是 true,因为 \’abc\’ 会转换为 0, \’1bc\’ 转换为 1。

array_search() 与 in_array() 也是一样的问题。

hg init 时会产生 .hg 文件。

利用工具 dvcs-ripper

.git 目录内有代码的变更记录等文件,如果部署时该目录下的文件可被访问,可能会被利用来恢复源代码。

GitHack

GitHacker(可恢复完整 Git 仓库)

Mac OS 中会包含有 .DS_Store 文件,包含文件名等信息。

利用工具 ds_store_exp

管理员备份网站文件后错误地将备份放在 Web 目录下。

常见的后缀名:

敏感文件:

dvcs-ripper

Seay – SVN

WEB-INF 是 Java Web 应用的安全目录,web.xml 中有文件的映射关系。

WEB-INF 主要包含以下文件或目录:

  • /WEB-INF/web.xml :Web 应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
  • /WEB-INF/classes/ :包含站点所有用到的 class 文件,包括 servlet class 和非 servlet class,他们不能包含在 jar 文件中。
  • /WEB-INF/lib/ :存放 web 应用需要的各种 JAR 文件,放置仅在这个应用中要求使用的 jar 文件,如数据库驱动 jar 文件。
  • /WEB-INF/src/ :源码目录,按照包名结构放置各个 java 文件。
  • /WEB-INF/database.properties :数据库配置文件。

通过找到 web.xml 文件,推断 class 文件的路径,最后直接 class 文件,再通过反编译 class 文件,得到网站源码。 一般情况,jsp 引擎默认都是禁止访问 WEB-INF 目录的,Nginx 配合 Tomcat 做均衡负载或集群等情况时,问题原因其实很简单,Nginx 不会去考虑配置其他类型引擎(Nginx 不是 jsp 引擎)导致的安全问题而引入到自身的安全规范中来(这样耦合性太高了),修改 Nginx 配置文件禁止访问 WEB-INF 目录就好了:

取回源码

PHP 8:函数和方法

作者 | Deepak Vohra

译者 | 刘雅梦

策划 | 丁晓昀

本文属于专题文章《深入浅出 PHP 8》。 根据w3tech的数据,PHP 仍然是 Web 上使用最广泛的脚本语言之一,77.3%的网站使用 PHP 进行服务器端编程。PHP 8 带来了许多新特性和其他改进,我们将在本系列文章中进行探讨。PHP 8.0 添加了对多个函数和方法相关特性的支持,其中一些是对现有特性的改进,而另一些则是全新的特性。PHP 8.1 中增强的可调用语法可用于通过可调用对象创建匿名函数。命名函数参数可以与位置参数一起使用,另外还有一个好处,即命名参数没有顺序,可以通过它们的名称来传达含义。纤程(Fiber)是可中断的函数,增加了对多任务的支持。

对象继承是大多数面向对象语言(包括 PHP)所使用的编程范式。它可以从任何扩展类中重写公共和受保护的方法,以及在类中定义的类属性和常量。在 PHP 中,公共方法不能通过更严格的访问来重新实现,例如将 public 方法设为 private 。为了演示这一点,考虑一个扩展了类 A 的类 B,它重新实现了类 A 中一个公共方法。

运行时,脚本会生成如下的一条错误信息:

相反,在类中定义的私有方法不是继承的,可以在扩展它的类中重新实现。例如,类 B 在下面的脚本中扩展了类 A,并重新实现了类 A 中一个私有方法。

在 PHP 8.0 之前,对扩展类中私有方法的重新声明应用了两个限制:不允许更改 finalstatic 修饰符。如果 private 方法被声明为 final ,则不允许扩展类重新声明该方法。如果私有方法被声明为静态的,那么它将在扩展类中保持静态。而且,如果私有方法没有static 修饰符,则不允许扩展类添加static 修饰符。在 PHP 8 中,这两个限制都被取消了。以下脚本在 PHP 8 中能正常运行。

PHP 8 中唯一的私有方法限制是强制使用 private final 构造函数,当使用静态工厂方法作为替代时,有时会使用private final来禁用构造函数。

该脚本生成如下的错误信息:

在 PHP 8 中,单个可变参数可以替换任意数量的函数参数。考虑下面的脚本,其中类 B 扩展了类 A,并用一个可变参数替换函数 sortArray 的三个参数。

可以使用多个参数调用类 B 中的 sortArray 函数。

输出结果如下所示:

可调用(callable)是可以被调用的 PHP 表达式,例如实例方法、静态方法或可调用对象。例如,可调用可用于为方法调用创建简短的表达式。在 PHP 8.1 中,可以用新的可调用语法:

AVariableCallableExpression(…)

AVariableCallableExpression 表示一个变量可调用表达式。省略号…包含在语法中。

这有两个问题:

  1. 语法涉及字符串和数组
  2. 在创建可调用时,作用域不会被维护。为了演示这一点,请考虑如下用于对数组进行排序的脚本,其中 getSortArrayMethod() 方法返回 sortArray() 方法的可调用项,[$this,\’sortArray\’]

该脚本会生成如下的错误信息:

使用 Closure::fromCallable([$this, \’sortArray\’]) 而不是 [$this, \’sortArray\’] 可以解决作用域问题,但使用 Closure::fromCallable 方法会使调用变得冗长。新的可调用语法解决了作用域和语法冗长的问题。使用新的可调用语法,函数变为:

数组根据输出进行排序:

新语法可以与涉及字符串和数组的传统语法结合使用,以解决作用域问题。创建可调用的作用域将保持不变。

新的可调用语法也可以与静态方法一起使用,如下面的脚本所示,该脚本包含一个静态函数。

输出结果与之前的相同:

以下是调用方法的等效方法:

以下是调用静态方法的等效方法:

即使函数声明了形参,也可以使用新的可调用语法。

如果一个方法声明了任意参数,则必须使用它的参数来调用可调用对象。

简化的可调用语法可以用于任意的 PHP 可调用表达式。用于对象创建的 new 运算符不支持可调用语法,因为可调用语法 AVariableCallableExpression(…) 没有指定构造函数参数的规定,这可能是必需的。以下是不支持的示例:

生成的错误信息为:

以下的脚本演示了受支持的所有可调用表达式。

PHP 8.0 的另一个新特性是支持在函数的参数列表末尾添加一个尾逗号,以提高可读性。任何尾逗号都将被忽略。尾逗号可能并不总是有用的,但如果参数列表很长,或者参数名称很长,则可能会有用,因此可以垂直列出它们。闭包使用列表也支持尾逗号。

PHP 8.0 不支持在必选参数之前声明可选参数。在必选参数之前声明的可选参数都是隐式的必选参数。

下面的脚本演示了必选参数的隐式顺序,以及尾逗号的使用。

输出如下所示:

可空参数不会被视为可选参数,可以使用 $param=null 形式或显式可空类型在必选参数之前声明,脚本如下所示:

PHP 8.0 除了已经支持的位置形参和实参之外,还增加了对命名函数形参和实参的支持。命名参数在函数调用中的传递语法如下所示:

命名参数的一些好处如下所示:

  • 可以为函数参数指定一个有意义的名称,使它们能够自我记录
  • 按名称传递时,参数与顺序无关
  • 可以任意跳过默认值。在下面的脚本中, array_hashtable 函数声明了命名参数。 该函数传递的实参值可以带参数名,也可以不带参数名。当传递位置实参时,使用函数形参声明顺序。但传递命名实参时,可以使用任意顺序。

输出结果为:

命名实参和位置实参可以在同一函数调用中使用。对相同的示例函数 array_hashtable 一起使用混合参数调用。

输出结果为:

请注意,命名参数只能用于位置参数之后。下面的脚本颠倒了顺序,在命名参数之后使用位置参数:

该脚本生成的错误信息为:

即使使用命名参数,也不推荐在必选参数之前声明可选参数,脚本如下所示:

输出将包括已弃用(不推荐)信息:

当在必选命名形参之后使用可选命名形参时,命名实参可用于跳过函数调用中的一个或多个可选形参,脚本如下所示:

输出结果为:

你可以只使用可选参数的子集来调用函数,而不用考虑它们的顺序。

输出结果如下所示:

即使使用可选参数的子集调用函数,也不能在命名参数之后使用位置参数,脚本如下所示:

生成的错误信息以下所示:

PHP 8.1 改进了命名实参特性,在解包实参后支持命名实参,脚本如下所示:

输出如下所示:

但是,命名的参数不能盖前面的参数,脚本如下所示:

输出如下所示:

在 PHP 8.0 之前,如果在静态上下文中调用非静态方法,或者静态调用,则只会收到一条已弃用(不推荐)的信息。使用 8.0,你现在会收到一条错误信息。此外, $this 在静态上下文中是未定义的。为了演示这一点,请考虑如下的脚本,其中使用静态语法 A::aNonStaticMethod() 调用了非静态方法 aNonStaticMethod()

如果你运行这个脚本,将会得到如下的错误信息:

PHP 8.1 添加了对纤程(Fiber)多任务的支持。纤程是一个可中断的函数,它具有自己的堆栈。纤程可以从调用堆栈中的任何位置挂起,然后再恢复。新的 Fiber 类是一个 final 类,它支持以下的公共方法:

纤程只能启动一次,但可以挂起并恢复多次。下面的脚本通过使用纤程在数组上执行不同类型的排序来演示多任务处理。纤程在每次排序后都会挂起,然后再恢复执行不同类型的排序。

输出如下所示:

如果纤程在第一次挂起后没有再恢复,则只进行一种类型的排序,这可以通过注释掉对 resume() 的两次调用来实现。

输出的是第一次排序的结果:

PHP 8.0 引入了一个名为 Stringable 的新接口,它只提供一个方法 __toString()__toString() 方法如果在类中提供,将隐式实现 Stringable 接口。考虑提供 __toString() 方法的类 A。

该脚本从 Stringable 的类型检查中返回 1。

然而,反之则不然。如果类实现了 Stringable 接口,则必须显式提供 __toString() 方法,因为该方法不会自动添加,比如:

PHP 8 引入了许多属于其标准库的新函数。

str_contains 函数返回一个 bool 值,用于指示作为第一个参数的字符串是否包含作为第二个参数的字符串。以下脚本将返回 false

下面的脚本返回 1,或 true:

str_starts_with 函数返回一个bool 值 ,指示作为第一个参数的字符串是否以作为第二个参数的字符串开头。以下脚本将返回 false

下面的脚本将返回 1,或 true。

str_ends_with 函数返回一个bool 值 ,指示作为第一个参数的字符串是否以作为第二个参数的字符串结尾。以下脚本将返回 false

下面的脚本将返回 1,或 true。

fdiv 函数将两个数字相除并返回一个 float 值,脚本如下所示:

输出为:

fdatasync 函数在 Windows 上的别名为 fsync ,用于将数据同步到文件上的流中。为了演示它的用法,在包含要运行的 PHP 脚本的脚本目录中创建一个空文件 test.txt 。运行脚本:

随后,打开 test.txt 文件会发现包含如下的文本:

array_is_list 函数返回布尔值,用于指示给定的数组是否为列表。数组必须从 0 开始,键必须是连续的整数,并且顺序正确。下面的脚本演示了 array_is_list 函数:

输出为:

魔术方法是 PHP 中用于覆盖默认操作的特殊方法。它们包括如下的方法,其中构造函数 __construct() 可能是大家最熟悉的:

从 PHP 8.0 开始,魔术方法定义的签名必须要是正确的,这意味着如果在方法参数或返回类型中使用类型声明,则它们必须与文档中的声明相同。新的 __toString() 方法的返回类型必须要声明为 string 。下面的演示将返回类型声明为 int

将生成如下的错误信息:

但是,未通过定义声明返回类型的函数(如构造函数)不能声明返回类型,即使是 void 返回类型也不行。示例如下脚本所示:

该脚本将返回如下的错误信息:

所有魔术方法,除了少数例外(例如 __construct() )外,都必须声明为具有公共可见性。为了演示这一点,声明了一个带有 private 可见性的 __callStatic

输出的警告信息为:

尽管可以省略混合返回类型,但方法签名也必须相同。例如,在下面的脚本中,类 A 声明了 __callStatic 而没有指定其返回类型,而类 B 将其第一个参数定义为int

输出的错误信息如下所示:

在 PHP 8.1 中,大多数内部方法,即内部类中的方法,都已经“试探性地”开始声明返回类型。试探性地暗示,虽然在 8.1 中只会引发不推荐(Deprecation)通知,但在 9.0 版中,则会输出错误条件信息。因此,任何扩展类都必须声明与内部类相兼容的返回类型,否则将会引发已弃用(不推荐)通知。为了演示这一点,扩展内部类 Directory 并重新声明没有返回类型的函数 read()

该脚本将生成已弃用(不推荐)通知:

但是,以下脚本是可以的:

添加 #[\\ReturnTypeWillChange] 属性能抑制已弃用(不推荐)通知:

虽然包含有关方法参数的详细信息的异常堆栈跟踪对调试非常有用,但你可能不希望输出某些敏感参数的参数值,例如与密码和凭据关联的参数值。PHP 8.2 引入了一个名为 \\SensitiveParameter 的新属性,这样,如果使用 \\SensitivyParameter 属性注解方法的参数,则该参数的值不会在异常堆栈跟踪中输出。

为了演示这一点,考虑下面的脚本,其中函数 f1 具有与 \\SensitiveParameter 属性关联的参数 $password

为了演示 \\SensitiveParameter 特性,该函数只是抛出一个异常。调用函数:

请注意,异常堆栈跟踪不包含 $password 参数的值,而是列出了 Object(SensitiveParameterValue)

内置函数 utf8_encode()utf8_decode() 经常被误解,因为它们的名称意味着对任何字符串进行编码/解码。实际上,这些函数仅用于编码/解码 ISO8859-1,即“Latin-1”字符串。此外,它们生成的错误信息对于调试来说描述性不够。PHP 8.2 已经弃用了这些函数。下面的脚本使用了它们:

对于 PHP 8.2,会输出已弃用(不推荐)通知:

在 PHP 8.2 中,函数 iterator_countiterator_to_array 接受所有可迭代的对象。 iterator_to_array() 函数将迭代器的元素复制到数组中。 iterator_count() 函数对数组的元素进行计数。这些函数接受一个 $iterator 作为第一个参数。在 PHP 8.2 中,$iterator 参数的类型已从 Traversable 扩展为 Traversable|array ,以便接受任意的可迭代值。

下面的脚本演示了它们在 arraysTraversables 中的使用。

输出如下所示:

在这篇 PHP 8 系列文章中,我们讨论了与函数和方法相关的新特性,其中最突出的是命名函数的形参/实参、简化的可调用语法和被称为纤程(Fiber)的可中断函数。

在本系列的下一篇文章中,我们将介绍 PHP 类型系统的新特性。

原文链接:

https://www.infoq.com/articles/php8-functions-methods/

相关阅读:

PHP 8:注解、match 表达式及其他改进

PHP 8:类和枚

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。