21CTO导读:根据正式发布计划,PHP 8.6 将于几周后发布——具体日期是 2026 年 11 月 19 日。我像往年一样,在正式版发布前撰写了这篇文章,内容基于 RFC 文档和候选版本包。
本文会将PHP 8.6 的所有新增功能,从最酷的功能到最小的细节,按顺序排列,并附有示例和发布日期。
PHP 8.6 将于 2026 年 11 月 19 日正式发布。其主要改进是函数局部应用,这将改变每个 PHP 开发人员编写回调函数的方式。
我编译了PHP-8.5两个PHP-8.6分支php-src并进行了差异比较,因此以下所有内容均来自源代码,而非 RFC 列表。功能冻结期已于 9 月结束,因此在正式发布前,该列表不会更改。
功能介绍的顺序是:最让我兴奋的功能排在最前面,小细节最后介绍。
| 里程碑 | 日期 |
|---|---|
| RC5 | 2026年11月5日 |
| PHP 8.6.0 | 2026年11月19日 |
说明:日期来自PHP wiki 的PHP 8.6 发布页面(https://wiki.php.net/todo/php86)。
就是这样。你编写一个函数调用,?在缺少参数的地方添加参数,PHP 会返回一个接受该参数的闭包。
$startsWithAdmin = str_starts_with(?, 'admin.');array_filter(['admin.users', 'web.home', 'admin.posts'],$startsWithAdmin);// ['admin.users', 'admin.posts']
现在想想你多久会写fn ($value) => something($value, 10),以及array_map()的文章。
整个结尾就变成了调用本身:
// Before$posts->map(fn (string$title) => Str::limit($title, 10));// PHP 8.6$posts->map(Str::limit(?, 10));
结果不出所料:
array(2) {[]=> string(13) "PHP 8.6 is..."[]=> string(13) "Partial fu..."}
这里有三个占位符:
?正好可以代替一个论点。...取代所有剩余的论点。?(例如limit: ?)的参数仅取代该参数本身。
function greet(string $greeting, string $name, string $punctuation = '!') {return "$greeting, $name $punctuation";}$hi = greet('Hi', ...);$hi('Nuno');// "Hi, Nuno!"$hi('Taylor', '?'); // "Hi, Taylor?"$hey = greet(name: 'Nuno', greeting: ?);$hey('Hey');// "Hey, Nuno!"Str::limit(?, end: '…', limit: 5); // named arguments in any order
返回的闭包是一个具有实际类型的真实函数。
我们使用反射可以证明这一点:
Closure [static function {closure:pfa:pfa.php:13} ] {- Bound Variables [1] {Variable #0 [ $greeting ]}- Parameters [2] {Parameter #0 [string $name ] Parameter #1 [string $punctuation = '!' ]}}
这一点很重要,因为依赖注入容器和路由会使用反射来读取你的闭包。局部视图会保留原始函数的类型、默认值和名称。
A?始终会将参数设为必需参数,即使原始参数有默认值。...占位符会保留原始参数的默认值。PHP 也会在调用时捕获这些错误:
Error: Named parameter $a overwrites previous placeholderError: Cannot use named placeholder for unknown or variadic parameter $c
|>,但每个步骤都必须是一个带一个参数的可调用对象。因此,如果你用fn ($x) => str_replace(' ', '-', $x)在管道中间编写代码,管道操作符就失去了大部分魅力。部分应用解决了这个问题:$slug = 'Hello World'|> strtolower(...)|> str_replace(','-', ?);//"hello-world"$title =' Hello PHP|>trim(...)|> Str::limit(?, 5)|> strtoupper(...);// "HELLO..."
当一个包含一个占位符的局部视图位于管道的右侧时,OPcache 会将其编译成一个直接函数调用。其操作码与原1 |> foo(?)操作码相同foo(1),不会创建闭包。
Time\Duration
PHP 终于有了正式表示时间的数据类型。
use Time\Duration;$timeout = Duration::fromSeconds(90) ->add(Duration::fromMilliseconds(500));$timeout->seconds; // 90$timeout->nanoseconds; // 500000000$meeting = Duration::fromIso8601DurationString('PT1H30M');$meeting->seconds; // 5400Duration::compare($meeting, Duration::fromMinutes(90)); // 0Duration::fromHours(2)->divideBy(3)->seconds; // 2400
这是一个final readonly包含三个属性的类,分别是 ` ` $secondsa、` $nanosecondsb` 和$negative`c`。
每个方法都会返回一个新的实例:
object(Time\Duration)["seconds"]=> int(5400)["nanoseconds"]=> int(0)["negative"]=> bool(false)}
构造函数有fromHours(),fromMinutes(),fromSeconds(),fromMilliseconds(),fromMicroseconds(),fromNanoseconds(),fromIso8601,DurationString(),add(),sub(),multiplyBy(),divideBy(),negate(),absolute(),compare()。
检查代码中的每一个参数 int $seconds,int $timeoutInMs:缓存 TTL、HTTP 客户端超时、队列重试延迟、锁持续时间。
3.clamp()
你写了上max($min, min($max, $value))百遍,至少读错了一次。
clamp(150, 1, 100); // 100clamp(-5, 1, 100); // 1clamp(42, 1, 100); // 42clamp(1.5, 0.0, 1.0); // 1.0clamp('m', 'a', 'f'); // "f"
最经典的用法是测量用户发送给你的页面大小:$perPage = clamp((int) ($_GET['per_page'] ?? 15), 1, 100);它适用于 PHP 可以比较的任何值,包括字符串。$min大于号比较$max是一个 bug,PHP 会给出相应的提示:
clamp(): Argument #2 ($min) must be smaller than or equal to argument #3 ($max)现在,只读属性可以直接声明其默认值:
final class Settings{public readonly int $perPage = 15;public readonlystring $theme = 'dark';}
该属性将保持只读状态:
$settings = newSettings();$settings->perPage; // 15$settings->perPage = 30;// Error: Cannot modify readonly property Settings::$perPage
这适用于你在每个项目中编写的数据对象:DTO、消息、具有固定配置的值对象。以前,必须将默认值移到构造函数中才能将属性设置为只读。
现在你可以写入对象中由常量存储的属性:
final class Config{public array $items = [];public int $hits = 0;}const CONFIG = new Config();CONFIG->items['app.name'] = 'Acme';CONFIG->hits++;array(1) {["app.name"]=> string(4) "Acme"}int(1)
常量仍然指向同一个对象,你无法替换它。只有对象本身会改变,就像readonly持有对象的属性一样。枚举类型的属性保持不可变。
#[\Override]关于常数PHP 8.3 引入了#[\Override]for 方法,PHP 8.5 将其扩展到了属性。PHP 8.6 又增加了类常量和枚举类型:
interface HasTable{const TABLE = 'models';}final class User implements HasTable{const TABLE = 'users';}
如果父常量被重命名或删除,PHP 会在编译时提示你:
Fatal error: Post::TABLE has #[\Override] attribute, but no matching parent constant exists7.__debugInfo()关于枚举
枚举现在可以决定var_dump()、dump()或dd()显示什么。
enum Status: string{case Active = 'active';case Banned = 'banned';public function __debugInfo(): array{return ['label' => ucfirst($this->value)];}}var_dump(Status::Active);enum(Status::Active) (1) { ["label"]=> string(6) "Active"}
这个功能很小巧,我想当你第一次遇到 webhook 有效负载解析失败时,就会爱上它。
json_decode('{"name": "Nuno", "age": }');json_last_error_msg();Syntax error near location 1:25
JsonException抛出异常的元素JSON_THROW_ON_ERROR会占据同一行和同一列。
现在,每个显示该消息的地方,例如 HTTP 客户端或请求解析器的错误信息,都会显示该消息在有效负载中的位置。
一直以来,file_get_contents()文件缺失都会发出警告false。大多数框架会将该警告转换为带有错误处理程序的异常。
PHP 8.6 现在本身就可以做到这一点:
$context = stream_context_create(['stream' => ['error_mode' => StreamErrorMode::Exception],]);try {file_get_contents('/does/not/exist.txt', context: $context);} catch (StreamException $e) {$e->getMessage(); // "Failed to open stream: No such file or directory"}
模式有三种,包括有:
StreamErrorMode::Error:` readError`、`readError` 和 `readError`。此外还有 ` readError` 和`readError`用于读取不带处理程序的错误,以及一个枚举类型,用于避免比较消息字符串。
StreamErrorCode旧的行为仍然是默认行为。StreamErrorMode::ExceptionStreamErrorMode::Silentstream_last_errors()stream_clear_errors()
相关内容:fclose(),file_put_contents()现在当刷新或关闭失败时copy()返回false,例如在最后一次写入时磁盘已满。
Io\Poll
这是异步 PHP 的基础。它的epoll支持 Linux 和kqueuemacOS,采用统一的对象 API,无需安装任何扩展:
use lo\Poll\Context;use lo\Poll\Event;use Time\Duration;[$a, $b] =stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM,STREAM_IPPROTO_IP);$poll = newContext();$poll->add(newStreamPollHandle($b), [Event::Read], data: 'worker-1');fwrite($a, 'ping');$watcher->getData();// "worker-1"foreach ($poll->wait(Duration::fromSeconds(1)) as $watcher) {fread($b, 4);// "ping"}
你不会在控制器中编写这段代码。
它是为那些编写事件循环的人准备的,例如 ReactPHP、Amp 和 Revolt,它们目前要么回退到事件循环,stream_select()要么需要扩展才能使用epoll事件循环。所有基于这些框架构建的东西,例如 WebSocket 服务器和 HTTP 客户端,都可以免费使用事件循环。
其后端包括:` WebSocket`、 ` WebSocket` Auto、 `WebSocket` 、` WebSocket`和`WebSocket`。PollEpollKqueueEventPortsWSAPoll
SortDirection
一个包含两种情况的全局枚举:
enum SortDirection{case Ascending;case Descending;}
PHP 8.6 目前还没有任何函数支持它。它的存在是为了防止你的包自行创建字符串'asc'和'desc':
function orderBy(string$column, SortDirection $direction = SortDirection::Ascending): string{return $column . ' ' . match ($direction) {SortDirection::Ascending => 'asc', SortDirection::Descending => 'desc',};}orderBy('created_at', SortDirection::Descending); // "created_at desc"
我希望->orderBy('created_at', SortDirection::Descending)在每个查询构建器中都能看到它。
PHP 8.5 添加了Uri解析 URI 的扩展。PHP 8.6 添加了另一半功能,即构建器:
uri = (newUri\Rfc3986\UriBuilder())setScheme('https')setHost('nunomaduro.com')setPath('/posts')setQuery('page=2')build();uri->toString(); // "https://nunomaduro.com/posts?page=2"
此外,还有Uri\WhatWg\UrlBuilder浏览器规则,getUriType()用于getHostType()判断主机是 IPv4、IPv6 还是域名Uri\WhatWg\url_percent_encode()。
这些内容来自“性能改进”部分。
array_map()使用一级可调用对象或局部视图(例如 `array_map(strtoupper(...), $names)会被编译成foreach循环。不会创建闭包,内部代码也不会回调到用户空间。printf()仅使用%s并%d编译成字符串插值。static不捕获任何数据的闭包只会创建一次并重复使用。请参见以下示例。new self()。json_encode()在数组和对象上速度更快,并且在JSON_PRETTY_PRINT。array_sum()对于array_product()整数数组,,,,,,,array_unshift()并且只有一个分量的array_walk(),速度更快。array_intersect()str_pad()str_split()mb_str_pad()pathinfo()可以从用户空间查看闭包缓存:
function make() { return static fn () =>1; }// bool(true)var_dump(make() === make());
RFC在实际应用模板上测得的性能提升约为 3%,但这个数字包含了另一项未被合并的优化。请将其视为一个方向,而非承诺。
trim()移除换行符。"\f"连接默认字符trim(),ltrim()和rtrim()。ARRAY_FILTER_USE_VALUE是 的默认模式的新名称array_filter(),在此可以显式地写出模式。WeakMap引用。$ref = &$map[$object]当密钥尚未存在时有效。ReflectionProperty::isReadable()并isWritable()告知当前作用域是否可以读取或写入属性,包括非对称可见性和钩子。序列化器和表单库需要此功能。ReflectionParameter::getDocComment()读取参数的文档注释。ReflectionAttribute::getShortName(),getNamespaceName()和inNamespace()。mysqli::quote_string()一次调用即可对字符串进行引号和转义。grapheme_strrev()按字素反转字符串,因此"👋🏽"始终保持一个表情符号。它在intl。ini_get_all()builtin_default_value为每个指令提供一个。php -S)接受 HTTPQUERY方法,不再反映Host标头。error_include_args新增的 INI 选项会在函数的错误信息中显示其参数。默认情况下此选项处于关闭状态。CURLOPT_SEEKFUNCTION,因此流式上传可以在重定向时回滚。IntlNumberRangeFormatter为“€3 – €5”,具体价格取决于当地语言环境。mbstring。升级生产环境前请阅读本节内容。
现在会话默认设置是安全的。`is`、session.use_strict_mode` is`和`is`都是安全的。
如果你的框架有自己的会话层或传递自己的会话选项,请检查其会话配置。如果应用程序使用默认设置从 JavaScript 读取会话 cookie,或者期望跨站请求会话 cookie ,则会出错。
session.cookie_httponlysession.cookie_samesite "Lax"session_start()
ValueError之前出现警告的地方现在会抛出异常。很多函数现在会因为传入的错误值而抛出异常:例如,传递给 `__init__`、`__init__` 或 `__init__` 的路径中包含空字节 (NUL)、file_exists()、is_file()、filesize()` stat()__init__` 、`__init__`或`__init__`sleep()的无效模式。
如果你的代码之前传递了无效值,现在会抛出异常。例如array_filter()、pathinfo()、scandir()、preg_grep()返回错误,例如错误的 UTF-8 编码,而不是部分数组。false
如果有意在字符串末尾保留换行符,请使用trim($value, " \n\r\t\v\0")。
SplFileObject更改了读取方式next(),fgets()并current()共享当前行。如果使用它逐行读取文件,请运行测试。
base_convert()当结果精度下降时,发出bindec()hexdec()octdec()通知。
session_encode()对于空会话,返回空字符串,而不是false。
内存严重不足时,程序会调用abort()并退出,退出代码为 134,而不是 1。你的进程监视器看到的是不同的代码。
弃用声明并非错误。你的代码仍可以运行,但会收到一条E_DEPRECATED通知,告知在 PHP 9 之前需要进行哪些更改。以下是一些示例:
Deprecated: Returning a value from a constructor is deprecatedDeprecated: Returning from a finally block is deprecatedDeprecated: Function is_integer() is deprecated since 8.6, use is_int() insteadDeprecated: Function is_double() is deprecated since 8.6, use is_float() insteadDeprecated: Function doubleval() is deprecated since 8.6, use floatval() insteadDeprecated: Function spl_object_hash() is deprecated since 8.6, consider using spl_object_id() instead
语言层面的一些更新:
__construct()__destruct()具有return值 ,以及一个生成器构造函数或析构函数。let以及is作为类、接口、特性、枚举、函数或常量的名称。这两个词是为将来保留的,所以不要用它们来命名类名。readonly可以用作函数名、namespace类常量名_或常量名。is_a()当是is_subclass_of()字符串时。
?array作为返回类型__debugInfo(),将写入array中。函数家族:
is_integer(),is_long(),doubleval()和is_double()请使用is_int(),is_float()和floatval()替换。spl_object_hash()请使用spl_object_id()。增加mysqli_get_charset()和mysqli_stmt_init()。
ReflectionProperty::setValue()使用错误类的对象,以及ReflectionMethod::invoke()使用静态方法的对象。create_sid()没有和的会话处理程序validateId()。git clone --depth 1 --branch PHP-8.6 https://github.com/php/php-src.gitcd php-src./buildconf --force./configure --disable-all--enable-cli--enable-mbstring--disable-mbregex --enable-tokenizer --enable-ctype --enable-filtermake -j8 sapi/cli/php -r 'var_dump(clamp(150, 1,100));
这便是 PHP 8.6 版本。这是一个令人兴奋的版本,PHP 团队在这方面做得非常出色。
在过去一个月间,安全漏洞一直是人们关注的焦点,而且人们至今仍在讨论这些问题。我希望这些讨论不会掩盖这次的版本发布,它值得更多的人关注。
PHP 是一门很棒的语言。最近围绕它的一切热潮再次证明了这一点。我热爱 PHP,也热爱这个开源社区。
作者:手扶拖拉斯基
本篇文章为 @ 场长 创作并授权 21CTO 发布,未经许可,请勿转载。
内容授权事宜请您联系 webmaster@21cto.com或关注 21CTO 微信公众号。
该文观点仅代表作者本人,21CTO 平台仅提供信息存储空间服务。
请扫描二维码,使用微信支付哦。