phpstan/phpstan-doctrine
GitHub: phpstan/phpstan-doctrine
PHPStan 的 Doctrine 专用扩展,为 PHP 项目中的 Doctrine ORM/ODM 实体、DQL 查询及 QueryBuilder 提供深度的静态分析和精确的类型推断。
Stars: 673 | Forks: 120
# PHPStan 的 Doctrine 扩展
[](https://github.com/phpstan/phpstan-doctrine/actions)
[](https://packagist.org/packages/phpstan/phpstan-doctrine)
[](https://packagist.org/packages/phpstan/phpstan-doctrine)
* [PHPStan](https://phpstan.org/)
* [Doctrine](https://www.doctrine-project.org/)
此扩展提供以下功能:
* DQL 验证,可检测解析错误、未知的 entity 类以及未知的持久化字段。同时也支持 QueryBuilder 验证。
* 识别 EntityRepository 上的魔术方法 `findBy*`、`findOneBy*` 和 `countBy*`。
* 验证 repository 中 `findBy`、`findBy*`、`findOneBy`、`findOneBy*`、`count` 和 `countBy*` 方法调用里的 entity 字段。
* 正确解析 phpDoc 中的 `EntityRepository`,以便对 repository 上调用的方法进行进一步的类型推断。
* 为 `Doctrine\ORM\EntityManager::getRepository()` 提供正确的返回类型。
* 当 `Foo::class` entity 类名作为第一个参数提供时,为 `Doctrine\ORM\EntityManager::find`、`getReference` 和 `getPartialReference` 提供正确的返回类型。
* 为 `Doctrine\Common\Collections\Collection` 添加缺失的 `matching` 方法。可以通过将 `parameters.doctrine.allCollectionsSelectable` 设置为 `false` 来关闭此功能。
* 同时支持 Doctrine ODM。
* 分析 entity 列类型与属性字段类型之间的差异。可以通过 `allowNullablePropertyForRequiredField: true` 设置来放宽此限制。
* 分析 entity 关联类型与属性字段类型之间的差异(to-one,to-many)。
* 在 `HYDRATE_OBJECT` 模式下,为 `Doctrine\ORM\Query::getResult`、`getOneOrNullResult`、`getSingleResult`、`toIterable` 和 `execute` 提供返回类型(见下文)。
* 报告可能导致 Doctrine proxy 生成问题的 `final` entity 类(当启用原生的 lazy objects 时则允许)。
* 报告可能导致 Doctrine proxy 生成问题的 `final` entity 构造函数。
* 检测 Doctrine 映射配置错误(注解/属性解析问题)。
* 禁止直接使用 Doctrine proxy 类名。
* 为 `EntityManager::flush()` 提供精确的抛出类型(`ORMException`、`UniqueConstraintViolationException`)。
* 与 PHPStan 死代码检测集成 —— entity 属性不会被报告为未使用。将生成的标识符、版本字段和只读 entity 识别为总是有写入操作。
* 支持 [Gedmo doctrine-extensions](https://github.com/doctrine-extensions/DoctrineExtensions) —— 识别由 Gedmo 注解/属性管理的属性(例如 `Timestampable`、`Blameable`、`Slug`),用于死代码检测。
* 当 `isEmpty()` 为 `false` 时,将 `Collection::first()` 和 `Collection::last()` 的返回类型从 `T|false` 缩小为 `T`。
## 安装说明
要使用此扩展,请在 [Composer](https://getcomposer.org/) 中引入它:
```
composer require --dev phpstan/phpstan-doctrine
```
如果你还安装了 [phpstan/extension-installer](https://github.com/phpstan/extension-installer),那么一切就绪了!
## 配置
如果你的 repository 有一个通用的基类,你可以在 `phpstan.neon` 中配置它,PHPStan 将能识别你在其中定义的其他方法:
```
parameters:
doctrine:
ormRepositoryClass: MyApp\Doctrine\BetterEntityRepository
odmRepositoryClass: MyApp\Doctrine\BetterDocumentRepository
```
你可以通过提供来自你自己应用的 object manager 来开启更高级的分析。这将启用 DQL 验证:
```
parameters:
doctrine:
objectManagerLoader: tests/object-manager.php
```
Symfony 4 示例:
```
// tests/object-manager.php
use App\Kernel;
require __DIR__ . '/../config/bootstrap.php';
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
return $kernel->getContainer()->get('doctrine')->getManager();
```
Symfony 5 示例:
```
// tests/object-manager.php
use App\Kernel;
use Symfony\Component\Dotenv\Dotenv;
require __DIR__ . '/../vendor/autoload.php';
(new Dotenv())->bootEnv(__DIR__ . '/../.env');
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
return $kernel->getContainer()->get('doctrine')->getManager();
```
如果你的应用使用多个 entity manager,请从加载器中返回 Doctrine manager
registry。PHPStan Doctrine 将使用它来挑选拥有正在分析的 entity 的 object manager:
```
// tests/object-manager.php
use App\Kernel;
use Symfony\Component\Dotenv\Dotenv;
require __DIR__ . '/../vendor/autoload.php';
(new Dotenv())->bootEnv(__DIR__ . '/../.env');
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
return $kernel->getContainer()->get('doctrine');
```
## 查询类型推断
当提供了 `objectManagerLoader` 时,此扩展可以推断 DQL 查询的结果类型。
示例:
```
$query = $entityManager->createQuery('SELECT u FROM Acme\User u');
$query->getResult(); // array
$query = $entityManager->createQuery('SELECT u.id, u.email, u.name FROM Acme\User u');
$query->getResult(); // array
$query = $entityManager->createQuery('
SELECT u.id, u.email, COALESCE(u.name, "Anonymous") AS name
FROM Acme\User u
');
$query->getSingleResult(Query::HYDRATE_OBJECT); // array{id: int, email: string, name: string}>
$query = $entityManager->createQueryBuilder()
->select('u')
->from(User::class, 'u')
->getQuery();
$query->getResult(); // array
```
查询是静态分析的,不需要运行中的数据库服务器。这利用了 Doctrine DQL 解析器和 entity 元数据。
支持大多数 DQL 特性,包括 `GROUP BY`、`INDEX BY`、`DISTINCT`、所有类型的 `JOIN`、算术表达式、函数、聚合、`NEW` 等。暂不支持子查询(推断类型将为 `mixed`)。
### 表达式的查询类型推断
例如,`SUM(e.column)` 是作为 `float`、`numeric-string` 还是 `int` 获取,很大程度[取决于驱动程序、其设置以及 PHP 版本](https://github.com/janedbal/php-database-drivers-fetch-test)。
此扩展会自动检测你的设置,并为 `pdo_mysql`、`mysqli`、`pdo_sqlite`、`sqlite3`、`pdo_pgsql` 和 `pgsql` 提供相当准确的结果。
### 支持的方法
在不带参数调用,或者 hydrateMode 参数设置为 `Query::HYDRATE_OBJECT` 时,支持 `getResult` 方法:
```
$query = $entityManager->createQuery('SELECT u FROM Acme\User u');
$query->getResult(); // array
$query->getResult(Query::HYDRATE_OBJECT); // array
```
当显式设置 hydrateMode 参数为 `Query::HYDRATE_OBJECT` 时,支持 `getOneOrNullResult`、`getSingleResult`、`toIterable` 和 `execute` 方法:
```
$query = $entityManager->createQuery('SELECT u FROM Acme\User u');
$query->getOneOrNullResult(); // mixed
$query->getOneOrNullResult(Query::HYDRATE_OBJECT); // User
```
这是因为 `Query` 类的设计,除非在调用期间显式指定,否则无法确定这些函数所使用的 hydration 模式。
### 存在问题的用法
并非所有的 QueryBuilder 都能被静态分析,以下是一些最大化类型推断的建议:
- 不要将 QueryBuilder 传递给方法
- 不要在 QueryBuilder 方法中使用动态表达式(主要是在 `select`/`join`/`from`/`set` 中)
你可以通过以下方式启用对无法进行推断的位置的报告:
```
parameters:
doctrine:
reportDynamicQueryBuilders: true
```
## 自定义类型
如果你的应用使用了自定义的 Doctrine 类型,你可以编写自己的类型描述符来正确分析它们。
类型描述符实现了 `PHPStan\Type\Doctrine\Descriptors\DoctrineTypeDescriptor` 接口,该接口如下所示:
```
walkSimpleArithmeticExpression($this->arithmeticExpression) . ')';
}
public function parse(Parser $parser): void
{
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->arithmeticExpression = $parser->SimpleArithmeticExpression();
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
public function getReturnType(): Type
{
return Type::getType(Types::INTEGER);
}
}
```
## 字面量字符串
phpstan-doctrine 中的 stub 文件带有许多标记为 `literal-string` 的参数。这是一种出于安全考虑的类型,只允许将代码中编写的字面量字符串传递给这些参数。
这降低了 SQL 注入的风险,因为来自用户输入的动态字符串不能替代 `literal-string`。
使用此类型的一个例子是 `Doctrine\Dbal\Connection::executeQuery()` 中的 `$sql` 参数。
要在 phpstan-doctrine 中启用此高级类型,请使用此配置参数:
```
parameters:
doctrine:
literalString: true
```
手动安装
如果你不想使用 `phpstan/extension-installer`,请在你的项目 PHPStan 配置中包含 extension.neon: ``` includes: - vendor/phpstan/phpstan-doctrine/extension.neon ``` 如果你需要 DQL/QueryBuilder 验证,请同时包含 `rules.neon`(你还需要提供 `objectManagerLoader`,见下文): ``` includes: - vendor/phpstan/phpstan-doctrine/rules.neon ```标签:Doctrine, ffuf, OpenVAS, PHP, PHPStan, SOC Prime, 云安全监控, 开发工具, 静态分析