PHP8-揭秘-二-

64 阅读10分钟

PHP8 揭秘(二)

原文:PHP 8 Revealed

协议:CC BY-NC-SA 4.0

十三、杂项

类构造函数属性提升

在以前的 PHP 版本中,定义一个简单的值对象所需的声明是冗长而重复的。

class Pants {
    public float $x;
    public float $y;
    public float $z;

    public function __construct(
        float $x = 0.0,
        float $y = 0.0,
        float $z = 0.0,
    ) {
        $this->x = $x;
        $this->y = $y;
        $this->z = $z;
    }
}

在这个新特性中,PHP 为实现一个新类提供了一个更优化的方法。

class Pants {
    public function __construct(
        public float $x = 0.0,
        public float $y = 0.0,
        public float $z = 0.0,
    ) {}
}

限制

不允许使用非抽象构造函数,这将导致错误。

// Error: Not a constructor.
function pants(private $x) {}

abstract class Pants {
    // Error: Abstract constructor.
    abstract public function __construct(private $x);
}

interface Pants {
    // Error: Abstract constructor.
    public function __construct(private $x);
}

如果使用 traits,类构造函数属性提升将被允许。提升的属性还必须使用可见性关键字之一(public、private、protected)。不支持使用var

class Pants {
    // Error: "var" keyword is not supported.
    public function __construct(var $foo) {}
}

与普通属性声明相同的限制也适用于通过提升参数声明的属性。这意味着不可能两次声明同一个属性。

class Test {
    public $prop;

    // Error: Redeclaration of property.
    public function __construct(public $prop) {}
}

可调用类型不可用,因为它们不支持作为属性类型。

class Test {
    // Error: Callable type not supported for properties.
    public function __construct(public callable $callback) {}
}

新的 fdiv()函数

fdiv()函数 将执行浮点除法,同时将除以零视为完全合法的操作,在这种情况下不会发出任何类型的诊断。相反,它将返回 IEEE-754 要求的 INF/-INF/NAN 结果。它镜像了现有的 fmod() 功能

—Nikita Popov

*目前,被零除会导致不可预测的行为。根据 PHP 8 中新的引擎警告,波波夫想在fmod()intdiv()家族中加入fdiv()

让我们看一些这种行为的例子。

$output = intdiv(1, 0);
Fatal error: Uncaught DivisionByZeroError:
$output = 1 % 0;
Fatal error: Uncaught DivisionByZeroError
----
actually dividing by zero does not behave the same.
$output = 1 / 0;
Warning: Division by zero.

fdiv将遵循 IEEE-754 语义并返回INF/-INF/NaN而不抛出关键错误。

总是为不兼容的方法签名生成致命错误

日期:2019-04-08

作者:尼基塔·波波夫

表决结果:39 票赞成、3 票反对

这是又一次错误升级。不兼容的方法签名总是会引发致命错误。以前的版本会抛出警告或致命错误,但是 PHP 8 把所有的错误都归为致命错误。

从负索引开始的数组

版本:0.4

日期:2017-04-20

作者:佩德罗·麦哲伦

表决结果:17 票赞成、2 票反对

任何第一个数字键有数字n的给定数组都将被隐式地分配下一个键为n+1(如果 n > = 0)或0(如果 n < 0)。PHP 8 对此不会有不同的处理,总是将下一个键指定为n+1,而不管第一个键的值。

在 ext/dom 中实现新的 DOM 生活标准 API

版本:0.3

日期:2019-09-15

作者:Benjamin eberhee(beber lei @ PHP . net),thomas weinert

表决结果:37 票赞成

最初的文档对象模型(DOM)是作为 HTML 和 XML 的接口而创建的,由 W3 在 2004 年建立,但已被 Web 超文本应用技术工作组(WHATWG)接管,并转化为生活标准。之所以采用这个标准,是因为新的 API 改进了数据的遍历和操作。这种采用也确保了随着标准的发展,PHP 支持也会发展。与标准本身有一些偏差,主要是因为它是为浏览器或 JavaScript 编写的,PHP 必须适应这些情况。

实施

<?php

interface DOMParentNode
{
    /** access to the first child of this node that is a DOMElement */
    public readonly ?DOMElement $firstElementChild;

    /** access to the last child of this node that is a DOMElement */
    public readonly ?DOMElement $lastElementChild;

