phpstan/phpstan-doctrine

GitHub: phpstan/phpstan-doctrine

PHPStan 的 Doctrine 专用扩展,为 PHP 项目中的 Doctrine ORM/ODM 实体、DQL 查询及 QueryBuilder 提供深度的静态分析和精确的类型推断。

Stars: 673 | Forks: 120

# PHPStan 的 Doctrine 扩展 [![Build](https://static.pigsec.cn/wp-content/uploads/repos/cas/2c/2c00db31cfb3d0cfb0e8190631a2236bbfe0bebc45914cef88e6ef4baf5b0b5b.svg)](https://github.com/phpstan/phpstan-doctrine/actions) [![Latest Stable Version](https://poser.pugx.org/phpstan/phpstan-doctrine/v/stable)](https://packagist.org/packages/phpstan/phpstan-doctrine) [![License](https://poser.pugx.org/phpstan/phpstan-doctrine/license)](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),那么一切就绪了!
手动安装 如果你不想使用 `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 ```
## 配置 如果你的 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 ```
标签:Doctrine, ffuf, OpenVAS, PHP, PHPStan, SOC Prime, 云安全监控, 开发工具, 静态分析