PHPStan 错误标识符 outOfClass.self 全解析:在类作用域之外使用 self 的检测与修复
【免费下载链接】phpstanPHP Static Analysis Tool - discover bugs in your code without running it!项目地址: https://gitcode.com/gh_mirrors/ph/phpstan
导读
outOfClass.self是 PHPStan 针对「在类作用域之外使用self关键字」这一问题产生的错误标识符(Error Identifier)。在全局作用域或普通函数中书写self::FOO、new self()、self::$prop等代码,虽然语法合法,但运行时必然触发致命错误,因为此时不存在可引用的当前类。本文以 PHPStan 仓库中的官方错误文档 website/errors/outOfClass.self.md 为核心,结合errorsIdentifiers.json中的规则映射,完整讲解该错误的触发场景、PHP 语言层面的成因、多种修复方案,以及与outOfClass.static、outOfClass.parent、magicConstant.outOfClass等同族标识符的异同,帮助你彻底掌握如何利用 PHPStan 在静态分析阶段提前拦截这类必现的运行时错误。
什么是 outOfClass.self 标识符
在 PHPStan 中,每条规则报告的错误都带有一个稳定、可检索的标识符。outOfClass.self正是「在类作用域之外使用self关键字」这一错误的专属标识符,其官方shortDescription为:
Keyword self is used outside of a class scope.
从官方文档的 frontmatter 可以看到,该标识符标记为ignorable: true,表示它是一条「可忽略」级别的错误——即可以在phpstan.neon的ignoreErrors中按标识符精确忽略,或写入 baseline 文件中,而不会破坏分析的完整性(关于标识符文档格式与ignorable字段的约定,可参见 website/errors/CLAUDE.md)。
触发错误的最小示例
以下代码位于全局作用域,会在没有任何类上下文的情况下引用self:
<?php declare(strict_types = 1); echo self::FOO; // ERROR: Using self outside of class scope.运行 PHPStan 后,这条语句即被报告为outOfClass.self。需要注意的是,这里的self并非指某个未定义的类,而是 PHP 语法层面就没有合法的解析目标,因此无论FOO常量是否存在,这段代码都无法正确执行。
为什么会被报告:self 的语言语义
从 PHP 语言角度看,self关键字的作用是引用「当前所在的类」。它只能在类定义内部使用,具体合法位置包括:
- 类方法体内;
- 类常量(class constant)的声明与引用中;
- 属性(property)的默认值声明中。
一旦在类作用域之外使用self——例如在普通函数中、在全局脚本中、在闭包中——由于不存在「当前类」可供引用,PHP 会在运行时直接抛出致命错误(Fatal error)。官方文档明确指出:这类代码本身就是 PHP 错误,因为没有任何类可以引用,而不是「引用了一个不存在的类」。
值得强调的是,这类错误是「写出来就必炸」的:分析期即可 100% 确定运行时会失败,不存在条件分支或类型不确定性。PHPStan 将其单独提炼为一个标识符,正是为了帮助开发者在 CI 阶段就把这类隐藏的运行时崩溃消灭掉。
覆盖的完整使用场景:不止是 self::CONST
查阅 website/src/errorsIdentifiers.json 可以发现,outOfClass.self并非只由一个规则产生,而是由多条规则协同报告,对应self在类作用域外的全部可能用法:
| 规则类(phpstan/phpstan-src) | 覆盖的 self 用法 | 对应源码位置 |
|---|---|---|
PHPStan\Rules\Classes\ClassConstantRule | self::CONST常量访问 | ClassConstantRule.php#L101 |
PHPStan\Rules\Classes\ExistingClassInInstanceOfRule | $x instanceof self判断 | ExistingClassInInstanceOfRule.php#L63 |
PHPStan\Rules\Classes\InstantiationRule | new self()实例化 | InstantiationRule.php#L162 |
PHPStan\Rules\Methods\CallStaticMethodsRule | self::method()静态方法调用 | StaticMethodCallCheck.php#L88 |
PHPStan\Rules\Methods\StaticMethodCallableRule | self::method(...)作为可调用对象 | StaticMethodCallCheck.php#L88 |
PHPStan\Rules\Properties\AccessStaticPropertiesInAssignRule | self::$prop静态属性赋值 | AccessStaticPropertiesCheck.php#L106 |
PHPStan\Rules\Properties\AccessStaticPropertiesRule | self::$prop静态属性读取 | AccessStaticPropertiesCheck.php#L106 |
这意味着 PHPStan 对类作用域外的self是全维度扫描的:无论是读常量、读/写静态属性、调用静态方法、实例化,还是instanceof判断,只要self出现在不该出现的位置,都会被归一到outOfClass.self这一标识符下。与同族错误outOfClass.parent在文档中明确列出的多场景(常量、静态方法、静态属性、new parent()、instanceof)相比,self的覆盖面有过之而无不及。
从这些规则的命名与分工可以推断,PHPStan 对类名关键字(self/static/parent)的检查采用了「按语法节点分散到各自规则、统一输出同一标识符」的设计:常量规则、实例化规则、方法调用规则、属性访问规则各自检查自己负责的表达式形态,最终共享outOfClass.*错误标识符,既保证了报告的一致性,也便于用户在配置中统一管理。
如何修复:三种实战方案
方案一:使用完全限定类名(推荐)
在类作用域之外,直接改用真实的类名代替self:
<?php declare(strict_types = 1); -echo self::FOO; +echo MyClass::FOO;这是最直接的修复:把self替换为实际的类名后,MyClass::FOO会在运行时正常解析常量。
方案二:把代码移回类方法内部
如果这段代码本质上属于某个类的职责,应当把它放回类的方法体内,让self恢复其语义:
<?php declare(strict_types = 1); class MyClass { public const FOO = 'bar'; public function doFoo(): void { echo self::FOO; // This works inside a class } }在类内部,self::FOO合法且指向MyClass::FOO。这一方案保留了self的「跟随类名变更而自动更新」的优势,适合被误移到全局的类相关逻辑。
方案三:通过参数传入类名上下文
当代码位于独立函数、且确实需要知道「调用方类名」时,与其使用非法的self,不如把类名作为参数传入。这一思路与同族文档 website/errors/magicConstant.outOfClass.md 中处理__CLASS__的方案完全一致:将类上下文显式参数化,消除对隐式类作用域的依赖:
-function logContext(): void +function logContext(string $className): void { - echo self::FOO; + echo $className::FOO; }同时,在涉及 PHP 版本兼容性时,还可以考虑:如果只是需要「当前类的名称」,在函数中可以用get_class()的返回值或显式传入的类名字符串替代;如果代码目标版本较新,也可结合静态分析时的类型收窄让调用方明确传入具体类名。核心原则是——类作用域之外不存在隐式的「当前类」,任何对self的依赖都必须显式化。
同族标识符:self / static / parent /CLASS的关系
outOfClass.self并非孤立错误。在 website/errors 目录下,它和另外三个错误文档共同构成了「类关键字/魔法常量在类外使用」的完整家族:
| 标识符 | 触发写法 | 核心差异 | 参考文档 |
|---|---|---|---|
outOfClass.self | self::FOO | 引用「当前所在类」,类外无当前类 | outOfClass.self.md |
outOfClass.static | static::FOO | 引用「运行时实际调用的类」(后期静态绑定),仅在类方法内才有意义,类外触发致命错误 | outOfClass.static.md |
outOfClass.parent | parent::FOO | 引用「当前类的父类」,类外无法解析父类上下文 | outOfClass.parent.md |
magicConstant.outOfClass | echo __CLASS__ | 类外不报致命错误,但__CLASS__恒等于空字符串'',几乎必然是重构残留 | magicConstant.outOfClass.md |
对比可见:
self、static、parent在类外使用都会导致运行时致命错误,属于「必炸」类问题,PHPStan 直接报outOfClass.*;__CLASS__在类外不会报错,但返回值恒为空串,属于「静默错误结果」类问题,故归入magicConstant.outOfClass;- 修复思路上两者相通:要么移入类作用域,要么用显式的类名/参数替代隐式上下文。
这种按「失败模式」而非「语法位置」划分标识符的设计,使开发者拿到错误标识符后,就能立即理解其严重级别与修复方向。
如何在你的项目中定位与处理此错误
1. 复现
将触发示例写入任意 PHP 文件,例如test.php,然后运行 PHPStan:
vendor/bin/phpstan analyse test.php或使用仓库自带的 phpstan 可执行文件:
./phpstan analyse test.php分析结果中会携带错误标识符,通常形如:
Line test.php ------- 3 Using self outside of class scope. 💡 Learn more: https://phpstan.org/... -------2. 按标识符精确忽略(可选)
由于outOfClass.self标记为ignorable: true,如确需临时豁免(例如遗留代码迁移期),可在phpstan.neon中按标识符精确配置:
parameters: ignoreErrors: - identifier: outOfClass.self path: legacy/GlobalScript.php相比按错误文本模糊匹配,按identifier配置更精确、更不易误伤同文本的其他错误。
3. 写入 baseline(推荐用于存量代码)
对存量项目,可将该错误一次性沉淀进 baseline 文件,保证「存量可追溯、新增零容忍」:
vendor/bin/phpstan analyse --generate-baseline总结
outOfClass.self是 PHPStan 对「类作用域之外使用self」这一必然运行时错误的静态化拦截。它由 errorsIdentifiers.json 中映射的 7 条规则协同报告,覆盖常量访问、实例化、instanceof、静态方法调用与静态属性读写等全部self用法;修复的核心思路是把隐式的类上下文显式化——改用真实类名、移入类方法,或通过参数传入类名。配合outOfClass.static、outOfClass.parent、magicConstant.outOfClass同族标识符,PHPStan 为「类关键字/魔法常量误用」提供了完整的静态分析防线,让这类在运行时必然暴露的问题提前暴露在 CI 阶段。
进一步阅读:标识符文档的生成规范与ignorable字段约定见 website/errors/CLAUDE.md,outOfClass.self官方文档原文见 website/errors/outOfClass.self.md。
【免费下载链接】phpstanPHP Static Analysis Tool - discover bugs in your code without running it!项目地址: https://gitcode.com/gh_mirrors/ph/phpstan
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考