    /** counts all child nodes that are DOMElements */
    public readonly int $childElementCount;

    /** appends one or many nodes to the list of children behind the last child node */
    public function append(...DOMNode|string|null $nodes) : void;

    /** prepends one or many nodes to the list of children before the first child node */
    public function prepend(...DOMNode|string|null $nodes) : void;
}

class DOMDocument implements DOMParentNode {}
class DOMElement implements DOMParentNode {}
class DOMDocumentFragment implements DOMParentNode {}

interface DOMChildNode
{
    /** Returns the previous node in the same hierarchy that is a DOMElement or NULL if there is none */
    public readonly ?DOMElement $previousElementSibling;

    /** Returns the next node in the same hierachy that is a DOMElement or NULL if there is none */
    public readonly ?DOMElement $nextElementSibling;

    /** acts as a simpler version of $element->parentNode->removeChild($element); */
    public function remove() : void;

    /** add passed node(s) before the current node */
    public function before(...DOMNode|string|null $nodes) : void;

    /** add passed node(s) after the current node */
    public function after(...DOMNode|string|null $nodes) : void;

    /** replace current node with new node(s), a combination of remove() + append() */
    public function replaceWith(...DOMNode|string|null $nodes) : void;
}
class DOMElement implements DOMChildNode {}
class DOMCharacterData implements DOMChildNode {}

生活标准包含一个中间特征(接口)DOMNonDocumentTypeChildNode,它定义了previousElementSiblingnextElementSibling属性。PHP 不允许接口声明属性;因此,该接口不可用,但是属性在实现DOMChildNode的每个类上都可用。另外两种方法,querySelectorquerySelectorAll,也被排除在这个植入之外。这些是面向层叠样式表(CSS)选择的,已经有特定的库(PhpCss 或 Symfony CSS 选择器)可以更好地处理这一功能。

静态返回类型

日期:2020 年 1 月 8 日

作者:尼基塔·波波夫

实现: https://github.com/php/php-src/pull/5062

投票结果:54 票赞成

目前,使用静态特殊类名指的是调用方法的实际类。无论该方法是否被继承,这都适用。这被称为后期静态绑定 (LSB)。LSB 通过存储最后一个非转发调用中命名的类来工作。对于静态方法调用,这是显式命名的类(通常在 :: 运算符的左边);对于非静态方法调用,它是对象的类。转移呼叫是由self::parent::static::引入的静态呼叫,如果在等级结构中向上,还有forward_static_call()

class A {
    public function test(): self {}
}
class B extends A {
    public function test(): static {}
}
class C extends B {}
class A {
    public function test(): A {}
}
class B extends A {}
class C extends B {
    public function test(): static {}
}

变量语法调整

这是为了更新 PHP 7 中的统一变量语法 RFC。

日期:2020 年 1 月 7 日

作者:尼基塔·波波夫

实现: https://github.com/php/php-src/pull/5061

表决结果:47 票赞成

PHP 中有四种主要类型的“取消引用”操作:

  • 数组:$foo[$bar], $foo{$bar}

  • 对象:$foo->bar, $foo->bar()

  • 静态:Foo::$bar, Foo::bar(), Foo::BAR

  • 通话:foo()

插值和非插值字符串

目前,像"pants"这样的非插入字符串被认为是完全不可引用的;也就是说,像"pants"[0]"pants"->shorts()这样的结构在语法上是合法的。插入的字符串如"pants$shorts"是不可区分的。

魔法、类和常规常量

__PANTS__这样的魔法常量现在将被视为普通常量,并允许数组去引用。

PANTS[0] and __PANTS__[0]

类似地,使用PANTS{0}PANTS->length().可以取消对类常量的引用

类常数可取消引用性

目前Foo::$bar::$baz是合法的,而Foo::BAR::$baz不是。PHP 8 将改变这一点,使Foo::BAR::$bazFoo::BAR::BAZ成为合法的。

对 new 和 instanceof 的任意表达式支持

PHP 8 还将引入语法new (expr)$x instanceof (expr).

添加字符串接口

版本:0.9

日期:2020 年 1 月 15 日

作者:尼古拉斯·格雷卡斯

表决结果:29 / 9

