返回资讯中心
外部精选
软件工程
#开发者实时流

Psalm 7 发布:PHP 安全与静态分析工具改用能力模型替代纯度注解

来源标题:Psalm 7 release: PHP security&static analysis software

Psalm 7 发布,用基于 Hack 上下文与能力模型的新机制取代 @psalm-pure 等旧注解,函数可精确声明读属性、写全局、IO、时间等副作用。新增纯度模板,使 array_map 等高阶函数可随回调保持纯性,Closure[_] 可继承闭包纯度。

Hacker News 最新danogentili2026/11/7 · 5 天前18 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

@psalm-pure、@psalm-mutation-free 和 @psalm-external-mutation-free 已被 capabilities 取代,后者仿照 Hack 的 contexts 和 capabilities 建模。类函数式声明明确指出它可能具有哪些副作用:read-props、write-this-props、write-props、read-globals、write-globals、write-refs、io 和 time,两端分别是 pure 和 impure(#11984):

<?php
/** @psalm-capabilities time */
function today(): string {
 return date('Y-m-d'); // no issue
/** @psalm-pure */
function todayWrong(): string {
 return date('Y-m-d'); // ImpureFunctionCall: ... requires time

在 psalm.dev 上试试 »

新的 Purity Model 文章完整解释了该模型。

许多函数的纯度取决于传入的参数:当回调是纯的时,array_map() 才是纯的。纯度模板是模板参数,其值是 capability 集合而非类型,类似于 Hack 的 [ctx $f] contexts。函数现在既可以同时是纯的又是高阶的,每次调用的开销仅取决于其参数的行为(#11984)。

使用 Closure[_],函数的纯度取自传入的闭包:

<?php
/**
 * @psalm-pure
 * @param Closure[_](int): int $f
 */
function apply(Closure $f, int $x): int {
 return $f($x);
/** @psalm-pure */
function double(int $x): int {
 return apply(fn(int $y): int => $y * 2, $x); // no issue: the closure is pure
/** @psalm-pure */
function noisy(int $x): int {
 return apply(function (int $y): int { echo $y; return $y; }, $x); // ImpureFunctionCall: The context is pure but function call on apply requires io
/** @psalm-pure */
function lengths(array $words): array {
 return array_map(fn(string $w): int => strlen($w), $words); // no issue: builtins taking callbacks work the same way

在类上,纯度模板让每个子类决定其方法可以做什么:

<?php
/** @psalm-purity-template P */
abstract class Handler {
 /**
 * @psalm-pure
 * @psalm-purity-from-template P
 */
 abstract public function handle(string $input): string;
/** @extends Handler[pure] */
final class Upper extends Handler {
 /** @psalm-pure */
 #[Override]
 public function handle(string $input): string {
 return strtoupper($input);
/** @extends Handler[io] */
final class Printer extends Handler {
 /** @psalm-capabilities io */
 #[Override]
 public function handle(string $input): string {
 echo $input;
 return $input;
/** @psalm-pure */
function viaUpper(Upper $handler): string {
 return $handler->handle('a'); // no issue: Upper binds P to pure
/** @psalm-pure */
function viaPrinter(Printer $handler): string {
 return $handler->handle('a'); // ImpureMethodCall: The context is pure but method Printer::handle requires io

Traversable、Iterator、IteratorAggregate 和 Generator 具有纯度模板,用于描述遍历它们时的行为。iterable 也有一个。带有纯度注解的 generator 将其绑定到其函数体的行为:

<?php
/**
 * @psalm-pure
 * @param Traversable[pure]<int, int> $xs
 */
function sum(Traversable $xs): int {
 $total = 0;
 foreach ($xs as $x) { // no issue
 $total += $x;
 return $total;
/**
 * @psalm-pure
 * @param Traversable<int, int> $xs
 */
function sumAny(Traversable $xs): int {
 $total = 0;
 foreach ($xs as $x) { // ImpureMethodCall: The context is pure but iterating over Traversable<int, int> requires impure
 $total += $x;
 return $total;
/**
 * @psalm-pure
 * @return Generator<int, int>
 */
function numbers(): Generator {
 yield 1;
 yield 2;
/** @psalm-pure */
function total(): int {
 return sum(numbers()); // no issue: numbers() returns a Generator[pure]

语法:纯度参数放在类型参数之前的方括号中,与 Hack 一致:Closureio: void、Handler[pure]、Iterator[pure]<int, string>、Box[io]<int>。命名模板用 @psalm-purity-template P 声明,用 @psalm-purity-from-template P 使用。

边界和默认值写成链式:@psalm-purity-template write-this-props <= C(write-this-props) <= write-this-props|write-props|io。方法可以无条件使用下界。子类必须保持在边界内,未绑定模板时获得默认值。

构造函数可以通过其参数绑定类模板,使用 Closure[P] 参数或 __construct 上的条件 @return。例如,new ArrayObject([...]) 是 ArrayObject[pure],因此纯函数可以填充它创建的 ArrayObject(#12116、#12179、#12190)。

纯度模板会在重写、trait 和嵌套在模板化作用域中的闭包中进行检查(#12015、#12126、#12027)。

同样支持推断:Psalm 会推断对模板化函数的调用的纯度,--alter 会为函数调用的闭包参数添加 Closure[_](#12077、#12019)。

接受回调或迭代器的内置函数和 SPL 用纯度模板声明:array_map、usort、preg_replace_callback、iterator_to_array、CallbackFilterIterator、Fiber 等(#12122)。

参见 Purity Model 文章中的 Purity templates。

未使用的代码现在在引用图上查找(#11939)。代码只有在可从入口点到达时才被视为已使用,因此仅被其他未使用代码引用的类和方法(包括循环引用)也会被报告。创建但从未抛出的异常也会被报告(#12487):

<?php
final class Service {
 public function run(int $id): void {
 if ($id < 0) {
 new InvalidArgumentException('negative id'); // UnusedMethodCall: ... never thrown or used
 echo $id;
final class Ping { // UnusedClass
 public static function ping(): void { Pong::pong(); }
final class Pong { // UnusedClass
 public static function pong(): void { Ping::ping(); }
(new Service())->run(1);

在 psalm.dev 上试试 »

--find-unused-psalm-suppress 现在还会检查类和 Tainted* 问题上的抑制。

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →