- 开发工具
- 后端
【免费下载链接】environment
Provides functionality that helps writing PHP code that has runtime-specific execution paths
PHPStan 2.0 是 PHP 静态分析工具的一次大版本跃迁,它把过去只在 Bleeding Edge 模式下启用的新规则、新行为全面铺开,并移除了大量旧配置项与旧 API。本文以 PHPStan 官方仓库中的 UPGRADING.md 为核心骨架,逐条拆解最终用户与扩展开发者需要面对的迁移点,并结合本仓库(sebastian/environment,一个探测 PHP/HHVM 运行时环境的开源组件)中真实的 phpstan.neon 与tools/.phpstan/vendor/phpstan/依赖布局,演示 2.0 配置在实践中长什么样。读完本文,你将掌握:升级前的准备步骤、被移除配置项的官方替代写法、ignoreErrors标识符与excludePaths的新语法,以及扩展开发所需的所有破坏性 API 变更清单。
一、PHP 版本要求:PHPStan 2.0 的最低门槛
根据 UPGRADING.md 的明确说明,PHPStan 2.0 自身运行要求PHP 7.4 或更新版本。这意味着即使你被分析的代码面向的是更老的 PHP 版本,运行 PHPStan 2.0 的这台机器(或 CI 环境)也必须至少具备 PHP 7.4。注意这是"运行 PHPStan"的要求,与被分析项目的运行时 PHP 版本无关——后者由 PHPStan 的phpVersion参数控制。
二、最终用户升级指南:三步走到 2.0
2.1 第一步:先升到最新 PHPStan 1.12 并开启 Bleeding Edge
官方升级指南 给出的核心策略是不要直接跳到 2.0,而是先升到最新的 PHPStan 1.12 release,并开启Bleeding Edge。Bleeding Edge 是 PHPStan 的"预览通道":2.0 会对所有用户强制开启的新规则和新行为,在 1.12 中可以通过该开关提前启用,从而让你在 1.x 时代就把未来要面对的报错全部暴露出来。
在本仓库中,这一机制有直接对应物:根目录的 phpstan.neon 末尾以includes形式引用了phar://phpstan.phar/conf/bleedingEdge.neon。也就是说,本项目已经以 Bleeding Edge 方式运行 PHPStan,这正是 UPGRADING.md 推荐的升级前姿态。同时,项目通过 tools/phpstan 这个 Composer 生成的代理脚本调用phpstan.phar,并额外安装了phpstan-strict-rules等扩展(见tools/.phpstan/vendor/phpstan/目录),与官方建议的"在最新 1.12.x 上把构建跑到全绿"的工作流一致。
2.2 第二步:安装并启用 deprecation-rules
升级前还必须安装并启用phpstan/phpstan-deprecation-rules(文档原文)。这个扩展会把你代码中所有即将在 2.0 失效的旧 API 用法标记为弃用错误,是"提前扫雷"的关键工具。
2.3 第三步:升级 composer.json 并更新依赖
当你基于"最新 PHPStan 1.12.x + Bleeding Edge + 无任何弃用警告"拿到全绿构建后,就可以在composer.json中把相关依赖统一抬到 2.0。官方给出的完整require-dev模板如下(原文档原文):
"require-dev": { "phpstan/phpstan": "^2.0", "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-doctrine": "^2.0", "phpstan/phpstan-nette": "^2.0", "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpstan/phpstan-symfony": "^2.0", "phpstan/phpstan-webmozart-assert": "^2.0", ... }除了第一方扩展,所有第三方 PHPStan 扩展也要同步升级到兼容 2.0 的版本。修改完composer.json后,运行:
composer update 'phpstan/*' -W-W让 Composer 允许同时升级依赖链中的相关包,避免新旧版本混装。
官方特别强调:任何处于 PHPStan 1.12 的项目都应该能够升级到 2.0。至于升级后新冒出来的报错,你可以逐条消化,也可以干脆全部丢进 baseline(基线文件),两者都被官方认可。
三、代码分析行为的四大显著变化
升级到 2.0 后,即使配置不变,分析结果也会因为引擎行为变化而产生差异。官方在 Noteworthy changes to code analysis 中列出了四项:
- 按引用传递参数的处理增强:
function foo(&$bar)这类场景下的类型推导与检查逻辑被重做,分析更精确。 - 内联 PHPDoc
@var标签类型会被校验:/** @var string */ $x = ...;中标注的类型如果与实际推导类型矛盾,2.0 会报错,不再盲目采信。 - List 类型被强制:
list<T>(索引连续、从 0 开始)语义在类型系统中被更严格地贯彻,数组类型的推导会区分"list"与普通数组。 - 恒为
true的条件始终报告:之前只有开启phpstan-strict-rules才会报告的"条件恒真"问题,2.0 中默认一律报告。
四、被移除的配置项与官方替代写法
这一节是最终用户迁移的重头戏。多个旧参数被删除,官方给出的替代方案全部是"把旧行为收窄到ignoreErrors的错误标识符(identifier)"。
4.1checkMissingIterableValueType→missingType.iterableValue
旧参数checkMissingIterableValueType被移除。官方强烈建议为数组补齐元素类型标注(typehint)。如果你暂时想继续忽略"数组缺元素类型"的报错,就在ignoreErrors中按错误标识符屏蔽(原文档示例):
parameters: ignoreErrors: - identifier: missingType.iterableValue4.2checkGenericClassInNonGenericObjectType→missingType.generics
同理,旧参数checkGenericClassInNonGenericObjectType被移除,官方强烈建议为泛型类补齐类型参数。想继续忽略时使用(原文档示例):
parameters: ignoreErrors: - identifier: missingType.generics注意这里与 4.1 的语法一致:identifier精确匹配错误标识符,只有"该标识符对应的错误"被屏蔽,其余错误不受影响——这正是 2.0 推荐用标识符取代整条错误消息正则的原因。
4.3checkAlwaysTrue*系列:行为被固化
以下四个选项被整体移除,因为 2.0 中它们的语义恒等于true(原文档列表):
checkAlwaysTrueCheckTypeFunctionCallcheckAlwaysTrueInstanceofcheckAlwaysTrueStrictComparisoncheckAlwaysTrueLooseComparison
也就是说,"instanceof恒真""严格比较恒真"等场景现在是默认检查项,配置里不需要也再无法显式开启。
4.4excludes_analyse→excludePaths
旧参数excludes_analyse被删除,统一由excludePaths承担(原文档说明)。如果你的配置还在写excludes_analyse,迁移时直接改名为excludePaths即可,语义等价。
4.5excludePaths/ignoreErrors的路径规则收紧
这是最容易踩坑的破坏性变更:excludePaths和ignoreErrors中的路径必须是一个真实存在的文件路径,或一个 fnmatch 通配模式(原文档说明)。
如果你的排除路径当前可能不存在、但希望保留在配置里,就给该条目追加(?)后缀:
parameters: excludePaths: - tests/*/data/* - src/broken - node_modules (?) # optional path, might not exist(?)意为"可选路径,可能不存在",PHPStan 不会因为该路径缺失而报"未匹配"警告。
ignoreErrors中的情况则不同:不支持(?)后缀。如果你要在ignoreErrors里忽略某个可能不存在的路径下的错误,请改用全局开关reportUnmatchedIgnoredErrors: false:
parameters: reportUnmatchedIgnoredErrors: false五、第一方扩展的配置变更
如果你使用了 PHPStan 官方生态的扩展,2.0 一并收紧了它们的配置参数(原文档列表):
- phpstan-doctrine:
- 移除
searchOtherMethodsForQueryBuilderBeginning(扩展现在恒等于该值被设为true的行为); - 移除
queryBuilderFastAlgorithm(扩展现在恒等于该值被设为false的行为)。
- 移除
- phpstan-symfony:
- 移除所有带下划线的旧选项名,统一改为驼峰式:
container_xml_path→containerXmlPathconstant_hassers→constantHassersconsole_application_loader→consoleApplicationLoader
六、最终用户视角的次要向后不兼容变更
以下配置参数在 2.0 中被清理(原文档清单),迁移时如果配置里还在使用,需要逐一处理:
| 被移除/变更项 | 处理方式 |
|---|---|
cache.nodesByFileCountMax | 删除(从未生效的未用参数) |
memoryLimitFile | 删除(未用参数) |
disableRuntimeReflectionProvider功能开关 | 删除(未用) |
staticReflectionClassNamePatterns | 删除(未用参数) |
fixerTmpDir | 改用pro.tmpDir |
tempResultCachePath | 改用resultCachePath |
additionalConfigFiles | 必须是 list(数组)类型 |
七、扩展开发者升级指南
如果你维护 PHPStan 扩展(自定义规则、动态返回类型扩展、类型扩展等),请先接受官方的一条强烈建议(原文档 NOTE):在扩展的下一个主版本中切换到 PHPStan 2.0,不要试图用同一份扩展代码同时兼容 1.x 与 2.x。比较现实的做法是:先在现有主版本内,用"PHPStan + phpstan-deprecation-rules + Bleeding Edge"分析你自己的扩展代码,解决所有弃用告警,尽量逼近 2.0 兼容;最终一步在composer.json中改为"phpstan/phpstan": "^2.0"并发布新的主版本。
7.1 底层依赖双升级:php-parser v5 与 phpdoc-parser v2
PHPStan 2.0 将 AST 解析器升级为nikic/php-parser v5。最值得注意的表示变化是throw语句(原文档说明):
- 旧版:独立语句
throw $e;用Stmt\Throw_节点表示;表达式内(如$x ?? throw $e)用Expr\Throw_。 - 新版:
Stmt\Throw_类被删除,throw $e;现在是一个包含Expr\Throw_的Stmt\Expression。
如果你的自定义规则通过遍历 AST 识别 throw 语句,必须按新结构重写 visitor 逻辑。
同时 PHPDoc 解析层升级为phpstan/phpdoc-parser v2,凡是直接依赖 PHPDoc AST 的扩展都要重新编译/适配。
7.2 自定义规则:字符串错误不再被支持,必须用 RuleErrorBuilder
2.0 中,自定义规则直接返回纯字符串数组作为错误的写法被移除,且错误必须携带identifier(原文档说明)。
之前(不再支持):
return ['My error'];之后(推荐):
return [ RuleErrorBuilder::message('My error') ->identifier('my.error') ->build(), ];标识符命名空间惯例是"厂商名.错误名"(如上例my.error),它让用户能够像第 4 节那样通过ignoreErrors.identifier精确屏蔽错误,也能让错误被 PHPStan 更好地聚合与解释。
7.3 弃用instanceof *Type:改用Type接口方法
官方明确反对在自定义扩展中通过$type instanceof XxxType判断类型,理由是这无法处理UnionType等复合类型。2.0 中这种写法被正式弃用,应改用Type接口上的方法(如Type::getArrays()、Type::isConstantValue()、Type::isCallable()等),这些方法内部已经处理了联合类型展开。
7.4ParametersAcceptorSelector::selectSingle()被移除
动态返回类型扩展中常用的selectSingle()被删除,应改用selectFromArgs(),它能结合调用点的实参和$scope选出更精确的变体(原文档示例):
$defaultReturnType = ParametersAcceptorSelector::selectFromArgs( $scope, $functionCall->getArgs(), $functionReflection->getVariants() )->getReturnType();而如果你正在分析函数/方法体本身,不要再用selectFromArgs(),而是直接从反射对象上取getParameters()和getReturnType()。适用于以下节点类型:
InClassMethodNode::getMethodReflection()InFunctionNode::getFunctionReflection()FunctionReturnStatementsNode::getFunctionReflection()MethodReturnStatementsNode::getMethodReflection()Scope::getFunction()
之前:
$function = $node->getFunctionReflection(); $returnType = ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType();之后:
$returnType = $node->getFunctionReflection()->getReturnType();7.5TypeSpecifier::create()与SpecifiedTypes构造参数变更
TypeSpecifier::create()现在只接受四个必填参数(原文档说明):
Expr $exprType $typeTypeSpecifierContext $contextScope $scope
之前通过create()设置的$overwrite与$rootExpr,改为在返回的SpecifiedTypes对象上调用setAlwaysOverwriteTypes()与setRootExpr()。注意SpecifiedTypes是不可变对象,这两个 setter 返回的是新实例。
SpecifiedTypes构造函数同样收紧,现在只接受:
array $sureTypesarray $sureNotTypes
7.6ConstantArrayType不再继承ArrayType
类型层级发生了结构性变化:ConstantArrayType不再是ArrayType的子类(原文档说明)。Type::getArrays()现在返回list<ArrayType|ConstantArrayType>。由于instanceof ArrayType本身已被弃用(见 7.3),这一变更的实际冲击有限——正确做法是改用Type::getArrays()后逐个区分。
7.7TypeSpecifier::specifyTypesInCondition()移除$rootExpr
该方法不再接受Expr $rootExpr参数。需要修改根表达式时,对返回的SpecifiedTypes调用setRootExpr()(同样返回新对象)。
7.8 AST 节点属性parent/previous/next被移除
2.0 中,AST 节点上的parent、previous、next三个属性不再可用。如果你的自定义规则依赖它们回溯 AST 结构,需要改用 PHPStan 提供的替代机制(预处理后的节点包装)。
7.9 移除scopeClass配置参数
自定义 Scope 类的方式被废弃。官方替代方案是实现PHPStan\Type\ExpressionTypeResolverExtension接口并注册为服务,在扩展中为表达式提供类型解析逻辑。
7.10 移除PHPStan\Broker\Broker
老牌入口Broker被移除,统一使用PHPStan\Reflection\ReflectionProvider(原文档说明):
BrokerAwareExtension一并删除——在扩展构造函数中直接请求ReflectionProvider;- 测试侧:
PHPStanTestCase::createBroker()改为PHPStanTestCase::createReflectionProvider()。
7.11 List 类型对所有人启用
AccessoryArrayListType的以下静态方法被移除(原文档说明):
isListTypeEnabled()setListTypeEnabled()intersectWith()
原来的AccessoryArrayListType::intersectWith($type)写法,改为:
TypeCombinator::intersect($type, new AccessoryArrayListType())7.12 其他扩展开发相关的破坏性变更清单
以下是 UPGRADING.md 中剩余的全部次要 BC 破坏,按主题归纳:
类与接口层级
- 原标记
@final的类在 2.0 中真正变成final。 ConstantType接口被移除,改用Type::isConstantValue()。- 类常量反射重构:
ClassConstantReflection更名为RealClassConstantReflection,ConstantReflection接口更名/新设为ClassConstantReflection,GlobalConstantReflection更名为ConstantReflection。 *WithPhpDocs系列接口全面更名为Extended*:ParametersAcceptorWithPhpDocs→ExtendedParametersAcceptor、ParameterReflectionWithPhpDocs→ExtendedParameterReflection、FunctionVariantWithPhpDocs→ExtendedFunctionVariant。PHPStan\Node\ClassMethod不再是 AST 节点,需调用getNode()取回原始 AST 节点。PHPStan\Node\Printer\Printer不再自动注入为PhpParser\PrettyPrinter\Standard,类型提示中必须使用PHPStan\Node\Printer\Printer。
方法签名与必填参数
MutatingScope::enterAnonymousFunction()与enterArrowFunction()的参数$callableParameters变为必填。NodeScopeResolver::processStmtNodes()的参数StatementContext $context变为必填。TypehintHelper::decideTypeFromReflection()的$selfClass不再接受string。ClassPropertyNode::getNativeType()返回类型从 AST 节点改为Type|null。Type::getProperty()现在返回ExtendedPropertyReflection。FunctionReflection::isFinal()被移除。
类型系统方法迁移
- 大小比较类方法要求显式传入
PhpVersion:Type::getSmallerType()、getSmallerOrEqualType()、getGreaterType()、getGreaterOrEqualType()、isSmallerThan()、isSmallerThanOrEqual();CompoundType::isGreaterThan()、isGreaterThanOrEqual()同理。 Type::isClassStringType()更名为Type::isClassString()。ConstantArrayType构造参数收紧:$isList只能传TrinaryLogic(不再接受bool),$nextAutoIndexes只能传non-empty-list<int>(不再接受int)。ArrayType移除generalizeKeys()、count()(改用Type::getArraySize())、castToArrayKeyType()(改用Type::toArrayKey())。ConstantArrayType移除isEmpty()(改用isIterableAtLeastOnce()->no())、getNextAutoIndex()、generalizeToArray()、removeLast()/removeFirst()/reverse()/chunk()/slice()(分别改用Type::popArray()、shiftArray()、reverseArray()、chunkArray()、sliceArray())、findTypeAndMethodName()(改用findTypeAndMethodNames())。Type::accepts()与CompoundType::isAcceptedBy()返回类型从TrinaryLogic改为AcceptsResult;Type::isSuperTypeOf()、CompoundType::isSubTypeOf()、TemplateType::isValidVariance()返回IsSuperTypeOfResult。RuleLevelHelper::accepts()返回类型从bool改为RuleLevelHelperAcceptsResult。Scope::isSpecified()移除,改用hasExpressionType();Scope::doNotTreatPhpDocTypesAsCertain()移除,改用getNativeType()。UnionType::pickTypes()移除,改用pickFromTypes()。GenericTypeVariableResolver移除,改用Type::getTemplateType()。ReflectionProvider::supportsAnonymousClasses()移除(所有反射提供器都支持匿名类)。acceptsNamedArguments()在FunctionReflection、ExtendedMethodReflection、CallableParametersAcceptor中返回TrinaryLogic而非bool。- 不应被缓存序列化的对象上移除
__set_state()。
TypeUtils 瘦身
TypeUtils的大量静态方法被移除,全部迁移到Type接口方法:
| 移除的 TypeUtils 方法 | 替代方法 |
|---|---|
getArrays()/getAnyArrays() | Type::getArrays() |
getConstantArrays()/getOldConstantArrays() | Type::getConstantArrays() |
getConstantStrings() | Type::getConstantStrings() |
getConstantTypes()/getAnyConstantTypes() | Type::isConstantValue()/Type::generalize() |
generalizeType() | Type::generalize() |
getDirectClassNames() | Type::getObjectClassNames() |
getConstantScalars() | Type::isConstantScalarValue()/Type::getConstantScalarTypes() |
getEnumCaseObjects() | Type::getEnumCases() |
containsCallable() | Type::isCallable() |
反射相关方法迁移
ClassReflection::getFileNameWithPhpDocs()→getFileName()。AnalysisResult::getInternalErrors()→getInternalErrorObjects()。ConstantReflection::getValue()→getValueExpr()(从Expr取Type用Scope::getType()或InitializerExprTypeResolver::getType())。PropertyTag::getType()→getReadableType()/getWritableType()。RegexArrayShapeMatcher::matchType()→matchExpr()。PHPStanTestCase::$useStaticReflectionProvider移除;getReflectors()改为getReflector()。LevelsTestCase::dataTopics()数据提供者改为 static。
八、结合本仓库:2.0 时代配置的实战场
最后,看看 phpstan.neon 这份真实存在于本仓库的配置,它是 2.0 风格配置的活样本:
parameters: level: 10 paths: - src - tests checkTooWideReturnTypesInProtectedAndPublicMethods: true reportAlwaysTrueInLastCondition: true reportPossiblyNonexistentConstantArrayOffset: true reportPossiblyNonexistentGeneralArrayOffset: true treatPhpDocTypesAsCertain: false strictRules: allRules: false booleansInConditions: true closureUsesThis: true disallowedBacktick: true disallowedEmpty: true disallowedImplicitArrayCreation: true disallowedLooseComparison: true disallowedShortTernary: true illegalConstructorMethodCall: true matchingInheritedMethodNames: true noVariableVariables: true numericOperandsInArithmeticOperators: true overwriteVariablesWithLoop: true requireParentConstructorCall: true strictArrayFilter: true strictFunctionCalls: true switchConditionsMatchingType: true uselessCast: true ergebnis: allRules: false final: enabled: true classesNotRequiredToBeAbstractOrFinal: privateInFinalClass: enabled: true includes: - phar://phpstan.phar/conf/bleedingEdge.neon几点值得注意:
reportAlwaysTrueInLastCondition等选项的存在,正对应第 3 节"恒真条件"在 2.0 中被固化为默认行为的方向——这类配置在 2.0 中会被并入默认检查。includes直接加载bleedingEdge.neon,与本仓库 vendor 目录下同时安装的phpstan-strict-rules(见tools/.phpstan/vendor/phpstan/phpstan-strict-rules/)共同构成高严格度检查链。treatPhpDocTypesAsCertain: false与 2.0"内联@var标签类型将被校验"的新行为相呼应:PHPDoc 不再是"说什么信什么",必须与实际推导一致。- 被分析对象
src/与tests/(如 src/Runtime.php、src/Console.php、tests/RuntimeTest.php)本身就是大量使用运行时探测 API 的代码,其 CI 中运行 PHPStan level 10,从实践侧验证了升级路径的可行性。
升级到 2.0 的总体心法可以概括为:先在 1.12 + Bleeding Edge 下把所有新行为"剧透"一遍并处理干净,再切换依赖版本;配置层面把旧参数翻译成ignoreErrors标识符与新命名;扩展层面按本文 7.12 的清单逐项改 API 并发布新主版本。遵循这条路径,从 1.x 到 2.0 的迁移是可控、可回退、可逐步验证的。
- 开发工具
- 后端
【免费下载链接】environment
Provides functionality that helps writing PHP code that has runtime-specific execution paths
相关推荐
PHPStan 1.x 升级到 2.0 完整迁移指南:配置变更、API 重构与扩展开发适配
PHPStan 1.x 升级到 2.0 完整迁移指南:配置变更、API 重构与扩展开发适配 本文以 PHPStan 2.2.16 自带的 UPGRADING.m
测试代码质量PHPStan 2.0 升级实战指南:从 1.x 平滑迁移到新主版本(附配置与扩展开发完整变更清单)
PHPStan 2.0 升级实战指南:从 1.x 平滑迁移到新主版本(附配置与扩展开发完整变更清单) 本篇指南以仓库根目录 UPGRADING.md https
开发工具代码质量静态分析Base UI 开源项目教程
Base UI 开源项目教程 1. 项目介绍 Base UI 是一个由 Radix、Floating UI 和 Material UI 的创造者们开发的未样式化
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考