stringable 接口将为所有实现__toString()方法的类添加一个 stringable 接口。这个接口有两个用途。第一个是允许使用string|Stringable来表达string|object-with-__toString().,第二个是提供从 PHP 7 到 PHP 8 的正向升级路径。

获取调试类型

日期:2020 年 2 月 15 日

作者:马克·兰道尔

表决结果:42 票赞成、3 票反对

PHP 8 的这一新增功能不同于显而易见的兄弟get_type(),它返回本机类型名int而不是integer,同时还解析类名。这个函数的主要目的是在处理 PHP 在运行时无法处理的类型时,通过现有的基于参数类型的检查来代替更复杂的过程。特别是在处理数组中的参数类型时,这是非常有益的。

$pants = $arr['key'];
if (!($pants instanceof Hive)) {
    throw new TypeError('Expected ' . Hive::class . ' got ' . (is_object($pants) ? get_class($pants) : gettype($pants)));
}

表 13-1 列出了gettypeget_debug_type的不同返回值。

表 13-1

gettypeget_debug_type的返回值

|

价值

|

get_debug_type()

|

gettype()

| | --- | --- | --- | | Zero | (同 Internationalorganizations)国际组织 | 整数 | | Zero point one | 漂浮物 | 两倍 | | 真实的 | 弯曲件 | 布尔 | | 错误的 | 弯曲件 | 布尔 | | "你好" | 线 |   | | [ ] | 排列 |   | | 空 | 空 | 空 | | 名为“Pants\Hive”的类 | 裤子\蜂巢 | 目标 | | 匿名班级 | 匿名类 | 目标 | | 一种资源 | 资源(xxx) | 资源 | | 封闭资源 | 资源(已关闭) |   |

// PHP 8 would allow for
if (!($pants instanceof Hive)) {
  throw new TypeError('Expected ' . Hive::class . ' got ' . get_debug_type($pants));
}
$pants>someHiveMethod();

这在 PHP 中很受欢迎,几乎没有争议。波波夫在投票时给出了更多的细节。

考虑到命名的讨论,我认为这里有一个重要的部分需要强调:对于匿名类,它只返回 "class@anonymous" ,而不是 "class@anonymous\0SOME_RANDOM_UNIQUE_STRING_HERE" 。因而这个功能肯定不能在一个 ::type 下构造(这个应该返回全称),甚至 get_canonical_type() 似乎也不合适

该函数特别适用于在错误信息或类似信息中包含类型名称,因此 get_debug_type()

—Nikita Popov

New preg_last_error_msg()

作者:尼科·奥尔加特

PHP 有一个标准的函数类,称为 Perl 兼容正则表达式(PCRE)。PCRE 函数允许 PHP 中正则表达式的标准使用。在以前的版本中,PHP 用户只能得到返回错误代码的preg_last_error()。使用preg_last_error_msg(),我们现在可以改变这一点:

<?php
preg_match('/(?:\D+|<\d+>)*[!?]/', 'foobar foobar foobar');
var_dump(preg_last_error()); // 2
var_dump(preg_last_error_msg()); // Backtrack limit was exhausted

添加 CMS 支持

日期:2020 年 5 月 13 日

作者:艾略特·李尔

不,这与 WordPress、Laravel、Drupal 或类似的软件无关!加密消息语法(CMS)是 PKCS#7 的较新版本。由 RSA Security,LLC 开发的 PKCS#7(公钥加密标准)是一种标准,其中数字签名和证书的生成和验证由公钥基础设施(PKI)管理。这是目前在电子邮件和物联网设备中使用的加密标准。

PKCS#7 和 OpenSSL 函数之间的关系如表 13-2 所示。

表 13-2

PKCS#7 和 OpenSSL 函数

|

PKCS#7 函数

|

新 CMS 功能

| | --- | --- | | openssl_pkcs7_encrypt() | openssl_cms_encrypt() | | openssl_pkcs7_decrypt() | openssl_cms_decrypt() | | openssl_pkcs7_sign() | openssl_cms_sign() | | openssl_pkcs7_verify() | openssl_cms_verify() | | openssl_pkcs7_read() | openssl_cms_read() |

function openssl_cms_sign(string $infile, string $outfile, $signcert, $signkey, ?array $headers, int $flags = 0, int $encoding = OPENSSL_ENCODING_SMIME, ?string $extracertsfilename = null): bool {}

