phpstan / phpstan-doctrine
PHPStan 的 Doctrine 扩展
Requires
- php: ^7.2 || ^8.0
- phpstan/phpstan: ^1.11.7
Requires (Dev)
- cache/array-adapter: ^1.1
- composer/semver: ^3.3.2
- cweagans/composer-patches: ^1.7.3
- doctrine/annotations: ^1.11 || ^2.0
- doctrine/collections: ^1.6 || ^2.1
- doctrine/common: ^2.7 || ^3.0
- doctrine/dbal: ^2.13.8 || ^3.3.3
- doctrine/lexer: ^2.0 || ^3.0
- doctrine/mongodb-odm: ^1.3 || ^2.4.3
- doctrine/orm: ^2.16.0
- doctrine/persistence: ^2.2.1 || ^3.2
- gedmo/doctrine-extensions: ^3.8
- nesbot/carbon: ^2.49
- nikic/php-parser: ^4.13.2
- php-parallel-lint/php-parallel-lint: ^1.2
- phpstan/phpstan-phpunit: ^1.3.13
- phpstan/phpstan-strict-rules: ^1.5.1
- phpunit/phpunit: ^9.6.16
- ramsey/uuid: ^4.2
- symfony/cache: ^5.4
Conflicts
- doctrine/collections: <1.0
- doctrine/common: <2.7
- doctrine/mongodb-odm: <1.2
- doctrine/orm: <2.5
- doctrine/persistence: <1.3
- 2.0.x-dev
- 1.5.x-dev
- 1.5.3
- 1.5.2
- 1.5.1
- 1.5.0
- 1.4.x-dev
- 1.4.8
- 1.4.7
- 1.4.6
- 1.4.5
- 1.4.4
- 1.4.3
- 1.4.2
- 1.4.1
- 1.4.0
- 1.3.x-dev
- 1.3.69
- 1.3.68
- 1.3.67
- 1.3.66
- 1.3.65
- 1.3.64
- 1.3.63
- 1.3.62
- 1.3.61
- 1.3.60
- 1.3.59
- 1.3.58
- 1.3.57
- 1.3.56
- 1.3.55
- 1.3.54
- 1.3.53
- 1.3.52
- 1.3.51
- 1.3.50
- 1.3.49
- 1.3.48
- 1.3.47
- 1.3.46
- 1.3.45
- 1.3.44
- 1.3.43
- 1.3.42
- 1.3.41
- 1.3.40
- 1.3.39
- 1.3.38
- 1.3.37
- 1.3.36
- 1.3.35
- 1.3.34
- 1.3.33
- 1.3.32
- 1.3.31
- 1.3.30
- 1.3.29
- 1.3.28
- 1.3.27
- 1.3.26
- 1.3.25
- 1.3.24
- 1.3.23
- 1.3.22
- 1.3.21
- 1.3.20
- 1.3.19
- 1.3.18
- 1.3.17
- 1.3.16
- 1.3.15
- 1.3.14
- 1.3.13
- 1.3.12
- 1.3.11
- 1.3.10
- 1.3.9
- 1.3.8
- 1.3.7
- 1.3.6
- 1.3.5
- 1.3.4
- 1.3.3
- 1.3.2
- 1.3.1
- 1.3.0
- 1.2.11
- 1.2.10
- 1.2.9
- 1.2.8
- 1.2.7
- 1.2.6
- 1.2.5
- 1.2.4
- 1.2.3
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.1
- 1.1.0
- 1.0.4
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
- 0.12.44
- 0.12.43
- 0.12.42
- 0.12.41
- 0.12.40
- 0.12.39
- 0.12.38
- 0.12.37
- 0.12.36
- 0.12.35
- 0.12.34
- 0.12.33
- 0.12.32
- 0.12.31
- 0.12.30
- 0.12.29
- 0.12.28
- 0.12.27
- 0.12.26
- 0.12.25
- 0.12.24
- 0.12.23
- 0.12.22
- 0.12.21
- 0.12.20
- 0.12.19
- 0.12.18
- 0.12.17
- 0.12.16
- 0.12.15
- 0.12.14
- 0.12.13
- 0.12.12
- 0.12.11
- 0.12.10
- 0.12.9
- 0.12.8
- 0.12.7
- 0.12.6
- 0.12.5
- 0.12.4
- 0.12.3
- 0.12.2
- 0.12.1
- 0.12.0
- 0.11.6
- 0.11.5
- 0.11.4
- 0.11.3
- 0.11.2
- 0.11.1
- 0.11
- 0.10
- 0.9.1
- 0.9
- 0.8
- 0.7
- 0.6.1
- 0.6
- dev-renovate/major-root-composer
- dev-renovate/major-github-actions
- dev-get-array-result
- dev-test-failures
- dev-revert-array-result
- dev-more-updates
- dev-update-em-create
- dev-update-cache
- dev-update-dependencies
- dev-revert-56-fix/52-find-by-methods
This package is auto-updated.
Last update: 2024-09-13 12:53:04 UTC
README
此扩展提供以下功能
- DQL 验证用于解析错误、未知实体类和未知持久字段。也支持 QueryBuilder 验证。
- 识别 EntityRepository 上的魔法方法
findBy*
、findOneBy*
和countBy*
。 - 验证在 repository 的
findBy
、findBy*
、findOneBy
、findOneBy*
、count
和countBy*
方法调用中的实体字段。 - 正确解释 phpDocs 中的
EntityRepository
,以进行对在 repository 上调用的方法的进一步类型推理。 - 为
Doctrine\ORM\EntityManager::getRepository()
提供正确的返回值。 - 当将
Foo::class
实体类名作为第一个参数提供时,为Doctrine\ORM\EntityManager::find
、getReference
和getPartialReference
提供正确的返回类型。 - 添加
Doctrine\Common\Collections\Collection
上缺失的matching
方法。可以通过将parameters.doctrine.allCollectionsSelectable
设置为false
来关闭此功能。 - 也支持 Doctrine ODM。
- 分析实体列类型与属性字段类型之间的差异。可以通过将
allowNullablePropertyForRequiredField: true
设置来放松此限制。 - 为
Doctrine\ORM\Query::getResult
、getOneOrNullResult
、getSingleResult
、toIterable
和execute
在HYDRATE_OBJECT
模式下提供返回类型(见下文)。
安装
要使用此扩展,请在 Composer 中要求它
composer require --dev phpstan/phpstan-doctrine
如果您还安装了 phpstan/extension-installer,那么您就准备好了!
手动安装
如果您不想使用 phpstan/extension-installer
,请将 extension.neon 包含在您项目的 PHPStan 配置中
includes: - vendor/phpstan/phpstan-doctrine/extension.neon
如果您对 DQL/QueryBuilder 验证感兴趣,请还包含 rules.neon
(您还需要提供 objectManagerLoader
,见下文)
includes: - vendor/phpstan/phpstan-doctrine/rules.neon
配置
如果您的工作库有一个公共基类,您可以在 phpstan.neon
中配置它,PHPStan 将看到您在其中定义的额外方法
parameters: doctrine: ormRepositoryClass: MyApp\Doctrine\BetterEntityRepository odmRepositoryClass: MyApp\Doctrine\BetterDocumentRepository
您可以选择通过提供自己的应用程序中的对象管理器来获得更高级的分析。这将启用 DQL 验证
parameters: doctrine: objectManagerLoader: tests/object-manager.php
对称法 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();
对称法 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();
查询类型推理
如果提供 objectManagerLoader
,则此扩展可以推断 DQL 查询的结果类型。
示例
$query = $entityManager->createQuery('SELECT u FROM Acme\User u'); $query->getResult(); // array<Acme\User> $query = $entityManager->createQuery('SELECT u.id, u.email, u.name FROM Acme\User u'); $query->getResult(); // array<array{id: int, email: string, name: string|null}> $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<Acme\User>
查询是在静态方式下分析的,不需要运行中的数据库服务器。这使用了 Doctrine DQL 解析器和实体元数据。
支持大多数 DQL 功能,包括 GROUP BY
、DISTINCT
、所有种类的 JOIN
、算术表达式、函数、聚合、NEW
等。子查询和 INDEX BY
还不支持(推断的类型将是 mixed
)。
表达式查询类型推理
例如,SUM(e.column)
是否作为 float
、numeric-string
或 int
获取高度依赖于驱动程序、它们的设置和 PHP 版本。此扩展自动检测您的设置,并为 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<User> $query->getResult(Query::HYDRATE_OBJECT); // array<User>
当显式地将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
,如下所示
<?php public function getType(): string; public function getWritableToPropertyType(): Type; public function getWritableToDatabaseType(): Type;
getType()
方法简单返回自定义类型的类名。getWritableToPropertyType()
方法返回自定义类型将要写入实体属性字段的PHPStan类型。基本上,它是自定义类型convertToPHPValue()
方法的返回类型。getWritableToDatabaseType()
方法返回可以写入实体属性字段并写入自定义类型的PHPStan类型。再次,基本上,它是自定义类型convertToDatabaseValue()
的第一个参数所允许的类型。
一般来说,至少对于大多数Doctrine的本地类型,这两个最后的方法将返回相同类型,但这并不总是如此。一个例子是datetime
类型,它可以设置任何\DateTimeInterface
到属性字段,但在从数据库加载时始终包含\DateTime
类型。
可空类型
类型描述符无需处理可空类型,因为这些类型可以透明地根据需要添加/删除到描述符的类型中。因此,即使您的自定义类型允许null
,您也不必从描述符的方法中返回自定义类型和NullType
的联合类型。
ReflectionDescriptor
如果您的自定义类型的convertToPHPValue()
和convertToDatabaseValue()
方法有正确的类型提示,则不需要为它编写自己的描述符。PHPStan\Type\Doctrine\Descriptors\ReflectionDescriptor
可以分析类型提示,并为您完成剩余工作。
如果您的类型的父类是Doctrine的非抽象类型之一,则ReflectionDescriptor
将重用其描述符,甚至在表达式解析(例如AVG(t.cost)
)中。例如,如果您扩展了Doctrine\DBAL\Types\DecimalType
,它将知道sqlite将其作为float|int
检索,而其他驱动程序作为numeric-string
。如果您仅扩展了Doctrine\DBAL\Types\Type
,则应使用自定义描述符,并可选择实现DoctrineTypeDriverAwareDescriptor
以提供特定驱动程序的解析。
注册类型描述符
当您编写自定义类型描述符时,您必须让PHPStan了解它。将以下内容添加到您的phpstan.neon
services: - class: MyCustomTypeDescriptor tags: [phpstan.doctrine.typeDescriptor] # in case you are using the ReflectionDescriptor - factory: PHPStan\Type\Doctrine\Descriptors\ReflectionDescriptor('MyApp\MyCustomTypeName') tags: [phpstan.doctrine.typeDescriptor]
确保类型具有描述符
如果您想确保您永远不会忘记某个自定义类型的描述符,您可以启用
parameters: doctrine: reportUnknownTypes: true
这将在您的实体使用自定义类型而没有描述符时导致失败
#[Entity] abstract class Uuid7Entity { #[Id] #[Column(type: Uuid7Type::NAME)] // reported when descriptor for such type is missing private Uuid7 $hsCode;
自定义DQL函数
任何实现Doctrine的TypedExpression
的自定义DQL函数都被此扩展理解,并以其在getReturnType()
方法中使用的类型进行推断。所有其他自定义DQL函数都被推断为mixed
。请注意,您不能使用原生StringType
进行类型转换(和推断)字符串结果(请参阅ORM问题)。
use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Query\AST\TypedExpression; use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\TokenType; class Floor extends FunctionNode implements TypedExpression { private AST\Node|string $arithmeticExpression; public function getSql(SqlWalker $sqlWalker): string { return 'FLOOR(' . $sqlWalker->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中,占位文件包含许多用literal-string
标记的参数。这是一种以安全为中心的类型,只允许将用代码编写的文字字符串传递到这些参数中。
这降低了SQL注入的风险,因为不允许用用户输入的动态字符串代替literal-string
。
使用此类型的一个例子是Doctrine\Dbal\Connection::executeQuery()
中的$sql
参数。
要在phpstan-doctrine中启用此高级类型,请使用此配置参数
parameters: doctrine: literalString: true