PHPStan 错误标识符 new.internalInterface 完全解读:禁止实例化 @internal 内部接口
2026/9/23 18:05:39 网站建设 项目流程
  • 开发工具
  • 代码质量
  • 静态分析

【免费下载链接】phpstan

PHP Static Analysis Tool - discover bugs in your code without running it!

项目地址:https://gitcode.com/gh_mirrors/ph/phpstan
点击查看免费下载

本篇技术指南围绕 PHPStan 错误标识符new.internalInterface展开,讲解静态分析器在"实例化(new)一个标记为@internal的内部接口"场景下如何报错、为何报错以及如何修复。读完本文,你将掌握 PHPStan 内部 API 约束体系的判定逻辑、new前缀标识符家族的组织方式,以及在实际项目中安全处理内部接口引用的完整实战方案。

一、错误标识符总览:什么是new.internalInterface

new.internalInterface是 PHPStan 在实例化上下文(instantiation context)中检测到内部接口引用时报告的错误标识符。其官方定义为:

Referencing an internal interface in an instantiation context.(在实例化上下文中引用了内部接口。)

该标识符的元数据在 website/errors/new.internalInterface.md 的 frontmatter 中声明了三个关键属性:

  • title: "new.internalInterface":错误标识符全名;
  • ignorable: true:该错误可通过ignoreErrors配置忽略;
  • unlikely: true:该标识符被标记为"不太可能单独出现"——因为接口根本无法实例化,此场景通常会被更基础的new.interface错误优先命中(详见下文第三节)。

从错误标识符的命名规则看(参见 CLAUDE.md 中的前缀参考表),前缀new明确对应new ClassName()实例化表达式。同样的前缀还派生出new.interfacenew.internalTraitnew.noConstructornew.nonObjectnew.privateConstructornew.protectedConstructor等一组与实例化相关的标识符(见 website/src/errorsIdentifiers.json)。

从源码结构看该标识符的来源

根据错误标识符与规则类的映射表 website/src/errorsIdentifiers.json,new.internalInterface由规则类PHPStan\Rules\InternalTag\RestrictedInternalClassNameUsageExtension产生。该类属于 phpstan-src 仓库中src/Rules/InternalTag/目录下的"内部标签限制"规则族——该规则族专门负责检查@internal标记类型在各种使用位置的越权访问,与new.internalInterface同族的标识符还包括assert.internalInterfaceinstanceof.internalInterfacecatch.internalInterfaceproperty.internalInterfacestaticMethod.internalInterface等二十余个(这些文档均收录于 website/errors 目录)。

二、触发场景:完整的可复现示例

文档给出了最小可复现示例(见 website/errors/new.internalInterface.md):