此函数使用 X.509 证书和密钥对文件进行签名。论据如下。

  • $infile:待签名文件的名称

  • $outfile:存放结果的文件的名称

  • $signcert:包含签名证书的文件的名称

  • $signkey:包含与$signcert关联的密钥的文件名

  • $headers:要包含在 S/MIME 输出中的标题数组

  • $flags:要传递给cms_sign()的标志

  • $encoding:输出文件的编码

  • $extracertsfilename:签名中包含的中间证书

function openssl_cms_verify(string $filename, int $flags = 0, string $signerscerts = UNKNOWN, array $cainfo = UNKNOWN, string $extracerts = UNKNOWN, string $content = UNKNOWN, string $pk7 = UNKNOWN, string $sigfile = UNKNOWN, $encoding = OPENSSL_ENCODING_SMIME ): bool {}

此函数使用指定的编码验证 CMS 签名,无论是附加的还是分离的。

以下是论点:

  • $filename:输入文件

  • $flags:将被传递给cms_verify的标志

  • $signercerts:签名者证书和可选的中间证书的文件

  • $cainfo:包含自签名认证机构证书的数组

  • $extracerts:包含附加中间证书的文件

  • $content:分离签名时指向内容的文件

  • $pk7:保存签名的文件

  • $encoding:支持的三种编码之一(PEM/DER/SMIME)。

如果成功,则返回TRUE,如果失败,则返回FALSE

function openssl_cms_encrypt(string $infile, string $outfile, $recipcerts, ?array $headers, int $flags = 0, int $encoding = OPENSSL_ENCODING_SMIME,  int $cipher = OPENSSL_CIPHER_RC2_40): bool {}

该函数根据传递给它的证书对一个或多个接收者的内容进行加密。

以下是论点:

  • $infile:要加密的文件

  • $outfile:输出文件

  • $recipcerts:要加密的收件人

  • $headers:使用 S/MIME 时要包含的标题

  • $flags:要传递给CMS_sign的标志

  • $encoding:要输出的编码

  • 要使用的密码

如果成功,则返回值TRUE,如果失败,则返回值FALSE

function openssl_cms_decrypt(string $infilename, string $outfilename, $recipcert, $recipkey = UNKNOWN, int $encoding = OPENSSL_ENCODING_SMIME): bool {}

这个函数解密一个 CMS 消息。以下是论点:

  • $infilename:包含加密内容的文件的名称

  • $outfilename:存放解密内容的文件名

  • $recipcert:包含接收方证书的文件的名称

  • $recipkey:包含 PKCS#8 密钥的文件的名称

  • $encoding:输入文件的编码。

该函数成功时返回TRUE,失败时返回FALSE

function openssl_cms_read(string $infilename, &$certs): bool {}

该功能完全模拟openssl_pkcs7_read()

还包括新的常数。

OPENSSL_ENCODING_CMS /* encoding is a CMS-encoded message */
OPENSSL_ENCODING_DER /* encoding is DER (Distinguished Encoding Rules) */
OPENSSL_ENCODING_PEM /* encoding is PEM (Privacy-Enhanced Mail) */
OPENSSL_CMS_DETACHED
OPENSSL_CMS_TEXT
OPENSSL_CMS_NOINTERN
OPENSSL_CMS_NOVERIFY
OPENSSL_CMS_NOCERTS
OPENSSL_CMS_NOATTR
OPENSSL_CMS_BINARY
OPENSSL_CMS_NOSIGS

对象上的 Allow ::class

日期:2020 年 1 月 9 日

作者:尼基塔·波波夫

投票结果:60 / 0

在 PHP 的早期版本中,常量::class允许访问完全限定的类名。此常数考虑了使用和当前可用的命名空间。

namespace 311\Albums;
use Grass\Roots;
use Roots\Music as Album;

class Pants {}
// `use` statement
echo Roots::class; // 'Grass\Roots"

// `use` X `as` Y
echo Album::class; // "Roots\Music"

// Current namespace
echo Albums::class; // "311\Albums\Pants"

以前版本的 PHP 在对象上使用::class时会抛出致命错误,但在 PHP 8 中,这种情况现在可以按预期执行。

$object = new Grass\Roots;
echo $object::class;
// "Grass\Roots"

