mglaman/phpstan-drupal
GitHub: mglaman/phpstan-drupal
PHPStan 的 Drupal 专用扩展,解决 Drupal 模块和主题缺少自动加载信息导致静态分析困难的问题,并提供 Drupal 特有的代码规范检查规则。
Stars: 208 | Forks: 85
# phpstan-drupal
[](https://github.com/mglaman/phpstan-drupal/actions/workflows/php.yml) [](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` 将被自动包含。
## 获取帮助
请在 [讨论区](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"
]
}
}
}
```
手动安装
如果你不想使用 `phpstan/extension-installer`,请在你的项目的 PHPStan 配置中包含 `extension.neon`: ``` includes: - vendor/mglaman/phpstan-drupal/extension.neon ``` 若要包含 Drupal 特定的分析规则,请包含此文件: ``` includes: - vendor/mglaman/phpstan-drupal/rules.neon ```标签:Bug检测, Composer, DNS解析, Drupal, ffuf, OpenVAS, PHP, PHPStan, 主题开发, 代码审查, 代码规范, 依赖注入, 安全专业人员, 开源框架, 开源项目, 扩展, 持续集成, 插件, 模块开发, 错误基检测, 静态代码分析