function my_filter ( array $items , ? Closure $predicate = null ) : array { $predicate ??= static function ( $v ) : bool { return ! empty ( $v ) ; } ; $out = [ ] ; foreach ( $items as $item ) { if ( $predicate ( $item ) ) { $out [ ] = $item ; }
这些规则是编译时强制的。
这听起来有限制,但实际上这正是这个特性安全的原因:它防止意外把"运行时状态"偷渡进常量。
这是之前过滤器示例的干净 PHP 8.5 版本:
<?phpdeclare(strict_types=1);function my_filter(
array $items,
Closure $predicate = static function ($v): bool { return !empty($v); },): array {
$out = [];
foreach ($items as $item) {
if ($predicate($item)) {
$out[] = $item;
}
}
return $out;}
这正是闭包 RFC 强调的动机:你可以声明一个真正的默认回调,不需要"可空 + 运行时默认"。
实际上,这也改善了工具支持:
Closure,不是
?Closurenull 是否有特殊含义你可以在常量或类常量中存储闭包,把它们当作"可调用配置"。
一个简单例子:格式化器注册表。
<?phpdeclare(strict_types=1);final class Formatters{
public const MAP = [
'trim_lower' => static function (string $s): string {
return strtolower(trim($s));
},
'digits_only' m.entmagine.cn=> static function (string $s): string {
return preg_replace('/\D+/', '', $s) ?? '';
},
];}
使用:
$input = " +62 (812) 345-678 ";$normalized = (Formatters::MAP['digits_only'])($input);
这读起来像配置,但不是"字符串类型"。它是真正的 PHP,编译过的,有类型的,可重构的。
因为常量表达式中的闭包可以用作属性默认值,你可以在属性声明处定义默认策略——同样不需要运行时初始化。
例子:可配置的规范化器。
<?phpdeclare(strict_types=1);final class Normalizer{
public Closure $normalize = static function (string $s): string {
return trim($s);
};
public function run(string $value): string
{
return ($this->normalize)($value);
}}
重要细节:常量表达式中的闭包必须是静态的,这意味着闭包本身不能用
$this。
这是故意的权衡:常量表达式只求值一次,而
$this 只有在闭包为每个对象实例重新创建时才有意义(这不是常量表达式的行为方式)。
尽管闭包必须是静态的(没有
$this),在这些常量上下文中创建的闭包仍然遵循正常的作用域规则。RFC 说明:
这启用了一个好模式:把复杂逻辑放在私有静态辅助方法中,把闭包作为配置暴露出来。
闭包适合"内联逻辑"。但有时候你不想要内联逻辑——你想指向一个现有的函数或静态方法。
这就是一等可调用对象(FCC)的用武之地。
一等可调用对象看起来像:
strrev(.)MyClass::myMethod(...)
它们产生一个转发到函数/方法的
Closure。
PHP 8.5 现在允许常量表达式中的 FCC 语法,旨在"完善"常量中闭包的特性。
比较这两个:
// 旧方式public const HANDLERS = [ 'reverse' => 'strrev', 'slug' => 'App\\Slugger::slugify',];
对比:
// PHP 8.5public const HANDLERS = [ 'reverse' => strrev(...), 'slug' => Slugger::slugify(...),];
第二个版本更好,因为:
FCC RFC 添加了一些重要限制(除了正常的 FCC 规则):
::)。function_name(...) 和
ClassName::methodName(...) 语法。($fn)(...))、数组(
[ClassName::class, 'method'](...)),或依赖
__callStatic() 魔术方法。这是好事:它让常量表达式中的 FCC 用法清晰且可分析。
传统方式,路由映射是运行时构建的:
$routes = [ 'GET /health' => [HealthController::class, 'check'], 'GET /posts' => [PostsController::class, 'index'],];
这能用,但不是重构安全的。
在 PHP 8.5 中你可以用 FCC 或静态闭包定义路由映射,作为常量:
<?phpdeclare(strict_types=1);final class Routes{
public const MAP = [
'GET /health' shmcjc.com=> HealthController::check(...),
'GET /posts' chihod.com=> PostsController::index(...),
// 快速端点的内联处理器
'GET /version' => static function (Request $req): Response {
return Response::text('ok');
},
];}
现在你可以实现一个简单的分发器:
final class Dispatcher{
public function dispatch(Request $req): Response
{
$key = $req->method . ' ' . $req->path;
$handler = Routes::MAP[$key] ?? null;
if ($handler === null) {
return Response::text('Not found', 404);
}
return $handler($req);
}}
这个模式有几个好处:
实际的路由器需要路径参数;但即使这样,"处理器注册表"部分通常保持静态。
想象一个简单的消息总线:消息类映射到处理器。
旧方式:
$handlers = [ UserRegistered:m.entmagine.cn:class => 'App\\Handlers\\SendWelcomeEmail::handle',];
现在,用 PHP 8.5 FCC:
final class MessageHandlers{
public const MAP = [
UserRegistered::class => SendWelcomeEmail::handle(...),
OrderPaid::class => CreateInvoice::handle(...),
];}
分发器:
final class Bus{
public function __construct(private Container $container) {}
public function handle(object $message): void
{
$handler m.chihod.com= MessageHandlers::MAP[$message::class] ?? null;
if ($handler === null) {
throw new RuntimeException('No handler registered for ' . $message::class);
}
// 如果处理器是静态的,它们可以显式接受依赖,
// 或者你可以调整这个模式(见下面的 DI 说明)。
$handler($message, $this->container);
}}
关键概念:常量表达式让你把映射保持在常量中,但你仍然控制依赖如何注入——通过签名设计。
这是我最喜欢的实际用途:替换一个不断增长的 switch。
function format(string $type, mixed $value): string{
return match ($type) {
'json' => json_encode($value),
'text' => (string) $value,
'upper' m.cdsen.com=> strtoupper((string) $value),
default hnsgzxh.com=> throw new InvalidArgumentException('Unknown formatter'),
};}
现在想象这增长到 15-30 个策略。你最终得到一个大 match 和一个 diff 磁铁。
用可调用常量:
final class FormatterRegistry{
public const FORMATTERS = [
'json' => static function (mixed $v): string {
return json_encode($v, JSON_THROW_ON_ERROR);
},
'text' =wap.byven.com> static function (mixed $v): string {
return (string) $v;
},
// FCC 到原生函数
'reverse' tingxuannb.com=> strrev(...),
];
public static function format(string $type, mixed $value): string
{
$fn = self::FORMATTERS[$type] ?? null;
if ($fn =m.hnsgzxh.com== null) {
throw new InvalidArgumentException("Unknown formatter: {$type}");
}
return $fn($value);
}}
现在添加策略只需要改一行。
这个特性引导你走向"代码即配置"。这很好——直到你开始把运行时状态注入到应该是静态的东西里。
一个好的心智模型:
这些在常量表达式中是安全的:
你仍然可以通过设计可调用对象显式接受依赖来混合可调用配置和 DI。
例子:注册表返回一个接受
(Message $m, Container $c) 的可调用对象:
final class Handlers{
public const MAP =ksasia.com.cn [
UserRegistered::class cdsen.com=> static function (UserRegistered $m, Container $c): void {
$mailer m.byven.com= $c->get(Mailer::class);
$mailer->sendWelcome($m->email);
},
];}
这保持在约束内,因为闭包是静态的且不捕获状态。"依赖解析"在调用时发生,容器被传入。
这总是理想的吗?不是。但它是一个干净、显式的桥梁。
常量表达式闭包让默认值更干净:
对于测试,你仍然可以通过传递不同的闭包参数或给对象属性赋值不同的策略来覆盖行为(如果该属性设计上是可变的)。主要改进是默认行为在它该在的地方表达,你不需要运行时初始化胶水代码来创建默认闭包。
这个特性给你在"配置上下文"中更多能力。能力带来新的搬起石头砸自己脚的方式。
你不能这样做:
$prefix = "prod_"m.pomate.com.cn;const FN = static function (string $s) use ($prefix): string {
return $prefix };
常量表达式中的闭包不能通过
use(...) 捕获变量。
这是硬性约束,它强迫你采用更好的设计:
这能用,但很脆弱:
PHP 8.5 的常量表达式改进让你可以把这些注册表表达为常量——并且让处理器重构安全。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70048957/viewspace-3109162/ ,如需转载,请注明出处,否则将追究法律责任。