如果$object是一个object,那么$object::class返回get_class($object)。否则它抛出一个TypeError异常。

$object = new stdClass;
var_dump($object::class); // "stdClass"

$object = null;
var_dump($object::class); // TypeError

基于对象的 token_get_all()替代

日期:2020 年 2 月 13 日

作者:nikic@php.net

实现: https://github.com/php/php-src/pull/5176

表决结果:47 票赞成

Popov 提出的PhpToken::getAll()方法是对token_get_all()的替代,它将返回一个PhpToken对象的数组,而不是字符串和数组的混合。这主要有两个原因。首先,返回一个对象数组将使返回结构标准化。现在,开发人员只需要期待对象的数组,而不是单个字符串或数组的可能性。第二个也是最有益的是使用数组时内存使用的减少。

Default:
    Memory Usage: 14.0MiB
    Time: 0.43s (for 100 tokenizations)
TOKEN_AS_OBJECT:
    Memory Usage: 8.0MiB
    Time: 0.32s (for 100 tokenizations)

此外,还包含了方法getTokenName(),它主要用于调试目的。对于 ID 小于 256 的单字符令牌,它返回与 ID 对应的扩展 ASCII 字符。对于已知的令牌,它返回与token_name()相同的结果。对于未知令牌,它返回 null。

下面是新的类实现:

class PhpToken {
    /** A T_* constant, or an integer < 256 representing a single-char token. */
    public int $id;
    /** The context of the token. */
    public string $text;
    /** starting line number (1-based) of the token. */
    public int $line;
    /** starting position (0-based) in the tokenized string. */
    public int $pos;
/**
     * Same as token_get_all(), but returning array of PhpToken.
     * @return static[]
     */
    public static function getAll(string $code, int $flags = 0): array;

    final public function __construct(int $id, string $text, int $line = -1, int $pos = -1);
    /** Get the name of the token. */
    public function getTokenName(): ?string;

因为PhpToken::getAll()方法返回static[],所以扩展这个类很容易。

class thePhpToken extends PhpToken {
    public function getLowerText() {
        return strtolower($this->text);
    }
}

$tokens = thePhpToken::getAll($code);
var_dump($tokens[0] instanceof thePhpToken); // true
$tokens[0]->getLowerText(); // works

抽象特征方法的验证

日期:2020 年 2 月 7 日

作者:尼基塔·波波夫

实现: https://github.com/php/php-src/pull/5068

投票结果:52 票对 0 票

PHP 中的特征类似于类,但是可以在多个实例中重用。例如:

<?php
trait CustomReturn {
    function getFirstReturnType() { /*1*/ }
    function getFirstReturnDesc() { /*2*/ }
}

class thisFakeMethod extends FakeMethod {
    use CustomReturn;
    /* ... */
}

class thisFakeFunction extends FakeFunction {
    use CustomReturn;
    /* ... */
}
?>

PHP 8 将总是根据独立于其来源的实现方法来验证抽象特征方法的签名。如果实现方法与抽象特征方法不兼容,就会产生致命错误。不相容的定义如下:

  • 签名必须兼容,包括奇偶校验兼容、逆变参数类型兼容和协变返回类型兼容。

