mglaman/phpstan-drupal

GitHub: mglaman/phpstan-drupal

PHPStan 的 Drupal 专用扩展,解决 Drupal 模块和主题缺少自动加载信息导致静态分析困难的问题,并提供 Drupal 特有的代码规范检查规则。

Stars: 208 | Forks: 85

# phpstan-drupal [![测试](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/87390c64d1193456.svg)](https://github.com/mglaman/phpstan-drupal/actions/workflows/php.yml) [![CircleCI](https://circleci.com/gh/mglaman/phpstan-drupal.svg?style=svg)](https://circleci.com/gh/mglaman/phpstan-drupal) [PHPStan](https://phpstan.org/) 扩展,用于分析 Drupal 代码。 PHPStan 能够通过使用 Composer 提供的自动加载来[发现符号](https://phpstan.org/user-guide/discovering-symbols)。然而,Drupal 并不为模块和主题提供自动加载信息。此项目注册了这些命名空间,以便 PHPStan 能够自动正确地发现你的 Drupal 代码库中的符号。 ## 用法 当你使用 [`phpstan/extension-installer`](https://github.com/phpstan/extension-installer) 时,`phpstan.neon` 将被自动包含。
手动安装 如果你不想使用 `phpstan/extension-installer`,请在你的项目的 PHPStan 配置中包含 `extension.neon`: ``` includes: - vendor/mglaman/phpstan-drupal/extension.neon ``` 若要包含 Drupal 特定的分析规则,请包含此文件: ``` includes: - vendor/mglaman/phpstan-drupal/rules.neon ```
## 获取帮助 请在 [讨论区](https://github.com/mglaman/phpstan-drupal/discussions) 或 Drupal Slack 的 [#phpstan](https://drupal.slack.com/archives/C033S2JUMLJ) 频道寻求帮助。 ## 从分析中排除测试 要从分析中排除测试,请添加以下参数 ``` parameters: excludePaths: - *Test.php - *TestBase.php ``` ## 弃用测试 此项目依赖于 `phpstan/phpstan-deprecation-rules`,后者添加了弃用规则。我们提供了特定于 Drupal 的弃用范围解析器。 若要仅处理弃用测试,请使用如下的 `phpstan.neon`: ``` parameters: customRulesetUsed: true reportUnmatchedIgnoredErrors: false # Ignore phpstan-drupal extension's rules. ignoreErrors: - '#\Drupal calls should be avoided in classes, use dependency injection instead#' - '#Plugin definitions cannot be altered.#' - '#Missing cache backend declaration for performance.#' - '#Plugin manager has cache backend specified but does not declare cache tags.#' includes: - vendor/mglaman/phpstan-drupal/extension.neon - vendor/phpstan/phpstan-deprecation-rules/rules.neon ``` 若要在使用 `phpstan/extension-installer` 时禁用弃用规则,你可以执行以下操作: ``` { "extra": { "phpstan/extension-installer": { "ignore": [ "phpstan/phpstan-deprecation-rules" ] } } } ``` 有关更多信息,请参阅 `extension-installer` 文档:https://github.com/phpstan/extension-installer#ignoring-a-particular-extension ## 适配你的项目 ### 自定义规则 #### 可选规则 这些规则默认被禁用,以避免在补丁发布中出现意外故障。它们将在未来的次要版本中升级为默认规则集。包含 [bleedingEdge.neon](#bleeding-edge-checks) 以一次性启用所有规则,或者单独启用它们: ``` parameters: drupal: rules: # Enforces that OOP hook implementations using the Hook attribute have the # correct method signature for hook_form_alter, hook_form_FORM_ID_alter, etc. # Requires Drupal 10.3+ (Hook attribute). hookRules: true # Flags non-abstract test classes whose names do not end with "Test". testClassSuffixNameRule: true # Flags properties that are private or read-only in classes using # DependencySerializationTrait, which does not support them. dependencySerializationTraitPropertyRule: true # Flags calls to AccessResult static methods (::allowed(), ::forbidden(), etc.) # whose argument type already makes the condition always true or always false. accessResultConditionRule: true # Flags addCacheableDependency() calls whose argument does not implement # CacheableDependencyInterface. cacheableDependencyRule: true # Flags logger channel objects (from LoggerChannelFactoryInterface::get()) that # are assigned to a property in a class using DependencySerializationTrait, # which cannot serialize logger channels correctly. loggerFromFactoryPropertyAssignmentRule: true # Flags direct injection of EntityStorageInterface (or a subtype) into a # constructor. Inject EntityTypeManagerInterface and call getStorage() instead. entityStorageDirectInjectionRule: true ``` #### 禁用对扩展 `@internal` 类的检查 你可以通过将以下内容添加到你的 `phpstan.neon` 来禁用 `ClassExtendsInternalClassRule` 规则: ``` parameters: drupal: rules: classExtendsInternalClassRule: false ``` #### 禁用扩展 你可以禁用各种扩展。这在为 Drupal Core 贡献代码以改进其类型时非常有用。 ``` parameters: drupal: extensions: entityFieldsViaMagicReflection: true entityFieldMethodsViaMagicReflection: true entityQuery: true entityRepository: true stubFiles: true ``` 这两个选项默认均已启用。 #### 前沿检查 `bleedingEdge.neon` 启用了所有[可选规则](#opt-in-rules),以及对 `.api.php` 文件的 hook 弃用检查。新规则在通过次要版本升级为默认规则集之前,会首先出现在这里。 ``` includes: - vendor/mglaman/phpstan-drupal/bleedingEdge.neon ``` 它当前启用的功能: - `checkCoreDeprecatedHooksInApiFiles` — 报告 Drupal 核心 `.api.php` 文件中被弃用的 hook 实现 - `checkContribDeprecatedHooksInApiFiles` — 报告 contrib 模块 `.api.php` 文件中被弃用的 hook 实现 - `hookRules` — 验证 OOP hook 方法签名(需要 Drupal 10.3+) - `testClassSuffixNameRule` — 非抽象测试类名必须以 `Test` 结尾 - `dependencySerializationTraitPropertyRule` — 标记使用 `DependencySerializationTrait` 的类中的私有或只读属性 - `accessResultConditionRule` — 标记永远为真/永远为假的 `AccessResult` 条件 - `cacheableDependencyRule` — 标记带有不可缓存参数的 `addCacheableDependency()` 调用 - `loggerFromFactoryPropertyAssignmentRule` — 标记在使用 `DependencySerializationTrait` 的类中被赋值给属性的 logger 通道 - `entityStorageDirectInjectionRule` — 标记在构造函数中直接注入 entity storage;应改为注入 `EntityTypeManagerInterface` 并调用 `getStorage()` - `containerHasAlwaysTrue: false` — `ContainerInterface::has()` 针对已知服务返回 `bool` 而非总是 `true`,从而防止可能导致开发者移除合法条件服务防护的误报 #### 检测引用当前 Drupal.org 问题的 @todo 注释(contrib CI) `TodoCommentWithIssueUrlRule` 是一个用于 Drupal contrib CI 流水线的可选规则。当在 GitLab 合并请求中运行 PHPStan 时,如果任何 `@todo` 注释包含与当前问题匹配的 drupal.org 问题 URL,它将报告错误——例如: ``` // @todo Remove once https://drupal.org/i/3456789 is resolved. ``` 这可以防止特定问题的 TODO 在未解决的情况下被意外合并。 该规则从标准的 GitLab CI 环境变量中自动检测当前问题的 NID: - `CI_MERGE_REQUEST_SOURCE_BRANCH_NAME`(例如 `3456789-my-feature`) - `CI_MERGE_REQUEST_SOURCE_PROJECT_PATH`(例如 `issue/mymodule-3456789`) 当这两个变量均未设置时,它是静默的,因此可以安全地包含在共享配置中。 该规则**默认未注册**。若要启用它,请将其添加到你的项目的 `phpstan.neon` 中: ``` rules: - mglaman\PHPStanDrupal\Rules\Drupal\TodoCommentWithIssueUrlRule ``` 支持识别 `drupal.org/i/{nid}` 和 `drupal.org/project/{project}/issues/{nid}` 两种 URL 格式。 ### 自定义 PHPDoc 类型 phpstan-drupal 提供了自定义 PHPDoc 类型,可用于提高 Drupal 代码的类型安全性。 #### `entity-type-id` `entity-type-id` 类型表示一个有效的 Drupal 实体类型 ID 字符串(例如 `'node'`、`'user'`、`'taxonomy_term'`)。当在期望 `entity-type-id` 的地方传递了一个非已知实体类型 ID 的常量字符串时,PHPStan 将报告错误。 Drupal 编码标准要求将 PHPStan 特定的类型保留在 `@phpstan-param` 和 `@phpstan-return` 标签中,而不是标准的 `@param` 和 `@return` 标签中: ``` /** * Loads an entity by its entity type ID and entity ID. * * @param string $entityTypeId * The entity type ID. * @param int|string $id * The entity ID. * * @phpstan-param entity-type-id $entityTypeId */ public function loadEntity(string $entityTypeId, int|string $id): ?EntityInterface { return $this->entityTypeManager->getStorage($entityTypeId)->load($id); } ``` 对于返回类型: ``` /** * Returns the entity type ID. * * @return string * The entity type ID. * * @phpstan-return entity-type-id */ public function getEntityTypeId(): string { return $this->entityTypeId; } ``` 已知的实体类型 ID 来源于 `drupal.entityMapping` 参数。有关如何注册自定义实体类型以便其 ID 也能被识别,请参阅 [Entity storage 映射](#entity-storage-mappings)。 ### Entity storage 映射。 `EntityTypeManagerGetStorageDynamicReturnTypeExtension` 服务有助于映射动态返回类型。它会检查传递的实体类型 ID,并尝试返回默认 `EntityStorageInterface` 之外的已知存储类。默认映射可以在 `extension.neon` 中找到。例如: ``` parameters: drupal: entityMapping: block: class: Drupal\block\Entity\Block storage: Drupal\Core\Config\Entity\ConfigEntityStorage node: class: Drupal\node\Entity\Node storage: Drupal\node\NodeStorage taxonomy_term: class: Drupal\taxonomy\Entity\Term storage: Drupal\taxonomy\TermStorage user: class: Drupal\user\Entity\User storage: Drupal\user\UserStorage ``` 要添加对自定义实体的支持,你可以将相同的定义添加到你的项目的 `phpstan.neon` 中。有关为 Search API 添加映射的示例,请参见以下内容: ``` parameters: drupal: entityMapping: search_api_index: class: Drupal\search_api\Entity\Index storage: Drupal\search_api\Entity\SearchApiConfigEntityStorage search_api_server: class: Drupal\search_api\Entity\Server storage: Drupal\search_api\Entity\SearchApiConfigEntityStorage ``` 类似地,`EntityStorageDynamicReturnTypeExtension` 服务有助于确定在使用 entity storage 时被加载、创建等的实体的类型。 例如,当使用 ``` $node = \Drupal::entityTypeManager()->getStorage('node')->create(['type' => 'page', 'title' => 'foo']); ``` 它有助于了解 `$node` 变量的类型是 `Drupal\node\Entity\Node`。 默认映射可以在 `extension.neon` 中找到: ``` parameters: drupal: entityMapping: block: class: Drupal\block\Entity\Block storage: Drupal\Core\Config\Entity\ConfigEntityStorage node: class: Drupal\node\Entity\Node storage: Drupal\node\NodeStorage taxonomy_term: class: Drupal\taxonomy\Entity\Term storage: Drupal\taxonomy\TermStorage user: class: Drupal\user\Entity\User storage: Drupal\user\UserStorage ``` 同样地,要添加对自定义实体的支持,你可以将相同的定义添加到你的项目的 `phpstan.neon` 中。 ### 为 contrib 模块提供实体类型映射 贡献模块可以提供自己的映射,当用户使用 `phpstan/extension-installer` 时,这些映射可以在用户的代码库中自动注册。extension installer 会扫描已安装包的 `composer.json` 中的 `extra.phpstan` 值。这将自动捆绑包含实体映射配置的指定包含文件。 例如,Paragraphs 模块可以有以下 `entity_mapping.neon` 文件: ``` parameters: drupal: entityMapping: paragraph: class: Drupal\paragraphs\Entity\Paragraph paragraphs_type: class: Drupal\paragraphs\Entity\ParagraphsType ``` 然后在 Paragraphs 的 `composer.json` 中,`entity_mapping.neon` 将作为一个 PHPStan 包含文件被提供 ``` { "name": "drupal/paragraphs", "description": "Enables the creation of Paragraphs entities.", "type": "drupal-module", "license": "GPL-2.0-or-later", "require": { "drupal/entity_reference_revisions": "~1.3" }, "extra": { "phpstan": { "includes": [ "entity_mapping.neon" ] } } } ```
标签:Bug检测, Composer, DNS解析, Drupal, ffuf, OpenVAS, PHP, PHPStan, 主题开发, 代码审查, 代码规范, 依赖注入, 安全专业人员, 开源框架, 开源项目, 扩展, 持续集成, 插件, 模块开发, 错误基检测, 静态代码分析