<?php declare(strict_types = 1); namespace Vendor { /** @internal */ interface Handler { public function handle(): void; } } namespace App { $handler = new \Vendor\Handler(); // error: Instantiation of internal interface Vendor\Handler. }

该示例包含两个关键要素:

  1. 接口被标记为@internalVendor命名空间中的Handler接口通过 PHPDoc 标签@internal声明为包内部类型;
  2. 在外部命名空间进行实例化App命名空间中通过new \Vendor\Handler()试图实例化该接口。

PHPStan 对第二个要素——new实例化表达式中的\Vendor\Handler类型引用——进行检查时发现它指向一个@internal接口,于是上报new.internalInterface错误,提示信息为Instantiation of internal interface Vendor\Handler.

需要注意的是,示例中的$handler = new \Vendor\Handler();只是触发检测的最小代码形态。实际项目中,@internal接口的"越权使用"不局限于实例化,凡是类型名出现在任何使用位置都可能被同族标识符命中——例如instanceof检查对应instanceof.internalInterface(见 website/errors/instanceof.internalInterface.md),@phpstan-assert断言对应assert.internalInterface(见 website/errors/assert.internalInterface.md)。理解这一族错误,本质上就是理解 PHPStan 对@internal契约的强制约束。

三、为什么很少单独出现:与new.interface的优先级关系

原文档明确指出一个重要的实践经验(见 website/errors/new.internalInterface.md):

In practice, this is typically reported asnew.interfacebecause interfaces cannot be instantiated at all. Thenew.internalInterfaceidentifier is reported when the internal access violation is the primary concern.

翻译过来即:实际场景中该错误通常表现为new.interface,因为接口从根本上就无法实例化;只有当"内部 API 越权访问"成为首要问题时,PHPStan 才报告new.internalInterface

对比new.interface的文档(见 website/errors/new.interface.md):

<?php declare(strict_types = 1); interface LoggerInterface { public function log(string $message): void; } $logger = new LoggerInterface();

new.interface描述的问题是"接口不能被实例化"——这是 PHP 语言层面的硬性约束:接口只定义契约、不提供方法实现,new一个接口名会在运行时直接触发致命错误。而new.internalInterface描述的问题是"内部接口被外部越权引用"——这是 API 设计层面的约束:@internal类型不保证向后兼容。

两个标识符的关系可以归纳为:

维度new.interfacenew.internalInterface
核心问题接口无法实例化(语言语义)内部类型被越权使用(API 契约)
判定来源InstantiationRule(见 errorsIdentifiers.json 中new.nonObject等同类规则)RestrictedInternalClassNameUsageExtension(内部标签规则族)
报告时机只要new目标是接口即报告仅当内部访问违规是首要关注点时报告
严重程度运行期必然致命错误可能当前能运行,但未来版本随时会坏

因此,如果你的代码同时命中两者,PHPStan 通常优先报告new.interface(接口实例化本身就不合法);只有内部契约的破坏才是分析重点时,new.internalInterface才作为独立标识符浮出水面。这也解释了 frontmatter 中unlikely: true标记的缘由。

四、为什么会报告:@internal的 API 契约语义

原文档"Why is it reported?"一节给出的核心解释(见 website/errors/new.internalInterface.md):

  • 内部接口正在被实例化,且调用方位于其根命名空间之外
  • 标记为@internal的接口不打算在定义它的包或命名空间之外被使用
  • 内部接口可能在未来的版本中无通知地变更或被移除

这三点构成了 PHPStan 报告此类错误的设计动机:@internal是 PHP 生态中广泛认可的一种"实现细节"标记。第三方包用它在公开 API 表面之下隐藏实现细节;而外部代码一旦直接引用@internal类型,就形成了一种脆弱的、对实现细节的隐式依赖——当包的下一个版本重构内部结构时,这类引用会静默损坏。PHPStan 通过静态分析在编译期/分析期就暴露出这种隐患,而不是等到升级依赖后由运行时错误来惩罚。

从同类文档的表述可以更完整地理解这套契约(如 website/errors/assert.internalInterface.md 所述):依赖内部类型会在你的断言、实例化、instanceof等位置形成对"会无通知变更的实现细节"的脆弱依赖。这本质上是把 API 边界问题前置到了静态分析阶段。

五、如何修复:两种标准解法

原文档给出了两种修复路径(见 website/errors/new.internalInterface.md)。

解法一:改用包的公开 API

不要直接new内部接口,而是使用包公共 API 提供的工厂方法或具体类:

namespace App { - $handler = new \Vendor\Handler(); + $handler = \Vendor\HandlerFactory::create(); }

这一方案的要点是:Vendor包既然把Handler标记为@internal,说明它期望调用方通过公开的创建入口(工厂、构造器、DI 容器、服务定位器等)获得实例,而不是自己直接实例化。HandlerFactory::create()返回的具体类型可能是Handler接口的公开实现类,也可能是满足公开契约的别的类型——总之,类型引用的书写位置从"越权使用内部类型"变成了"合法使用公开 API"。

解法二:从源头开放内部接口

如果你自己就是该内部接口的维护者,可以考虑两条路:

  • 将接口设为公开:移除@internal标记,将其纳入包的正式公共 API 并承诺向后兼容;
  • 提供公开替代品:保留内部接口不动,另设计一个公开接口(或抽象类),并让内部实现实现它。

如果两个方向都不可行(例如你只是第三方代码的使用者、无法影响上游包),文档建议与包维护者沟通,请求为你的用例提供公开 API。

修复时的通用原则

结合 CLAUDE.md 中的修复偏好顺序,此类错误的修复优先级应为:

  1. 修复真正的 bug——使用公开类型替换内部类型引用;
  2. 如果问题出在类型声明上,用原生 PHP 类型声明或 PHPDoc 类型(@param@return@var)收紧类型;
  3. 在函数体内做类型收窄;
  4. 只有规则可配置时才考虑调整 PHPStan 配置。

注意:不要通过ignoreErrors忽略该错误来"解决"问题——ignorable: true只说明技术上允许忽略,但忽略意味着把脆弱依赖继续留在代码里,与 PHPStan 报告它的初衷背道而驰。

六、进阶:new.internalInterfacenew.internalTrait的同族关系

在 website/src/errorsIdentifiers.json 中可以看到,紧邻new.internalInterface的是new.internalTrait,二者映射到同一个规则类RestrictedInternalClassNameUsageExtension,且源码位置完全一致(src/Rules/InternalTag/RestrictedInternalClassNameUsageExtension.php#L65)。

这说明 PHPStan 内部标签规则族对"内部类名在实例化上下文中的使用"采用统一判定:只要new的目标是被@internal标记的类、接口或 trait,就由同一规则上报new.internal*系列标识符。同理,new.interface则聚焦"接口不可实例化"这一与@internal无关的通用问题。

new前缀下的标识符家族(映射自 errorsIdentifiers.json)还包括:

标识符含义
new.noConstructor目标类没有构造函数(对应InstantiationRule
new.nonObjectnew的目标不是可实例化对象
new.privateConstructor/new.protectedConstructor构造函数可见性不允许外部实例化

理解这条家族脉络,有助于你在排查实例化相关报错时快速定位:new.internal*看 API 契约,new.interface看语言语义,new.privateConstructor等看访问控制——三者是不同层面的约束,修复策略也各不相同。

七、实践要点总结

  • 快速识别:看到错误信息Instantiation of internal interface <FQCN>.,即对应new.internalInterface;标识符的 frontmatter 文档位于 website/errors/new.internalInterface.md。
  • 判定根因:检查被new的类型是否带有@internal标记,以及调用代码是否位于该类型定义命名空间之外。
  • 优先修复顺序:换用工厂/公开实现 → 开放接口(若你拥有该类型)→ 与上游维护者沟通申请公开 API。
  • 识别连带风险:即使代码当前能运行,@internal引用也是"定时炸弹",升级依赖时可能随时失效,因此不应以忽略错误了事。
  • 举一反三:同样的@internal契约约束遍布instanceofcatch@phpstan-assert、属性声明、静态方法调用等所有类型使用位置,分别对应instanceof.internalInterfacecatch.internalInterfaceassert.internalInterfaceproperty.internalInterfacestaticMethod.internalInterface等同族标识符,完整列表可查阅 website/src/errorsIdentifiers.json 与 website/errors 目录下的对应文档。

延伸阅读:想深入了解 PHPStan 错误标识符文档体系的生成规则与命名前缀约定,可阅读 website/errors/CLAUDE.md;想了解同族@internal约束在instanceof场景下的表现,可对照 website/errors/instanceof.internalInterface.md。

  • 开发工具
  • 代码质量
  • 静态分析

【免费下载链接】phpstan

PHP Static Analysis Tool - discover bugs in your code without running it!

项目地址:https://gitcode.com/gh_mirrors/ph/phpstan
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询