  • 方法的静态性必须匹配。

此外,现在可以只在 traits 中声明抽象私有方法了。抽象私有方法在术语上是矛盾的,因为声明实现的方法从发布需求的类中是不可见的。然而,trait 提供了对抽象私有方法的明确访问,因为 trait 方法可以访问 using 类的私有方法。

抛出表达式

日期:2020 年 3 月 21 日

作者:Ilija Tovilo

实现: https://github.com/php/php-src/pull/5279

表决结果:46 票赞成,3 票反对

Throwcatch一起用于处理编程逻辑中的异常。被一个try块包围着,你可以抛出一个异常来捕获和处理,而不是破坏代码。但是Throw只在这类场景中使用过,不能作为表达式使用。这将允许使用带有箭头函数的throw、联合操作符和三元/elvis 操作符。

$pants = fn() => throw new Exception();
$pants = $nullyMcNull ?? throw new NullException();
$pants = $isFalse ?: throw new FalseException();
$pants = !empty($array)
    ? reset($array)
    : throw new EmptyException();
$isWinning && throw new Exception();
$isWinning || throw new Exception();
$isWinning and throw new Exception();
$isWinning or throw new Exception();

优先级也将变得重要。throw语句之后的所有内容将被认为具有更高的重要性。也就是说,throw将采用最低的操作符优先级。这是有意义的,因为一般来说,throw应该在逻辑语句的末尾。

throw (static::wrongPasswordException());
throw ($cheeseIsFree ? new payForCheeseException() : new noCheeseException());
throw ($foreverAlone ?? new Exception());
throw ($lightning = new Exception());
throw ($promises ??= new words());
throw ($fu && $gazi ? new selling() : new buying());

需要断言特殊性的一个地方是短路操作符。如果选择这样做,必须使用括号来区分优先级。

$transistor || (throw new Exception('$transistor must be resistor') && $unplugged || (throw new Exception('$unplugged must be resistor')));

独立于区域设置的浮点到字符串转换

版本:1.0

日期:2020 年 3 月 11 日

作者:乔治·彼得·班亚,梅特·科 csis

实现: https://github.com/php/php-src/pull/5224

表决结果:42 票赞成,1 票反对

在 PHP 中将浮点数转换成字符串已经成为一个问题有一段时间了。出现此问题是因为不同的国家表示十进制字符(。或者,)不同地。例如,3.14 在美国是相当标准的,可以确定为圆周率值的开始。然而,如果你在波兰发展,你会使用“3,14”。这种差异的主要问题是,当使用floatstring的转换时,由于地区不同,结果会不一致。这一增加将用“.”使这些问题标准化作为主要的小数占位符。

setlocale(LC_ALL, "pl_PL");
$f = 3.11;
(string) $f;            // 3,11 would become 3.11
strval($f);             // 3,11 would become 3.11
print_r($f);            // 3,11 would become 3.11
var_dump($f);           // float(3,11) would become float(3.11)
debug_zval_dump($f);    // float(3,11) would become float(3.11)
settype($f, "string");  // 3,11 would become 3.11
implode([$f]);          // 3,11 would become 3.11
xmlrpc_encode($f);      // 3,11 would become 3.11

这种行为在 userland 并不是前所未有的。例如,PDO 扩展利用这一点来标准化浮点的字符串表示。在var_exportserializejson_encode中也使用了语言环境独立性。然而,printf已经有了一个用%f指定非比例感知转换的选项,保持不变。

非捕获捕获

版本:0.9

日期:2020 年 4 月 5 日

作者:马克斯·塞姆尼克

实现: https://github.com/php/php-src/pull/5345

表决结果:48 票赞成,1 票反对

简而言之,我希望能够做到以下:

请分享你的想法!:)

这名男子的名字在纽约一名 2013-16 岁的男子家中被杀。

我看起来不错。

—斯塔斯·马利舍夫

try {
foo();
catch (SomeExceptionClass) {
bar();
}}

编程中的try / catch范例允许定义一个代码块并测试其错误(try),同时定义另一个代码块来处理所述错误(catch)。在 PHP 中,这是通过使用catch块中的一个变量来实现的,这个变量将自己分配给错误。

try {
    foo();
} catch (weekendException $ex) {
    die($ex->showMessage());
}

会变成

try {
    foo();
} catch (weekendException) {
    echo "This does not work on the weekend";
}

这种增加不再需要变量的说明。catch的意图很清楚,因此不需要任何其他细节。

始终可用的 JSON 扩展

版本:0.3

日期:2020 年 4 月 29 日

作者:泰森·安德烈

实现: https://github.com/php/php-src/pull/5495

票数:56 票/ 0 票

通过这次添加,PHP 团队迈出了将 JSON 从 PECL 扩展转变为 PHP 核心特性的第一步。目前,在 PHP 版本中禁用 JSON 的唯一方法是通过./configure --disable-json,这使得代码有可能要求 JSON 出现故障。在以前的 PHP 版本中禁用 JSON 的唯一原因是由于许可问题,这个问题已经解决了。

然而,这种改变会有向后兼容性的问题。脚本或命令行界面(CLI)指令中使用的--enable-json or --disable-json将需要更新,因为这些选项将不再存在。这同样适用于任何使用extension_loaded('json'),的代码,因为这将始终是true

Zend . exception _ String _ param _ max _ len:getTraceAsString()中可配置的字符串长度

自 2003 年以来,两个非常有用的功能被限制为 15 字节的信息。Throwable->getTraceAsString()Throwable->__toString()被期望将堆栈跟踪返回给急切地等待挤压他们代码中的 bug 的开发人员。直到 PHP 8,这些都返回了类似于"/path/to/the/vesion/file.php 1349 function(whe ...",的半有用的字符串,考虑到路径、URL 和 UUIDs 的使用,这些信息实际上是不够的。

这个问题的解决方案是一个.ini设置zend.exception_string_param_max_len,它允许将字符串的字节限制更改为从 0 到 1000000 的任何值。如果没有设置值,默认值将保持为 15。然而,与此相关的一个问题是,随着暴露数据的增加,无意中获得敏感数据的风险也会增加。

function badHTMLRenderingExample(string $secretCode, string $secretPassword) {
   echo "<h1>Welcome AOL</h1>\n";
   try {
       process($secretCode);
   } catch (Exception $e) {
       // The output will include both $secretCode and $secretPassword.
       // in PHP 7x, only 15 bytes would be displayed.
       echo "ID: 10 T error, please feed the dev team: $e\n";
   }
}

15 字节的默认设置将保持实际执行此操作的遗留代码的安全性,除非.ini设置被更改。

解包 ext/xmlrpc

版本:1.0

日期:2020 年 5 月 12 日

作者:克里斯托弗·m·贝克尔

通过 https://github.com/php/php-src/pull/5640 进行拆分

票数:50 / 0

似乎并没有强烈的需求去暗示人们应该尽快停止使用它。扩展部分(更重要的是,底层库)已经很多年没有维护了,搬到 PECL 不会在这方面有实质性的改变。

*——*Nikita Popov

目前,是 PHP 中一个不可避免的弊端。对于那些不了解的人来说,XML-RPC 是允许在系统之间使用 XML 的规范,因此是必要的。尽管 XML 本质上并不邪恶,但出于多种原因,目前这种移植的使用是邪恶的。第一,ext/xmlrpc依赖于被抛弃的libxmlrpc-epi。这种放弃可以通过滚动 xmlrpc-epi-dev 邮件列表中当前的垃圾邮件数量来验证。这是第一击。打击二是 PHP 目前实现的是 0.51 版本,但最新版本是 0.54。一个解决方案是控制库并继续更新和维护。这不是 PHP 团队的目标或宗旨。该提案已提交给 unbundle ext/xmlrpc,这意味着它将被视为通过 PECL 的第三方扩展,因此选择使用或不使用它成为最终用户的决定和责任。这里需要注意的是,团队并不认为ext/xmlrpc是无用的或者需要被废弃。API 和功能按预期工作。这仅仅是为这个库或 PHP 关于 XML 的下一步做准备的一步。

不要在 getMetadata()之外自动取消 Phar 元数据的序列化

版本:0.4

日期:2020 年 7 月 7 日

作者:泰森·安德烈

实现: https://github.com/php/php-src/pull/5855

票数:25 票/ 0 票

除了它吸引人的名字,PHP 8 的这一新增功能带来了一个急需的安全更新。快速搜索“php phar 流包装器漏洞”将返回几个影响 Drupal、WordPress、Prestashop 等的结果。基本上今天互联网上的大多数网站在 PHP 8 之前都是易受攻击的。正如 RFC 指出的,“由于对象实例化和自动加载,非序列化会导致代码被加载和执行,恶意用户可能会利用这一点。”

提案

当 PHP 打开 phar 文件时,不要自动解序列化元数据。仅当直接调用Phar->getMetadata()PharFile->getMetadata()时,才使 PHP 不序列化元数据。

另外,添加一个数组$unserialize_options = [] parameter to both getMetadata()实现,默认为当前默认的unserialize()行为,比如允许任何类。(作为一个实现细节,如果$unserialize_options被设置为默认值以外的任何值,那么产生的元数据将不会被缓存,并且不会从缓存中返回值。例如,setMetadata(new stdClass())之后的getMetaData(['allowed_classes' => []])将可能触发内部的unserialize(['allowed_classes' => []])呼叫。

向后不兼容的更改

加载 phar 时,在元数据非序列化期间或之后触发的来自__wakeup()__destruct()等的任何副作用都将停止发生,只有在直接调用getMetadata()时才会发生。*