From ca69937830f90726affd9775371d8700ca32e162 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 5 Aug 2024 15:08:08 +0200 Subject: [PATCH 01/71] Open 1.5.x-dev --- .github/workflows/build.yml | 2 +- .github/workflows/platform-test.yml | 2 +- .github/workflows/test-projects.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 476203bb..28b40c02 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "1.4.x" + - "1.5.x" jobs: lint: diff --git a/.github/workflows/platform-test.yml b/.github/workflows/platform-test.yml index 22e867ea..5c063f0e 100644 --- a/.github/workflows/platform-test.yml +++ b/.github/workflows/platform-test.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "1.4.x" + - "1.5.x" jobs: tests: diff --git a/.github/workflows/test-projects.yml b/.github/workflows/test-projects.yml index 6bd1e05b..d33dcf6f 100644 --- a/.github/workflows/test-projects.yml +++ b/.github/workflows/test-projects.yml @@ -5,7 +5,7 @@ name: "Test projects" on: push: branches: - - "1.4.x" + - "1.5.x" jobs: test-projects: From e71f08d9f69be65da76e61b72d684a581bc982b4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 5 Aug 2024 15:14:53 +0200 Subject: [PATCH 02/71] Get rid of bleedingEdge stubs, use a separate config option for enabling literal-string parameters --- extension.neon | 9 +- ...LiteralStringTypeNodeResolverExtension.php | 45 +++++ .../Doctrine/StubFilesExtensionLoader.php | 28 +--- .../DBAL/ArrayParameterType.stub | 0 stubs/DBAL/Connection.stub | 56 +++++++ .../{bleedingEdge => }/DBAL/Connection4.stub | 6 +- .../DBAL/ParameterType.stub | 0 stubs/EntityRepository.stub | 8 + stubs/ORM/QueryBuilder.stub | 134 +++++++++++++++ stubs/bleedingEdge/DBAL/Connection.stub | 64 ------- stubs/bleedingEdge/EntityRepository.stub | 74 --------- stubs/bleedingEdge/ORM/QueryBuilder.stub | 156 ------------------ 12 files changed, 261 insertions(+), 319 deletions(-) create mode 100644 src/PhpDoc/Doctrine/DoctrineLiteralStringTypeNodeResolverExtension.php rename stubs/{bleedingEdge => }/DBAL/ArrayParameterType.stub (100%) rename stubs/{bleedingEdge => }/DBAL/Connection4.stub (89%) rename stubs/{bleedingEdge => }/DBAL/ParameterType.stub (100%) delete mode 100644 stubs/bleedingEdge/DBAL/Connection.stub delete mode 100644 stubs/bleedingEdge/EntityRepository.stub delete mode 100644 stubs/bleedingEdge/ORM/QueryBuilder.stub diff --git a/extension.neon b/extension.neon index f4d3b7ef..84bc0d04 100644 --- a/extension.neon +++ b/extension.neon @@ -196,8 +196,6 @@ services: class: PHPStan\Stubs\Doctrine\StubFilesExtensionLoader tags: - phpstan.stubFilesExtension - arguments: - bleedingEdge: %featureToggles.bleedingEdge% doctrineQueryBuilderArgumentsProcessor: class: PHPStan\Type\Doctrine\ArgumentsProcessor @@ -434,6 +432,13 @@ services: tags: - phpstan.phpDoc.typeNodeResolverExtension + - + class: PHPStan\PhpDoc\Doctrine\DoctrineLiteralStringTypeNodeResolverExtension + arguments: + bleedingEdge: %featureToggles.bleedingEdge% + tags: + - phpstan.phpDoc.typeNodeResolverExtension + - class: PHPStan\Type\Doctrine\EntityManagerInterfaceThrowTypeExtension tags: diff --git a/src/PhpDoc/Doctrine/DoctrineLiteralStringTypeNodeResolverExtension.php b/src/PhpDoc/Doctrine/DoctrineLiteralStringTypeNodeResolverExtension.php new file mode 100644 index 00000000..ce73011e --- /dev/null +++ b/src/PhpDoc/Doctrine/DoctrineLiteralStringTypeNodeResolverExtension.php @@ -0,0 +1,45 @@ +bleedingEdge = $bleedingEdge; + } + + public function resolve(TypeNode $typeNode, NameScope $nameScope): ?Type + { + if (!$typeNode instanceof IdentifierTypeNode) { + return null; + } + + if ($typeNode->name !== '__doctrine-literal-string') { + return null; + } + + if ($this->bleedingEdge) { + return new IntersectionType([ + new StringType(), + new AccessoryLiteralStringType(), + ]); + } + + return new StringType(); + } + +} diff --git a/src/Stubs/Doctrine/StubFilesExtensionLoader.php b/src/Stubs/Doctrine/StubFilesExtensionLoader.php index 95d43a66..97656d2d 100644 --- a/src/Stubs/Doctrine/StubFilesExtensionLoader.php +++ b/src/Stubs/Doctrine/StubFilesExtensionLoader.php @@ -9,7 +9,6 @@ use PHPStan\PhpDoc\StubFilesExtension; use function class_exists; use function dirname; -use function file_exists; use function strpos; class StubFilesExtensionLoader implements StubFilesExtension @@ -18,39 +17,28 @@ class StubFilesExtensionLoader implements StubFilesExtension /** @var Reflector */ private $reflector; - /** @var bool */ - private $bleedingEdge; - public function __construct( - Reflector $reflector, - bool $bleedingEdge + Reflector $reflector ) { $this->reflector = $reflector; - $this->bleedingEdge = $bleedingEdge; } public function getFiles(): array { $stubsDir = dirname(dirname(dirname(__DIR__))) . '/stubs'; - $path = $stubsDir; - - if ($this->bleedingEdge === true) { - $path .= '/bleedingEdge'; - } - $files = []; - if (file_exists($path . '/DBAL/Connection4.stub') && $this->isInstalledVersion('doctrine/dbal', 4)) { - $files[] = $path . '/DBAL/Connection4.stub'; - $files[] = $path . '/DBAL/ArrayParameterType.stub'; - $files[] = $path . '/DBAL/ParameterType.stub'; + if ($this->isInstalledVersion('doctrine/dbal', 4)) { + $files[] = $stubsDir . '/DBAL/Connection4.stub'; + $files[] = $stubsDir . '/DBAL/ArrayParameterType.stub'; + $files[] = $stubsDir . '/DBAL/ParameterType.stub'; } else { - $files[] = $path . '/DBAL/Connection.stub'; + $files[] = $stubsDir . '/DBAL/Connection.stub'; } - $files[] = $path . '/ORM/QueryBuilder.stub'; - $files[] = $path . '/EntityRepository.stub'; + $files[] = $stubsDir . '/ORM/QueryBuilder.stub'; + $files[] = $stubsDir . '/EntityRepository.stub'; $hasLazyServiceEntityRepositoryAsParent = false; diff --git a/stubs/bleedingEdge/DBAL/ArrayParameterType.stub b/stubs/DBAL/ArrayParameterType.stub similarity index 100% rename from stubs/bleedingEdge/DBAL/ArrayParameterType.stub rename to stubs/DBAL/ArrayParameterType.stub diff --git a/stubs/DBAL/Connection.stub b/stubs/DBAL/Connection.stub index 8e88410a..d11b040f 100644 --- a/stubs/DBAL/Connection.stub +++ b/stubs/DBAL/Connection.stub @@ -2,7 +2,63 @@ namespace Doctrine\DBAL; +use Doctrine\DBAL\Cache\CacheException; +use Doctrine\DBAL\Cache\QueryCacheProfile; +use Doctrine\DBAL\Types\Type; + class Connection { + /** + * Executes an SQL statement with the given parameters and returns the number of affected rows. + * + * Could be used for: + * - DML statements: INSERT, UPDATE, DELETE, etc. + * - DDL statements: CREATE, DROP, ALTER, etc. + * - DCL statements: GRANT, REVOKE, etc. + * - Session control statements: ALTER SESSION, SET, DECLARE, etc. + * - Other statements that don't yield a row set. + * + * This method supports PDO binding types as well as DBAL mapping types. + * + * @param __doctrine-literal-string $sql SQL statement + * @param list|array $params Statement parameters + * @param array|array $types Parameter types + * + * @return int|string The number of affected rows. + * + * @throws Exception + */ + public function executeStatement($sql, array $params = [], array $types = []); + + /** + * Executes an, optionally parameterized, SQL query. + * + * If the query is parametrized, a prepared statement is used. + * If an SQLLogger is configured, the execution is logged. + * + * @param __doctrine-literal-string $sql SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @throws Exception + */ + public function executeQuery( + string $sql, + array $params = [], + $types = [], + ?QueryCacheProfile $qcp = null + ): Result; + + /** + * Executes a caching query. + * + * @param __doctrine-literal-string $sql SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types + * + * @throws CacheException + * @throws Exception + */ + public function executeCacheQuery($sql, $params, $types, QueryCacheProfile $qcp): Result; } diff --git a/stubs/bleedingEdge/DBAL/Connection4.stub b/stubs/DBAL/Connection4.stub similarity index 89% rename from stubs/bleedingEdge/DBAL/Connection4.stub rename to stubs/DBAL/Connection4.stub index 0e6edd58..0500252f 100644 --- a/stubs/bleedingEdge/DBAL/Connection4.stub +++ b/stubs/DBAL/Connection4.stub @@ -24,7 +24,7 @@ class Connection * * This method supports PDO binding types as well as DBAL mapping types. * - * @param literal-string $sql SQL statement + * @param __doctrine-literal-string-string $sql SQL statement * @param list|array $params Statement parameters * @param WrapperParameterTypeArray $types Parameter types * @@ -40,7 +40,7 @@ class Connection * If the query is parametrized, a prepared statement is used. * If an SQLLogger is configured, the execution is logged. * - * @param literal-string $sql SQL query + * @param __doctrine-literal-string-string $sql SQL query * @param list|array $params Query parameters * @param WrapperParameterTypeArray $types Parameter types * @@ -56,7 +56,7 @@ class Connection /** * Executes a caching query. * - * @param literal-string $sql SQL query + * @param __doctrine-literal-string-string $sql SQL query * @param list|array $params Query parameters * @param WrapperParameterTypeArray $types Parameter types * diff --git a/stubs/bleedingEdge/DBAL/ParameterType.stub b/stubs/DBAL/ParameterType.stub similarity index 100% rename from stubs/bleedingEdge/DBAL/ParameterType.stub rename to stubs/DBAL/ParameterType.stub diff --git a/stubs/EntityRepository.stub b/stubs/EntityRepository.stub index cfa838fd..f6fc8d17 100644 --- a/stubs/EntityRepository.stub +++ b/stubs/EntityRepository.stub @@ -63,4 +63,12 @@ class EntityRepository implements ObjectRepository */ public function matching(Criteria $criteria); + /** + * @param __doctrine-literal-string $alias + * @param __doctrine-literal-string|null $indexBy + * + * @return QueryBuilder + */ + public function createQueryBuilder($alias, $indexBy = null); + } diff --git a/stubs/ORM/QueryBuilder.stub b/stubs/ORM/QueryBuilder.stub index db14c5b2..46b8e0ee 100644 --- a/stubs/ORM/QueryBuilder.stub +++ b/stubs/ORM/QueryBuilder.stub @@ -2,6 +2,8 @@ namespace Doctrine\ORM; +use Doctrine\ORM\Query\Expr; + class QueryBuilder { @@ -21,4 +23,136 @@ class QueryBuilder { } + /** + * @param string $dqlPartName + * @param __doctrine-literal-string|object|list<__doctrine-literal-string>|array{join: array} $dqlPart + * @param bool $append + * + * @return $this + */ + public function add($dqlPartName, $dqlPart, $append = false) + { + + } + + /** + * @param __doctrine-literal-string|null $delete + * @param __doctrine-literal-string|null $alias + * + * @return $this + */ + public function delete($delete = null, $alias = null) + { + + } + + /** + * @param __doctrine-literal-string|null $update + * @param __doctrine-literal-string|null $alias + * + * @return $this + */ + public function update($update = null, $alias = null) + { + + } + + /** + * @param __doctrine-literal-string|class-string $from + * @param __doctrine-literal-string $alias + * @param __doctrine-literal-string|null $indexBy + * + * @return $this + */ + public function from($from, $alias, $indexBy = null) + { + + } + + /** + * @param __doctrine-literal-string|class-string $join + * @param __doctrine-literal-string $alias + * @param Expr\Join::ON|Expr\Join::WITH|null $conditionType + * @param __doctrine-literal-string|Expr\Comparison|Expr\Composite|Expr\Func|null $condition + * @param __doctrine-literal-string|null $indexBy + * + * @return $this + */ + public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) + { + + } + + /** + * @param __doctrine-literal-string|class-string $join + * @param __doctrine-literal-string $alias + * @param Expr\Join::ON|Expr\Join::WITH|null $conditionType + * @param __doctrine-literal-string|Expr\Comparison|Expr\Composite|Expr\Func|null $condition + * @param __doctrine-literal-string|null $indexBy + * + * @return $this + */ + public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) + { + + } + + /** + * @param __doctrine-literal-string|class-string $join + * @param __doctrine-literal-string $alias + * @param Expr\Join::ON|Expr\Join::WITH|null $conditionType + * @param __doctrine-literal-string|Expr\Comparison|Expr\Composite|Expr\Func|null $condition + * @param __doctrine-literal-string|null $indexBy + * + * @return $this + */ + public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null) + { + + } + + /** + * @return __doctrine-literal-string + */ + public function getRootAlias() + { + + } + + /** + * @return list<__doctrine-literal-string> + */ + public function getRootAliases() + { + + } + + /** + * @return list<__doctrine-literal-string> + */ + public function getAllAlias() + { + + } + + /** + * @param __doctrine-literal-string|object|array $predicates + * @return $this + */ + public function where($predicates) + { + + } + + /** + * @param __doctrine-literal-string|object|array $predicates + * @return $this + */ + public function andWhere($predicates) + { + + } + + + } diff --git a/stubs/bleedingEdge/DBAL/Connection.stub b/stubs/bleedingEdge/DBAL/Connection.stub deleted file mode 100644 index 6bb4a01d..00000000 --- a/stubs/bleedingEdge/DBAL/Connection.stub +++ /dev/null @@ -1,64 +0,0 @@ -|array $params Statement parameters - * @param array|array $types Parameter types - * - * @return int|string The number of affected rows. - * - * @throws Exception - */ - public function executeStatement($sql, array $params = [], array $types = []); - - /** - * Executes an, optionally parameterized, SQL query. - * - * If the query is parametrized, a prepared statement is used. - * If an SQLLogger is configured, the execution is logged. - * - * @param literal-string $sql SQL query - * @param list|array $params Query parameters - * @param array|array $types Parameter types - * - * @throws Exception - */ - public function executeQuery( - string $sql, - array $params = [], - $types = [], - ?QueryCacheProfile $qcp = null - ): Result; - - /** - * Executes a caching query. - * - * @param literal-string $sql SQL query - * @param list|array $params Query parameters - * @param array|array $types Parameter types - * - * @throws CacheException - * @throws Exception - */ - public function executeCacheQuery($sql, $params, $types, QueryCacheProfile $qcp): Result; - -} diff --git a/stubs/bleedingEdge/EntityRepository.stub b/stubs/bleedingEdge/EntityRepository.stub deleted file mode 100644 index 6e22e323..00000000 --- a/stubs/bleedingEdge/EntityRepository.stub +++ /dev/null @@ -1,74 +0,0 @@ - - */ -class EntityRepository implements ObjectRepository -{ - - /** @var class-string */ - protected $_entityName; - - /** - * @phpstan-param mixed $id - * @phpstan-param int|null $lockMode - * @phpstan-param int|null $lockVersion - * @phpstan-return TEntityClass|null - */ - public function find($id, $lockMode = null, $lockVersion = null); - - /** - * @phpstan-return list - */ - public function findAll(); - - /** - * @phpstan-param array $criteria - * @phpstan-param array|null $orderBy - * @phpstan-param int|null $limit - * @phpstan-param int|null $offset - * @phpstan-return list - */ - public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null); - - /** - * @phpstan-param array $criteria The criteria. - * @phpstan-param array|null $orderBy - * @phpstan-return TEntityClass|null - */ - public function findOneBy(array $criteria, array $orderBy = null); - - /** - * @phpstan-return class-string - */ - public function getClassName(); - - /** - * @phpstan-return class-string - */ - protected function getEntityName(); - - /** - * @param \Doctrine\Common\Collections\Criteria $criteria - * - * @return \Doctrine\Common\Collections\Collection - * - * @psalm-return \Doctrine\Common\Collections\Collection - */ - public function matching(Criteria $criteria); - - /** - * @param literal-string $alias - * @param literal-string|null $indexBy - * - * @return QueryBuilder - */ - public function createQueryBuilder($alias, $indexBy = null); - -} diff --git a/stubs/bleedingEdge/ORM/QueryBuilder.stub b/stubs/bleedingEdge/ORM/QueryBuilder.stub deleted file mode 100644 index 25e05ee2..00000000 --- a/stubs/bleedingEdge/ORM/QueryBuilder.stub +++ /dev/null @@ -1,156 +0,0 @@ - - */ - public function getQuery() - { - } - - /** - * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters - * @return static - */ - public function setParameters($parameters) - { - - } - - /** - * @param string $dqlPartName - * @param literal-string|object|list|array{join: array} $dqlPart - * @param bool $append - * - * @return $this - */ - public function add($dqlPartName, $dqlPart, $append = false) - { - - } - - /** - * @param literal-string|null $delete - * @param literal-string|null $alias - * - * @return $this - */ - public function delete($delete = null, $alias = null) - { - - } - - /** - * @param literal-string|null $update - * @param literal-string|null $alias - * - * @return $this - */ - public function update($update = null, $alias = null) - { - - } - - /** - * @param literal-string|class-string $from - * @param literal-string $alias - * @param literal-string|null $indexBy - * - * @return $this - */ - public function from($from, $alias, $indexBy = null) - { - - } - - /** - * @param literal-string|class-string $join - * @param literal-string $alias - * @param Expr\Join::ON|Expr\Join::WITH|null $conditionType - * @param literal-string|Expr\Comparison|Expr\Composite|Expr\Func|null $condition - * @param literal-string|null $indexBy - * - * @return $this - */ - public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) - { - - } - - /** - * @param literal-string|class-string $join - * @param literal-string $alias - * @param Expr\Join::ON|Expr\Join::WITH|null $conditionType - * @param literal-string|Expr\Comparison|Expr\Composite|Expr\Func|null $condition - * @param literal-string|null $indexBy - * - * @return $this - */ - public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) - { - - } - - /** - * @param literal-string|class-string $join - * @param literal-string $alias - * @param Expr\Join::ON|Expr\Join::WITH|null $conditionType - * @param literal-string|Expr\Comparison|Expr\Composite|Expr\Func|null $condition - * @param literal-string|null $indexBy - * - * @return $this - */ - public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null) - { - - } - - /** - * @return literal-string - */ - public function getRootAlias() - { - - } - - /** - * @return list - */ - public function getRootAliases() - { - - } - - /** - * @return list - */ - public function getAllAlias() - { - - } - - /** - * @param literal-string|object|array $predicates - * @return $this - */ - public function where($predicates) - { - - } - - /** - * @param literal-string|object|array $predicates - * @return $this - */ - public function andWhere($predicates) - { - - } - -} From f7b533de0c76826f0eca49ebefd85a60094a5571 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 5 Aug 2024 15:35:34 +0200 Subject: [PATCH 03/71] Fix --- stubs/DBAL/Connection4.stub | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stubs/DBAL/Connection4.stub b/stubs/DBAL/Connection4.stub index 0500252f..2e80b001 100644 --- a/stubs/DBAL/Connection4.stub +++ b/stubs/DBAL/Connection4.stub @@ -24,7 +24,7 @@ class Connection * * This method supports PDO binding types as well as DBAL mapping types. * - * @param __doctrine-literal-string-string $sql SQL statement + * @param __doctrine-literal-string $sql SQL statement * @param list|array $params Statement parameters * @param WrapperParameterTypeArray $types Parameter types * @@ -40,7 +40,7 @@ class Connection * If the query is parametrized, a prepared statement is used. * If an SQLLogger is configured, the execution is logged. * - * @param __doctrine-literal-string-string $sql SQL query + * @param __doctrine-literal-string $sql SQL query * @param list|array $params Query parameters * @param WrapperParameterTypeArray $types Parameter types * @@ -56,7 +56,7 @@ class Connection /** * Executes a caching query. * - * @param __doctrine-literal-string-string $sql SQL query + * @param __doctrine-literal-string $sql SQL query * @param list|array $params Query parameters * @param WrapperParameterTypeArray $types Parameter types * From 0c7771b7d91bf4c62eb8d4710a763d749ccd018f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 5 Aug 2024 15:46:13 +0200 Subject: [PATCH 04/71] Update README --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 4f148e8f..83bc5364 100644 --- a/README.md +++ b/README.md @@ -286,3 +286,19 @@ class Floor extends FunctionNode implements TypedExpression } ``` + +## Literal strings + +Stub files in phpstan-doctrine come with many parameters marked with `literal-string`. This is a security-focused type that only allows literal strings written in code to be passed into these parameters. + +This reduces risk of SQL injection because dynamic strings from user input are not accepted in place of `literal-string`. + +An example where this type is used is `$sql` parameter in `Doctrine\Dbal\Connection::executeQuery()`. + +To enable this advanced type in phpstan-doctrine, use this configuration parameter: + +```neon +parameters: + doctrine: + literalString: true +``` From caa046bd6152818e781260fb3a7a96d6b0fadeed Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 5 Aug 2024 15:40:25 +0200 Subject: [PATCH 05/71] Clean up parameters config --- extension.neon | 10 +++++++++- rules.neon | 1 + .../DoctrineLiteralStringTypeNodeResolverExtension.php | 8 ++++---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/extension.neon b/extension.neon index 84bc0d04..fc7ad592 100644 --- a/extension.neon +++ b/extension.neon @@ -1,5 +1,8 @@ parameters: doctrine: + reportDynamicQueryBuilders: false + reportUnknownTypes: false + allowNullablePropertyForRequiredField: false repositoryClass: null ormRepositoryClass: null odmRepositoryClass: null @@ -8,6 +11,7 @@ parameters: objectManagerLoader: null searchOtherMethodsForQueryBuilderBeginning: true queryBuilderFastAlgorithm: false + literalString: false featureToggles: skipCheckGenericClasses: - Doctrine\ODM\MongoDB\Mapping\ClassMetadata @@ -75,6 +79,10 @@ parametersSchema: objectManagerLoader: schema(string(), nullable()) searchOtherMethodsForQueryBuilderBeginning: bool() queryBuilderFastAlgorithm: bool() + reportDynamicQueryBuilders: bool() + reportUnknownTypes: bool() + allowNullablePropertyForRequiredField: bool() + literalString: bool() ]) conditionalTags: @@ -435,7 +443,7 @@ services: - class: PHPStan\PhpDoc\Doctrine\DoctrineLiteralStringTypeNodeResolverExtension arguments: - bleedingEdge: %featureToggles.bleedingEdge% + enabled: %doctrine.literalString% tags: - phpstan.phpDoc.typeNodeResolverExtension diff --git a/rules.neon b/rules.neon index feb41184..de338d2a 100644 --- a/rules.neon +++ b/rules.neon @@ -17,6 +17,7 @@ parametersSchema: reportDynamicQueryBuilders: bool() reportUnknownTypes: bool() allowNullablePropertyForRequiredField: bool() + literalString: bool() ]) rules: diff --git a/src/PhpDoc/Doctrine/DoctrineLiteralStringTypeNodeResolverExtension.php b/src/PhpDoc/Doctrine/DoctrineLiteralStringTypeNodeResolverExtension.php index ce73011e..3aa371d5 100644 --- a/src/PhpDoc/Doctrine/DoctrineLiteralStringTypeNodeResolverExtension.php +++ b/src/PhpDoc/Doctrine/DoctrineLiteralStringTypeNodeResolverExtension.php @@ -15,11 +15,11 @@ class DoctrineLiteralStringTypeNodeResolverExtension implements TypeNodeResolver { /** @var bool */ - private $bleedingEdge; + private $enabled; - public function __construct(bool $bleedingEdge) + public function __construct(bool $enabled) { - $this->bleedingEdge = $bleedingEdge; + $this->enabled = $enabled; } public function resolve(TypeNode $typeNode, NameScope $nameScope): ?Type @@ -32,7 +32,7 @@ public function resolve(TypeNode $typeNode, NameScope $nameScope): ?Type return null; } - if ($this->bleedingEdge) { + if ($this->enabled) { return new IntersectionType([ new StringType(), new AccessoryLiteralStringType(), From 285d78d01a8542544ac559f537967dff50a5f5c4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 5 Aug 2024 17:43:51 +0200 Subject: [PATCH 06/71] Simplify StubFilesExtensionLoader --- extension.neon | 2 ++ src/Stubs/Doctrine/StubFilesExtensionLoader.php | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/extension.neon b/extension.neon index fc7ad592..a4f36b15 100644 --- a/extension.neon +++ b/extension.neon @@ -34,6 +34,7 @@ parameters: - stubs/EntityManager.stub - stubs/EntityManagerDecorator.stub - stubs/EntityManagerInterface.stub + - stubs/EntityRepository.stub - stubs/MongoClassMetadataInfo.stub - stubs/Persistence/ManagerRegistry.stub @@ -61,6 +62,7 @@ parameters: - stubs/ORM/UnexpectedResultException.stub - stubs/ORM/Query/Expr.stub - stubs/ORM/Query.stub + - stubs/ORM/QueryBuilder.stub - stubs/ORM/Query/Expr/Comparison.stub - stubs/ORM/Query/Expr/Composite.stub - stubs/ORM/Query/Expr/Func.stub diff --git a/src/Stubs/Doctrine/StubFilesExtensionLoader.php b/src/Stubs/Doctrine/StubFilesExtensionLoader.php index 97656d2d..0b3a69d8 100644 --- a/src/Stubs/Doctrine/StubFilesExtensionLoader.php +++ b/src/Stubs/Doctrine/StubFilesExtensionLoader.php @@ -37,9 +37,6 @@ public function getFiles(): array $files[] = $stubsDir . '/DBAL/Connection.stub'; } - $files[] = $stubsDir . '/ORM/QueryBuilder.stub'; - $files[] = $stubsDir . '/EntityRepository.stub'; - $hasLazyServiceEntityRepositoryAsParent = false; try { From 6632f382180746fa4b317b4dae30e242f2e990f9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 19 Aug 2024 16:11:06 +0200 Subject: [PATCH 07/71] Try to reproduce internal error See https://github.com/phpstan/phpstan-doctrine/issues/602 --- tests/Rules/Doctrine/ORM/data/query-builder-dql.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/Rules/Doctrine/ORM/data/query-builder-dql.php b/tests/Rules/Doctrine/ORM/data/query-builder-dql.php index 49aa95f2..f94085a3 100644 --- a/tests/Rules/Doctrine/ORM/data/query-builder-dql.php +++ b/tests/Rules/Doctrine/ORM/data/query-builder-dql.php @@ -291,6 +291,14 @@ public function qbExprMethod(): void $queryBuilder->getQuery(); } + public function bug602(array $objectConditions): void + { + $queryBuilder = $this->entityManager->createQueryBuilder(); + $queryBuilder->select('e') + ->from(MyEntity::class, 'e') + ->andWhere($queryBuilder->expr()->orX(...$objectConditions)); + } + } class CustomExpr extends \Doctrine\ORM\Query\Expr From a9bb990e0a1ab33078aa925114845769b4abbb96 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Tue, 20 Aug 2024 17:12:42 +0200 Subject: [PATCH 08/71] EntityRepository::count returns positive int - fixes #603 --- stubs/EntityRepository.stub | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/stubs/EntityRepository.stub b/stubs/EntityRepository.stub index f6fc8d17..4dcde8bf 100644 --- a/stubs/EntityRepository.stub +++ b/stubs/EntityRepository.stub @@ -71,4 +71,11 @@ class EntityRepository implements ObjectRepository */ public function createQueryBuilder($alias, $indexBy = null); + /** + * @param array $criteria + * + * @return int<0, max> + */ + public function count(array $criteria); + } From 4d17bed8a33aa8220c1f2a21a6b14fcdb0e5b02c Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Fri, 23 Aug 2024 11:47:37 +0200 Subject: [PATCH 09/71] Fix support for `enumType` on array fields --- src/Rules/Doctrine/ORM/EntityColumnRule.php | 66 ++++++++++++++----- .../Doctrine/Query/QueryResultTypeWalker.php | 35 ++++++---- .../Doctrine/ORM/EntityColumnRuleTest.php | 18 ++++- .../ORM/data-attributes/enum-type.php | 21 ++++++ .../Query/QueryResultTypeWalkerTest.php | 6 +- .../EntitiesEnum/EntityWithEnum.php | 6 ++ 6 files changed, 121 insertions(+), 31 deletions(-) diff --git a/src/Rules/Doctrine/ORM/EntityColumnRule.php b/src/Rules/Doctrine/ORM/EntityColumnRule.php index e51e9c95..70c1fcea 100644 --- a/src/Rules/Doctrine/ORM/EntityColumnRule.php +++ b/src/Rules/Doctrine/ORM/EntityColumnRule.php @@ -21,6 +21,7 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypehintHelper; use PHPStan\Type\TypeTraverser; +use PHPStan\Type\TypeUtils; use PHPStan\Type\VerbosityLevel; use Throwable; use function get_class; @@ -115,25 +116,58 @@ public function processNode(Node $node, Scope $scope): array $enumTypeString = $fieldMapping['enumType'] ?? null; if ($enumTypeString !== null) { - if ($this->reflectionProvider->hasClass($enumTypeString)) { - $enumReflection = $this->reflectionProvider->getClass($enumTypeString); - $backedEnumType = $enumReflection->getBackedEnumType(); - if ($backedEnumType !== null) { - if (!$backedEnumType->equals($writableToDatabaseType) || !$backedEnumType->equals($writableToPropertyType)) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Property %s::$%s type mapping mismatch: backing type %s of enum %s does not match database type %s.', - $className, - $propertyName, - $backedEnumType->describe(VerbosityLevel::typeOnly()), - $enumReflection->getDisplayName(), - $writableToDatabaseType->describe(VerbosityLevel::typeOnly()) - ))->identifier('doctrine.enumType')->build(); + if ($writableToDatabaseType->isArray()->no() && $writableToPropertyType->isArray()->no()) { + if ($this->reflectionProvider->hasClass($enumTypeString)) { + $enumReflection = $this->reflectionProvider->getClass($enumTypeString); + $backedEnumType = $enumReflection->getBackedEnumType(); + if ($backedEnumType !== null) { + if (!$backedEnumType->equals($writableToDatabaseType) || !$backedEnumType->equals($writableToPropertyType)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s type mapping mismatch: backing type %s of enum %s does not match database type %s.', + $className, + $propertyName, + $backedEnumType->describe(VerbosityLevel::typeOnly()), + $enumReflection->getDisplayName(), + $writableToDatabaseType->describe(VerbosityLevel::typeOnly()) + ))->identifier('doctrine.enumType')->build(); + } } } + $enumType = new ObjectType($enumTypeString); + $writableToPropertyType = $enumType; + $writableToDatabaseType = $enumType; + } else { + $enumType = new ObjectType($enumTypeString); + if ($this->reflectionProvider->hasClass($enumTypeString)) { + $enumReflection = $this->reflectionProvider->getClass($enumTypeString); + $backedEnumType = $enumReflection->getBackedEnumType(); + if ($backedEnumType !== null) { + if (!$backedEnumType->equals($writableToDatabaseType->getIterableValueType()) || !$backedEnumType->equals($writableToPropertyType->getIterableValueType())) { + $errors[] = RuleErrorBuilder::message( + sprintf( + 'Property %s::$%s type mapping mismatch: backing type %s of enum %s does not match value type %s of the database type %s.', + $className, + $propertyName, + $backedEnumType->describe(VerbosityLevel::typeOnly()), + $enumReflection->getDisplayName(), + $writableToDatabaseType->getIterableValueType()->describe(VerbosityLevel::typeOnly()), + $writableToDatabaseType->describe(VerbosityLevel::typeOnly()) + ) + )->identifier('doctrine.enumType')->build(); + } + } + } + + $writableToPropertyType = TypeCombinator::intersect(new ArrayType( + $writableToPropertyType->getIterableKeyType(), + $enumType + ), ...TypeUtils::getAccessoryTypes($writableToPropertyType)); + $writableToDatabaseType = TypeCombinator::intersect(new ArrayType( + $writableToDatabaseType->getIterableKeyType(), + $enumType + ), ...TypeUtils::getAccessoryTypes($writableToDatabaseType)); + } - $enumType = new ObjectType($enumTypeString); - $writableToPropertyType = $enumType; - $writableToDatabaseType = $enumType; } $identifiers = []; diff --git a/src/Type/Doctrine/Query/QueryResultTypeWalker.php b/src/Type/Doctrine/Query/QueryResultTypeWalker.php index 2ce3e1ce..4ef105d9 100644 --- a/src/Type/Doctrine/Query/QueryResultTypeWalker.php +++ b/src/Type/Doctrine/Query/QueryResultTypeWalker.php @@ -19,6 +19,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; @@ -39,6 +40,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeTraverser; +use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; use function array_key_exists; use function array_map; @@ -2009,17 +2011,28 @@ private function getTypeOfField(ClassMetadata $class, string $fieldName): array /** @param ?class-string $enumType */ private function resolveDoctrineType(string $typeName, ?string $enumType = null, bool $nullable = false): Type { - if ($enumType !== null) { - $type = new ObjectType($enumType); - } else { - try { - $type = $this->descriptorRegistry - ->get($typeName) - ->getWritableToPropertyType(); - if ($type instanceof NeverType) { - $type = new MixedType(); + try { + $type = $this->descriptorRegistry + ->get($typeName) + ->getWritableToPropertyType(); + + if ($enumType !== null) { + if ($type->isArray()->no()) { + $type = new ObjectType($enumType); + } else { + $type = TypeCombinator::intersect(new ArrayType( + $type->getIterableKeyType(), + new ObjectType($enumType) + ), ...TypeUtils::getAccessoryTypes($type)); } - } catch (DescriptorNotRegisteredException $e) { + } + if ($type instanceof NeverType) { + $type = new MixedType(); + } + } catch (DescriptorNotRegisteredException $e) { + if ($enumType !== null) { + $type = new ObjectType($enumType); + } else { $type = new MixedType(); } } @@ -2028,7 +2041,7 @@ private function resolveDoctrineType(string $typeName, ?string $enumType = null, $type = TypeCombinator::addNull($type); } - return $type; + return $type; } /** @param ?class-string $enumType */ diff --git a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php index 5dfcc639..c7de7957 100644 --- a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php @@ -391,15 +391,27 @@ public function testEnumType(?string $objectManagerLoader): void $this->analyse([__DIR__ . '/data-attributes/enum-type.php'], [ [ 'Property PHPStan\Rules\Doctrine\ORMAttributes\Foo::$type2 type mapping mismatch: database can contain PHPStan\Rules\Doctrine\ORMAttributes\FooEnum but property expects PHPStan\Rules\Doctrine\ORMAttributes\BarEnum.', - 35, + 42, ], [ 'Property PHPStan\Rules\Doctrine\ORMAttributes\Foo::$type2 type mapping mismatch: property can contain PHPStan\Rules\Doctrine\ORMAttributes\BarEnum but database expects PHPStan\Rules\Doctrine\ORMAttributes\FooEnum.', - 35, + 42, ], [ 'Property PHPStan\Rules\Doctrine\ORMAttributes\Foo::$type3 type mapping mismatch: backing type string of enum PHPStan\Rules\Doctrine\ORMAttributes\FooEnum does not match database type int.', - 38, + 45, + ], + [ + 'Property PHPStan\Rules\Doctrine\ORMAttributes\Foo::$type5 type mapping mismatch: database can contain array but property expects PHPStan\Rules\Doctrine\ORMAttributes\FooEnum.', + 51, + ], + [ + 'Property PHPStan\Rules\Doctrine\ORMAttributes\Foo::$type5 type mapping mismatch: property can contain PHPStan\Rules\Doctrine\ORMAttributes\FooEnum but database expects array.', + 51, + ], + [ + 'Property PHPStan\Rules\Doctrine\ORMAttributes\Foo::$type7 type mapping mismatch: backing type int of enum PHPStan\Rules\Doctrine\ORMAttributes\BazEnum does not match value type string of the database type array.', + 63, ], ]); } diff --git a/tests/Rules/Doctrine/ORM/data-attributes/enum-type.php b/tests/Rules/Doctrine/ORM/data-attributes/enum-type.php index a0c3b937..50e0a2ec 100644 --- a/tests/Rules/Doctrine/ORM/data-attributes/enum-type.php +++ b/tests/Rules/Doctrine/ORM/data-attributes/enum-type.php @@ -18,6 +18,13 @@ enum BarEnum: string { } +enum BazEnum: int { + + case ONE = 1; + case TWO = 2; + +} + #[ORM\Entity] class Foo { @@ -40,4 +47,18 @@ class Foo #[ORM\Column] public FooEnum $type4; + #[ORM\Column(type: "simple_array", enumType: FooEnum::class)] + public FooEnum $type5; + + /** + * @var list + */ + #[ORM\Column(type: "simple_array", enumType: FooEnum::class)] + public array $type6; + + /** + * @var list + */ + #[ORM\Column(type: "simple_array", enumType: BazEnum::class)] + public array $type7; } diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php index 003a0737..fd6fee74 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php @@ -14,7 +14,9 @@ use PHPStan\Doctrine\Driver\DriverDetector; use PHPStan\Php\PhpVersion; use PHPStan\Testing\PHPStanTestCase; +use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -181,6 +183,7 @@ public static function setUpBeforeClass(): void $entityWithEnum->stringEnumColumn = StringEnum::A; $entityWithEnum->intEnumColumn = IntEnum::A; $entityWithEnum->intEnumOnStringColumn = IntEnum::A; + $entityWithEnum->stringEnumListColumn = [StringEnum::A, StringEnum::B]; $em->persist($entityWithEnum); } @@ -1499,9 +1502,10 @@ private function yieldConditionalDataset(): iterable $this->constantArray([ [new ConstantStringType('stringEnumColumn'), new ObjectType(StringEnum::class)], [new ConstantStringType('intEnumColumn'), new ObjectType(IntEnum::class)], + [new ConstantStringType('stringEnumListColumn'), AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new ObjectType(StringEnum::class)))], ]), ' - SELECT e.stringEnumColumn, e.intEnumColumn + SELECT e.stringEnumColumn, e.intEnumColumn, e.stringEnumListColumn FROM QueryResult\EntitiesEnum\EntityWithEnum e ', ]; diff --git a/tests/Type/Doctrine/data/QueryResult/EntitiesEnum/EntityWithEnum.php b/tests/Type/Doctrine/data/QueryResult/EntitiesEnum/EntityWithEnum.php index c22acd45..2a4c535a 100644 --- a/tests/Type/Doctrine/data/QueryResult/EntitiesEnum/EntityWithEnum.php +++ b/tests/Type/Doctrine/data/QueryResult/EntitiesEnum/EntityWithEnum.php @@ -38,4 +38,10 @@ class EntityWithEnum * @Column(type="string", enumType="QueryResult\EntitiesEnum\IntEnum") */ public $intEnumOnStringColumn; + + /** + * @var list + * @Column(type="simple_array", enumType="QueryResult\EntitiesEnum\StringEnum") + */ + public $stringEnumListColumn; } From ba9563e1c0294ff4fd0d59491ff4802af6462313 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 1 Sep 2024 13:43:40 +0200 Subject: [PATCH 10/71] Remove obsolete patches --- .github/workflows/build.yml | 3 +-- compatibility/patches/Base.patch | 13 ------------- compatibility/patches/DateAddFunction.patch | 10 ---------- compatibility/patches/DateSubFunction.patch | 10 ---------- compatibility/patches/DateTimeImmutableType.patch | 11 ----------- compatibility/patches/DateTimeType.patch | 11 ----------- 6 files changed, 1 insertion(+), 57 deletions(-) delete mode 100644 compatibility/patches/Base.patch delete mode 100644 compatibility/patches/DateAddFunction.patch delete mode 100644 compatibility/patches/DateSubFunction.patch delete mode 100644 compatibility/patches/DateTimeImmutableType.patch delete mode 100644 compatibility/patches/DateTimeType.patch diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 28b40c02..e654a7a2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -112,8 +112,7 @@ jobs: - php-version: "8.3" dependencies: "highest" update-packages: | - composer config extra.patches.doctrine/orm --json --merge '["compatibility/patches/Base.patch", "compatibility/patches/Column.patch", "compatibility/patches/DateAddFunction.patch", "compatibility/patches/DateSubFunction.patch", "compatibility/patches/DiscriminatorColumn.patch", "compatibility/patches/DiscriminatorMap.patch", "compatibility/patches/Embeddable.patch", "compatibility/patches/Embedded.patch", "compatibility/patches/Entity.patch", "compatibility/patches/GeneratedValue.patch", "compatibility/patches/Id.patch", "compatibility/patches/InheritanceType.patch", "compatibility/patches/JoinColumn.patch", "compatibility/patches/JoinColumns.patch", "compatibility/patches/ManyToMany.patch", "compatibility/patches/ManyToOne.patch", "compatibility/patches/MappedSuperclass.patch", "compatibility/patches/OneToMany.patch", "compatibility/patches/OneToOne.patch", "compatibility/patches/OrderBy.patch", "compatibility/patches/UniqueConstraint.patch", "compatibility/patches/Version.patch"]' - composer config extra.patches.carbonphp/carbon-doctrine-types --json --merge '["compatibility/patches/DateTimeImmutableType.patch", "compatibility/patches/DateTimeType.patch"]' + composer config extra.patches.doctrine/orm --json --merge '["compatibility/patches/Column.patch", "compatibility/patches/DiscriminatorColumn.patch", "compatibility/patches/DiscriminatorMap.patch", "compatibility/patches/Embeddable.patch", "compatibility/patches/Embedded.patch", "compatibility/patches/Entity.patch", "compatibility/patches/GeneratedValue.patch", "compatibility/patches/Id.patch", "compatibility/patches/InheritanceType.patch", "compatibility/patches/JoinColumn.patch", "compatibility/patches/JoinColumns.patch", "compatibility/patches/ManyToMany.patch", "compatibility/patches/ManyToOne.patch", "compatibility/patches/MappedSuperclass.patch", "compatibility/patches/OneToMany.patch", "compatibility/patches/OneToOne.patch", "compatibility/patches/OrderBy.patch", "compatibility/patches/UniqueConstraint.patch", "compatibility/patches/Version.patch"]' composer require --dev doctrine/orm:^3.0 doctrine/dbal:^4.0 carbonphp/carbon-doctrine-types:^3 gedmo/doctrine-extensions:^3 -W steps: diff --git a/compatibility/patches/Base.patch b/compatibility/patches/Base.patch deleted file mode 100644 index 9a5f2ace..00000000 --- a/compatibility/patches/Base.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- src/Query/Expr/Base.php 2024-02-09 14:21:17 -+++ src/Query/Expr/Base.php 2024-02-09 14:21:24 -@@ -33,6 +33,10 @@ - - public function __construct(mixed $args = []) - { -+ if (is_array($args) && array_key_exists(0, $args) && is_array($args[0])) { -+ $args = $args[0]; -+ } -+ - $this->addMultiple($args); - } - diff --git a/compatibility/patches/DateAddFunction.patch b/compatibility/patches/DateAddFunction.patch deleted file mode 100644 index 79a7606a..00000000 --- a/compatibility/patches/DateAddFunction.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- src/Query/AST/Functions/DateAddFunction.php 2024-02-09 14:22:59 -+++ src/Query/AST/Functions/DateAddFunction.php 2024-02-09 14:23:02 -@@ -71,7 +71,6 @@ - private function dispatchIntervalExpression(SqlWalker $sqlWalker): string - { - $sql = $this->intervalExpression->dispatch($sqlWalker); -- assert(is_numeric($sql)); - - return $sql; - } diff --git a/compatibility/patches/DateSubFunction.patch b/compatibility/patches/DateSubFunction.patch deleted file mode 100644 index 12a8fcf3..00000000 --- a/compatibility/patches/DateSubFunction.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- src/Query/AST/Functions/DateSubFunction.php 2024-02-09 14:22:31 -+++ src/Query/AST/Functions/DateSubFunction.php 2024-02-09 14:22:50 -@@ -64,7 +64,6 @@ - private function dispatchIntervalExpression(SqlWalker $sqlWalker): string - { - $sql = $this->intervalExpression->dispatch($sqlWalker); -- assert(is_numeric($sql)); - - return $sql; - } diff --git a/compatibility/patches/DateTimeImmutableType.patch b/compatibility/patches/DateTimeImmutableType.patch deleted file mode 100644 index e8525247..00000000 --- a/compatibility/patches/DateTimeImmutableType.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- src/Carbon/Doctrine/DateTimeImmutableType.php 2023-12-10 16:33:53 -+++ src/Carbon/Doctrine/DateTimeImmutableType.php 2024-02-09 11:36:50 -@@ -17,7 +17,7 @@ - /** - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ -- public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTimeImmutable -+ public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?CarbonImmutable - { - return $this->doConvertToPHPValue($value); - } diff --git a/compatibility/patches/DateTimeType.patch b/compatibility/patches/DateTimeType.patch deleted file mode 100644 index 0a36920f..00000000 --- a/compatibility/patches/DateTimeType.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- src/Carbon/Doctrine/DateTimeType.php 2023-12-10 16:33:53 -+++ src/Carbon/Doctrine/DateTimeType.php 2024-02-09 11:36:58 -@@ -17,7 +17,7 @@ - /** - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ -- public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTime -+ public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?Carbon - { - return $this->doConvertToPHPValue($value); - } From 66c248d09cde902ff83a6a2d53e7be92f5426e98 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 1 Sep 2024 14:15:29 +0200 Subject: [PATCH 11/71] Precise return type for `Result::rowCount()` based on detected driver --- extension.neon | 12 ++ ...wCountMethodDynamicReturnTypeExtension.php | 118 ++++++++++++++++++ .../MysqliResultRowCountReturnTypeTest.php | 35 ++++++ .../DBAL/PDOResultRowCountReturnTypeTest.php | 35 ++++++ .../DBAL/data/mysqli-result-row-count.php | 15 +++ .../DBAL/data/pdo-result-row-count.php | 15 +++ tests/Type/Doctrine/DBAL/mysqli.neon | 6 + tests/Type/Doctrine/DBAL/mysqli.php | 25 ++++ tests/Type/Doctrine/DBAL/pdo.neon | 6 + tests/Type/Doctrine/DBAL/pdo.php | 25 ++++ 10 files changed, 292 insertions(+) create mode 100644 src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php create mode 100644 tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php create mode 100644 tests/Type/Doctrine/DBAL/PDOResultRowCountReturnTypeTest.php create mode 100644 tests/Type/Doctrine/DBAL/data/mysqli-result-row-count.php create mode 100644 tests/Type/Doctrine/DBAL/data/pdo-result-row-count.php create mode 100644 tests/Type/Doctrine/DBAL/mysqli.neon create mode 100644 tests/Type/Doctrine/DBAL/mysqli.php create mode 100644 tests/Type/Doctrine/DBAL/pdo.neon create mode 100644 tests/Type/Doctrine/DBAL/pdo.php diff --git a/extension.neon b/extension.neon index a4f36b15..63365d37 100644 --- a/extension.neon +++ b/extension.neon @@ -313,6 +313,18 @@ services: class: PHPStan\Type\Doctrine\DBAL\QueryBuilder\QueryBuilderExecuteMethodExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: PHPStan\Type\Doctrine\DBAL\RowCountMethodDynamicReturnTypeExtension + arguments: + class: Doctrine\DBAL\Result + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: PHPStan\Type\Doctrine\DBAL\RowCountMethodDynamicReturnTypeExtension + arguments: + class: Doctrine\DBAL\Driver\Result + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension # Type descriptors - diff --git a/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php b/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php new file mode 100644 index 00000000..bb4f626b --- /dev/null +++ b/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php @@ -0,0 +1,118 @@ +class = $class; + $this->objectMetadataResolver = $objectMetadataResolver; + $this->driverDetector = $driverDetector; + $this->reflectionProvider = $reflectionProvider; + } + + public function getClass(): string + { + return $this->class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'rowCount'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + $objectManager = $this->objectMetadataResolver->getObjectManager(); + if (!$objectManager instanceof EntityManagerInterface) { + return null; + } + + $connection = $objectManager->getConnection(); + $driver = $this->driverDetector->detect($connection); + if ($driver === null) { + return null; + } + + $resultClass = $this->getResultClass($driver); + if ($resultClass === null) { + return null; + } + + if (!$this->reflectionProvider->hasClass($resultClass)) { + return null; + } + + $resultReflection = $this->reflectionProvider->getClass($resultClass); + if (!$resultReflection->hasNativeMethod('rowCount')) { + return null; + } + + $rowCountMethod = $resultReflection->getNativeMethod('rowCount'); + $variant = ParametersAcceptorSelector::selectSingle($rowCountMethod->getVariants()); + + return $variant->getReturnType(); + } + + /** + * @param DriverDetector::* $driver + * @return class-string|null + */ + private function getResultClass(string $driver): ?string + { + switch ($driver) { + case DriverDetector::IBM_DB2: + return 'Doctrine\DBAL\Driver\IBMDB2\Result'; + case DriverDetector::MYSQLI: + return 'Doctrine\DBAL\Driver\Mysqli\Result'; + case DriverDetector::OCI8: + return 'Doctrine\DBAL\Driver\OCI8\Result'; + case DriverDetector::PDO_MYSQL: + case DriverDetector::PDO_OCI: + case DriverDetector::PDO_PGSQL: + case DriverDetector::PDO_SQLITE: + case DriverDetector::PDO_SQLSRV: + return 'Doctrine\DBAL\Driver\PDO\Result'; + case DriverDetector::PGSQL: + return 'Doctrine\DBAL\Driver\PgSQL\Result'; + case DriverDetector::SQLITE3: + return 'Doctrine\DBAL\Driver\SQLite3\Result'; + case DriverDetector::SQLSRV: + return 'Doctrine\DBAL\Driver\SQLSrv\Result'; + } + + return null; + } + +} diff --git a/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php b/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php new file mode 100644 index 00000000..5a4841f5 --- /dev/null +++ b/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php @@ -0,0 +1,35 @@ + */ + public function dataFileAsserts(): iterable + { + yield from $this->gatherAssertTypes(__DIR__ . '/data/mysqli-result-row-count.php'); + } + + /** + * @dataProvider dataFileAsserts + * @param mixed ...$args + */ + public function testFileAsserts( + string $assertType, + string $file, + ...$args + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + /** @return string[] */ + public static function getAdditionalConfigFiles(): array + { + return [__DIR__ . '/mysqli.neon']; + } + +} diff --git a/tests/Type/Doctrine/DBAL/PDOResultRowCountReturnTypeTest.php b/tests/Type/Doctrine/DBAL/PDOResultRowCountReturnTypeTest.php new file mode 100644 index 00000000..0b6aa6bc --- /dev/null +++ b/tests/Type/Doctrine/DBAL/PDOResultRowCountReturnTypeTest.php @@ -0,0 +1,35 @@ + */ + public function dataFileAsserts(): iterable + { + yield from $this->gatherAssertTypes(__DIR__ . '/data/pdo-result-row-count.php'); + } + + /** + * @dataProvider dataFileAsserts + * @param mixed ...$args + */ + public function testFileAsserts( + string $assertType, + string $file, + ...$args + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + /** @return string[] */ + public static function getAdditionalConfigFiles(): array + { + return [__DIR__ . '/pdo.neon']; + } + +} diff --git a/tests/Type/Doctrine/DBAL/data/mysqli-result-row-count.php b/tests/Type/Doctrine/DBAL/data/mysqli-result-row-count.php new file mode 100644 index 00000000..84f69543 --- /dev/null +++ b/tests/Type/Doctrine/DBAL/data/mysqli-result-row-count.php @@ -0,0 +1,15 @@ +rowCount()); +}; + +function (DriverResult $r): void { + assertType('int|numeric-string', $r->rowCount()); +}; diff --git a/tests/Type/Doctrine/DBAL/data/pdo-result-row-count.php b/tests/Type/Doctrine/DBAL/data/pdo-result-row-count.php new file mode 100644 index 00000000..ec73a00c --- /dev/null +++ b/tests/Type/Doctrine/DBAL/data/pdo-result-row-count.php @@ -0,0 +1,15 @@ +rowCount()); +}; + +function (DriverResult $r): void { + assertType('int', $r->rowCount()); +}; diff --git a/tests/Type/Doctrine/DBAL/mysqli.neon b/tests/Type/Doctrine/DBAL/mysqli.neon new file mode 100644 index 00000000..e287719f --- /dev/null +++ b/tests/Type/Doctrine/DBAL/mysqli.neon @@ -0,0 +1,6 @@ +includes: + - ../../../../extension.neon + +parameters: + doctrine: + objectManagerLoader: mysqli.php diff --git a/tests/Type/Doctrine/DBAL/mysqli.php b/tests/Type/Doctrine/DBAL/mysqli.php new file mode 100644 index 00000000..2bc11294 --- /dev/null +++ b/tests/Type/Doctrine/DBAL/mysqli.php @@ -0,0 +1,25 @@ +setProxyDir(__DIR__); +$config->setProxyNamespace('App\GeneratedProxy'); +$config->setMetadataCache(new ArrayCachePool()); +$config->setMetadataDriverImpl(new AnnotationDriver( + new AnnotationReader(), + [__DIR__ . '/data'] +)); + +return new EntityManager( + DriverManager::getConnection([ + 'driver' => 'mysqli', + 'memory' => true, + ]), + $config +); diff --git a/tests/Type/Doctrine/DBAL/pdo.neon b/tests/Type/Doctrine/DBAL/pdo.neon new file mode 100644 index 00000000..ee4897c8 --- /dev/null +++ b/tests/Type/Doctrine/DBAL/pdo.neon @@ -0,0 +1,6 @@ +includes: + - ../../../../extension.neon + +parameters: + doctrine: + objectManagerLoader: pdo.php diff --git a/tests/Type/Doctrine/DBAL/pdo.php b/tests/Type/Doctrine/DBAL/pdo.php new file mode 100644 index 00000000..c7e48751 --- /dev/null +++ b/tests/Type/Doctrine/DBAL/pdo.php @@ -0,0 +1,25 @@ +setProxyDir(__DIR__); +$config->setProxyNamespace('App\GeneratedProxy'); +$config->setMetadataCache(new ArrayCachePool()); +$config->setMetadataDriverImpl(new AnnotationDriver( + new AnnotationReader(), + [__DIR__ . '/data'] +)); + +return new EntityManager( + DriverManager::getConnection([ + 'driver' => 'pdo_pgsql', + 'memory' => true, + ]), + $config +); From a9db60d6f68ef4c4184fd69ee0307e43b3a31039 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 1 Sep 2024 14:49:29 +0200 Subject: [PATCH 12/71] Fix test --- .../DBAL/MysqliResultRowCountReturnTypeTest.php | 8 +++++++- .../DBAL/data/mysqli-result-row-count-dbal-3.php | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/Type/Doctrine/DBAL/data/mysqli-result-row-count-dbal-3.php diff --git a/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php b/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php index 5a4841f5..8990bda1 100644 --- a/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php +++ b/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php @@ -2,6 +2,8 @@ namespace PHPStan\Type\Doctrine\DBAL; +use Composer\InstalledVersions; +use Composer\Semver\VersionParser; use PHPStan\Testing\TypeInferenceTestCase; class MysqliResultRowCountReturnTypeTest extends TypeInferenceTestCase @@ -10,7 +12,11 @@ class MysqliResultRowCountReturnTypeTest extends TypeInferenceTestCase /** @return iterable */ public function dataFileAsserts(): iterable { - yield from $this->gatherAssertTypes(__DIR__ . '/data/mysqli-result-row-count.php'); + if (InstalledVersions::satisfies(new VersionParser(), 'doctrine/dbal', '>=4.0')) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/mysqli-result-row-count.php'); + } else { + yield from $this->gatherAssertTypes(__DIR__ . '/data/mysqli-result-row-count-dbal-3.php'); + } } /** diff --git a/tests/Type/Doctrine/DBAL/data/mysqli-result-row-count-dbal-3.php b/tests/Type/Doctrine/DBAL/data/mysqli-result-row-count-dbal-3.php new file mode 100644 index 00000000..8226f6e4 --- /dev/null +++ b/tests/Type/Doctrine/DBAL/data/mysqli-result-row-count-dbal-3.php @@ -0,0 +1,15 @@ +rowCount()); +}; + +function (DriverResult $r): void { + assertType('int', $r->rowCount()); +}; From 6fac80bcdceb58f553724fa154275bd52c26b608 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 1 Sep 2024 14:52:38 +0200 Subject: [PATCH 13/71] Fix build --- .../DBAL/RowCountMethodDynamicReturnTypeExtension.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php b/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php index bb4f626b..8fe17348 100644 --- a/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php @@ -105,9 +105,9 @@ private function getResultClass(string $driver): ?string case DriverDetector::PDO_SQLSRV: return 'Doctrine\DBAL\Driver\PDO\Result'; case DriverDetector::PGSQL: - return 'Doctrine\DBAL\Driver\PgSQL\Result'; + return 'Doctrine\DBAL\Driver\PgSQL\Result'; // @phpstan-ignore return.type case DriverDetector::SQLITE3: - return 'Doctrine\DBAL\Driver\SQLite3\Result'; + return 'Doctrine\DBAL\Driver\SQLite3\Result'; // @phpstan-ignore return.type case DriverDetector::SQLSRV: return 'Doctrine\DBAL\Driver\SQLSrv\Result'; } From 00439d504eb4baa3f608e3857ff72c91b2188b4a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 1 Sep 2024 14:59:12 +0200 Subject: [PATCH 14/71] Fix build --- .../DBAL/MysqliResultRowCountReturnTypeTest.php | 7 +++++-- .../DBAL/data/mysqli-result-row-count-dbal-2.php | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 tests/Type/Doctrine/DBAL/data/mysqli-result-row-count-dbal-2.php diff --git a/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php b/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php index 8990bda1..cdfa8490 100644 --- a/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php +++ b/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php @@ -12,10 +12,13 @@ class MysqliResultRowCountReturnTypeTest extends TypeInferenceTestCase /** @return iterable */ public function dataFileAsserts(): iterable { - if (InstalledVersions::satisfies(new VersionParser(), 'doctrine/dbal', '>=4.0')) { + $versionParser = new VersionParser(); + if (InstalledVersions::satisfies($versionParser, 'doctrine/dbal', '>=4.0')) { yield from $this->gatherAssertTypes(__DIR__ . '/data/mysqli-result-row-count.php'); - } else { + } elseif (InstalledVersions::satisfies($versionParser, 'doctrine/dbal', '>=3.0')) { yield from $this->gatherAssertTypes(__DIR__ . '/data/mysqli-result-row-count-dbal-3.php'); + } else { + yield from $this->gatherAssertTypes(__DIR__ . '/data/mysqli-result-row-count-dbal-2.php'); } } diff --git a/tests/Type/Doctrine/DBAL/data/mysqli-result-row-count-dbal-2.php b/tests/Type/Doctrine/DBAL/data/mysqli-result-row-count-dbal-2.php new file mode 100644 index 00000000..235e6142 --- /dev/null +++ b/tests/Type/Doctrine/DBAL/data/mysqli-result-row-count-dbal-2.php @@ -0,0 +1,15 @@ +rowCount()); +}; + +function (DriverResult $r): void { + assertType('int|string', $r->rowCount()); +}; From 7414a7d2ba3746bc6def9cccaa233d8a12b7d60a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 1 Sep 2024 15:05:16 +0200 Subject: [PATCH 15/71] Fix build --- .../DBAL/PDOResultRowCountReturnTypeTest.php | 9 ++++++++- .../DBAL/data/pdo-result-row-count-dbal-2.php | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/Type/Doctrine/DBAL/data/pdo-result-row-count-dbal-2.php diff --git a/tests/Type/Doctrine/DBAL/PDOResultRowCountReturnTypeTest.php b/tests/Type/Doctrine/DBAL/PDOResultRowCountReturnTypeTest.php index 0b6aa6bc..d10ef8ab 100644 --- a/tests/Type/Doctrine/DBAL/PDOResultRowCountReturnTypeTest.php +++ b/tests/Type/Doctrine/DBAL/PDOResultRowCountReturnTypeTest.php @@ -2,6 +2,8 @@ namespace PHPStan\Type\Doctrine\DBAL; +use Composer\InstalledVersions; +use Composer\Semver\VersionParser; use PHPStan\Testing\TypeInferenceTestCase; class PDOResultRowCountReturnTypeTest extends TypeInferenceTestCase @@ -10,7 +12,12 @@ class PDOResultRowCountReturnTypeTest extends TypeInferenceTestCase /** @return iterable */ public function dataFileAsserts(): iterable { - yield from $this->gatherAssertTypes(__DIR__ . '/data/pdo-result-row-count.php'); + $versionParser = new VersionParser(); + if (InstalledVersions::satisfies($versionParser, 'doctrine/dbal', '<3')) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/pdo-result-row-count-dbal-2.php'); + } else { + yield from $this->gatherAssertTypes(__DIR__ . '/data/pdo-result-row-count.php'); + } } /** diff --git a/tests/Type/Doctrine/DBAL/data/pdo-result-row-count-dbal-2.php b/tests/Type/Doctrine/DBAL/data/pdo-result-row-count-dbal-2.php new file mode 100644 index 00000000..ff6a8cf8 --- /dev/null +++ b/tests/Type/Doctrine/DBAL/data/pdo-result-row-count-dbal-2.php @@ -0,0 +1,15 @@ +rowCount()); +}; + +function (DriverResult $r): void { + assertType('int|string', $r->rowCount()); +}; From 38db3bad8f1567d7bf64806738d724261f8a2b5c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 1 Sep 2024 15:16:09 +0200 Subject: [PATCH 16/71] Fix internal error Closes https://github.com/phpstan/phpstan-doctrine/issues/602 --- src/Type/Doctrine/ArgumentsProcessor.php | 3 +++ tests/Rules/Doctrine/ORM/data/query-builder-dql.php | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Type/Doctrine/ArgumentsProcessor.php b/src/Type/Doctrine/ArgumentsProcessor.php index f2f88b82..4ace7f16 100644 --- a/src/Type/Doctrine/ArgumentsProcessor.php +++ b/src/Type/Doctrine/ArgumentsProcessor.php @@ -34,6 +34,9 @@ public function processArgs( { $args = []; foreach ($methodCallArgs as $arg) { + if ($arg->unpack) { + throw new DynamicQueryBuilderArgumentException(); + } $value = $scope->getType($arg->value); if ( $value instanceof ExprType diff --git a/tests/Rules/Doctrine/ORM/data/query-builder-dql.php b/tests/Rules/Doctrine/ORM/data/query-builder-dql.php index f94085a3..e221f3ed 100644 --- a/tests/Rules/Doctrine/ORM/data/query-builder-dql.php +++ b/tests/Rules/Doctrine/ORM/data/query-builder-dql.php @@ -291,12 +291,17 @@ public function qbExprMethod(): void $queryBuilder->getQuery(); } - public function bug602(array $objectConditions): void + public function bug602(array $objectConditions, bool $rand): void { + $orParts = ['e.title LIKE :termLike']; + if ($rand) { + $orParts[] = 'p.version = :term'; + } $queryBuilder = $this->entityManager->createQueryBuilder(); $queryBuilder->select('e') ->from(MyEntity::class, 'e') - ->andWhere($queryBuilder->expr()->orX(...$objectConditions)); + ->andWhere($queryBuilder->expr()->orX(...$orParts)) + ->setParameter('termLike', 'someTerm'); } } From 6cc1cb7405d6958cec844d1cda780dd7cf12e490 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 14:28:54 +0200 Subject: [PATCH 17/71] Test newer PHP versions --- .github/workflows/build.yml | 4 ++++ .github/workflows/platform-test.yml | 3 +++ composer.json | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e654a7a2..95471736 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" steps: - name: "Checkout" @@ -103,6 +104,8 @@ jobs: - "8.0" - "8.1" - "8.2" + - "8.3" + - "8.4" dependencies: - "lowest" - "highest" @@ -163,6 +166,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" update-packages: - "" include: diff --git a/.github/workflows/platform-test.yml b/.github/workflows/platform-test.yml index 5c063f0e..293c85fb 100644 --- a/.github/workflows/platform-test.yml +++ b/.github/workflows/platform-test.yml @@ -26,6 +26,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" update-packages: - "" include: @@ -35,6 +36,8 @@ jobs: update-packages: doctrine/orm:^3.0 doctrine/dbal:^4.0 carbonphp/carbon-doctrine-types:^3 gedmo/doctrine-extensions:^3 - php-version: "8.3" update-packages: doctrine/orm:^3.0 doctrine/dbal:^4.0 carbonphp/carbon-doctrine-types:^3 gedmo/doctrine-extensions:^3 + - php-version: "8.4" + update-packages: doctrine/orm:^3.0 doctrine/dbal:^4.0 carbonphp/carbon-doctrine-types:^3 gedmo/doctrine-extensions:^3 steps: - name: "Checkout" diff --git a/composer.json b/composer.json index e992ccae..98c7981d 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11.7" + "phpstan/phpstan": "^1.12" }, "conflict": { "doctrine/collections": "<1.0", From d8b96f9b3125b9a245c4c1faf13e8764596467ec Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 14:29:24 +0200 Subject: [PATCH 18/71] Pin build-cs --- .github/workflows/build.yml | 1 + Makefile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 95471736..ccdeb5cf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,6 +65,7 @@ jobs: with: repository: "phpstan/build-cs" path: "build-cs" + ref: "1.x" - name: "Install PHP" uses: "shivammathur/setup-php@v2" diff --git a/Makefile b/Makefile index 2ec6452c..c62886f9 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ lint: .PHONY: cs-install cs-install: git clone https://github.com/phpstan/build-cs.git || true - git -C build-cs fetch origin && git -C build-cs reset --hard origin/main + git -C build-cs fetch origin && git -C build-cs reset --hard origin/1.x composer install --working-dir build-cs .PHONY: cs From bfb1fc958a857b305f5986c56420e10a2a1b34ea Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 14:38:59 +0200 Subject: [PATCH 19/71] Allow installing dependencies on PHP 8.4 even when not all of them support it --- .github/workflows/build.yml | 12 ++++++++++++ .github/workflows/platform-test.yml | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ccdeb5cf..065346b2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,6 +41,10 @@ jobs: - name: "Validate Composer" run: "composer validate" + - name: "Allow installing on PHP 8.4" + if: matrix.php-version == '8.4' + run: "composer config platform.php 8.3.99" + - name: "Downgrade PHPUnit" if: matrix.php-version == '7.2' || matrix.php-version == '7.3' run: "composer require --dev phpunit/phpunit:^8.5.31 --no-update --update-with-dependencies" @@ -131,6 +135,10 @@ jobs: ini-file: development extensions: "mongodb" + - name: "Allow installing on PHP 8.4" + if: matrix.php-version == '8.4' + run: "composer config platform.php 8.3.99" + - name: "Downgrade PHPUnit" if: matrix.php-version == '7.2' || matrix.php-version == '7.3' run: "composer require --dev phpunit/phpunit:^8.5.31 --no-update --update-with-dependencies" @@ -186,6 +194,10 @@ jobs: extensions: "mongodb" ini-file: development + - name: "Allow installing on PHP 8.4" + if: matrix.php-version == '8.4' + run: "composer config platform.php 8.3.99" + - name: "Downgrade PHPUnit" if: matrix.php-version == '7.2' || matrix.php-version == '7.3' run: "composer require --dev phpunit/phpunit:^8.5.31 --no-update --update-with-dependencies" diff --git a/.github/workflows/platform-test.yml b/.github/workflows/platform-test.yml index 293c85fb..fe88b6c9 100644 --- a/.github/workflows/platform-test.yml +++ b/.github/workflows/platform-test.yml @@ -51,6 +51,10 @@ jobs: ini-file: development extensions: pdo, mysqli, pgsql, pdo_mysql, pdo_pgsql, pdo_sqlite, mongodb + - name: "Allow installing on PHP 8.4" + if: matrix.php-version == '8.4' + run: "composer config platform.php 8.3.99" + - name: "Install dependencies" run: "composer install --no-interaction --no-progress" From 8badde6a55c02c186f9af98992012d1d26b817d3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 14:41:21 +0200 Subject: [PATCH 20/71] Bump PHPUnit --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 98c7981d..94c15b34 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "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", + "phpunit/phpunit": "^9.6.20", "ramsey/uuid": "^4.2", "symfony/cache": "^5.4" }, From 767698a41b3f00e05fbbdafa485d67b5ae7a85a7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 14:42:19 +0200 Subject: [PATCH 21/71] Open 2.0.x --- .github/workflows/build.yml | 2 +- .github/workflows/platform-test.yml | 2 +- .github/workflows/test-projects.yml | 2 +- composer.json | 9 ++++----- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 065346b2..bb82f08a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "1.5.x" + - "2.0.x" jobs: lint: diff --git a/.github/workflows/platform-test.yml b/.github/workflows/platform-test.yml index fe88b6c9..38110353 100644 --- a/.github/workflows/platform-test.yml +++ b/.github/workflows/platform-test.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "1.5.x" + - "2.0.x" jobs: tests: diff --git a/.github/workflows/test-projects.yml b/.github/workflows/test-projects.yml index d33dcf6f..2b85f70e 100644 --- a/.github/workflows/test-projects.yml +++ b/.github/workflows/test-projects.yml @@ -5,7 +5,7 @@ name: "Test projects" on: push: branches: - - "1.5.x" + - "2.0.x" jobs: test-projects: diff --git a/composer.json b/composer.json index 94c15b34..7c12d599 100644 --- a/composer.json +++ b/composer.json @@ -6,8 +6,8 @@ "MIT" ], "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.12" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" }, "conflict": { "doctrine/collections": "<1.0", @@ -30,10 +30,9 @@ "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", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6.20", "ramsey/uuid": "^4.2", "symfony/cache": "^5.4" From bf122092e27faf219adb723721c1dff95fda160e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 14:52:12 +0200 Subject: [PATCH 22/71] Stop testing PHP 7.2 and 7.3 --- .github/workflows/build.yml | 20 ------------------- composer.json | 10 +++++----- .../MysqliResultRowCountReturnTypeTest.php | 4 +--- .../DBAL/PDOResultRowCountReturnTypeTest.php | 9 +-------- .../data/mysqli-result-row-count-dbal-2.php | 15 -------------- .../DBAL/data/pdo-result-row-count-dbal-2.php | 15 -------------- 6 files changed, 7 insertions(+), 66 deletions(-) delete mode 100644 tests/Type/Doctrine/DBAL/data/mysqli-result-row-count-dbal-2.php delete mode 100644 tests/Type/Doctrine/DBAL/data/pdo-result-row-count-dbal-2.php diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb82f08a..ebed3920 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,8 +17,6 @@ jobs: fail-fast: false matrix: php-version: - - "7.2" - - "7.3" - "7.4" - "8.0" - "8.1" @@ -45,10 +43,6 @@ jobs: if: matrix.php-version == '8.4' run: "composer config platform.php 8.3.99" - - name: "Downgrade PHPUnit" - if: matrix.php-version == '7.2' || matrix.php-version == '7.3' - run: "composer require --dev phpunit/phpunit:^8.5.31 --no-update --update-with-dependencies" - - name: "Install dependencies" run: "composer install --no-interaction --no-progress" @@ -103,8 +97,6 @@ jobs: fail-fast: false matrix: php-version: - - "7.2" - - "7.3" - "7.4" - "8.0" - "8.1" @@ -139,18 +131,10 @@ jobs: if: matrix.php-version == '8.4' run: "composer config platform.php 8.3.99" - - name: "Downgrade PHPUnit" - if: matrix.php-version == '7.2' || matrix.php-version == '7.3' - run: "composer require --dev phpunit/phpunit:^8.5.31 --no-update --update-with-dependencies" - - name: "Install lowest dependencies" if: ${{ matrix.dependencies == 'lowest' }} run: "composer update --prefer-lowest --no-interaction --no-progress" - - name: "Update Doctrine DBAl to ^3" - if: matrix.php-version != '7.2' && matrix.dependencies == 'lowest' - run: "composer require --dev doctrine/dbal:^3.3.8 --no-interaction --no-progress" - - name: "Install highest dependencies" if: ${{ matrix.dependencies == 'highest' }} run: "composer update --no-interaction --no-progress" @@ -198,10 +182,6 @@ jobs: if: matrix.php-version == '8.4' run: "composer config platform.php 8.3.99" - - name: "Downgrade PHPUnit" - if: matrix.php-version == '7.2' || matrix.php-version == '7.3' - run: "composer require --dev phpunit/phpunit:^8.5.31 --no-update --update-with-dependencies" - - name: "Install dependencies" run: "composer update --no-interaction --no-progress" diff --git a/composer.json b/composer.json index 7c12d599..1bad25c5 100644 --- a/composer.json +++ b/composer.json @@ -20,12 +20,12 @@ "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/annotations": "^2.0", + "doctrine/collections": "^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/dbal": "^3.3.8", + "doctrine/lexer": "^3.0", + "doctrine/mongodb-odm": "^2.4.3", "doctrine/orm": "^2.16.0", "doctrine/persistence": "^2.2.1 || ^3.2", "gedmo/doctrine-extensions": "^3.8", diff --git a/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php b/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php index cdfa8490..89ac5eb6 100644 --- a/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php +++ b/tests/Type/Doctrine/DBAL/MysqliResultRowCountReturnTypeTest.php @@ -15,10 +15,8 @@ public function dataFileAsserts(): iterable $versionParser = new VersionParser(); if (InstalledVersions::satisfies($versionParser, 'doctrine/dbal', '>=4.0')) { yield from $this->gatherAssertTypes(__DIR__ . '/data/mysqli-result-row-count.php'); - } elseif (InstalledVersions::satisfies($versionParser, 'doctrine/dbal', '>=3.0')) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/mysqli-result-row-count-dbal-3.php'); } else { - yield from $this->gatherAssertTypes(__DIR__ . '/data/mysqli-result-row-count-dbal-2.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/mysqli-result-row-count-dbal-3.php'); } } diff --git a/tests/Type/Doctrine/DBAL/PDOResultRowCountReturnTypeTest.php b/tests/Type/Doctrine/DBAL/PDOResultRowCountReturnTypeTest.php index d10ef8ab..0b6aa6bc 100644 --- a/tests/Type/Doctrine/DBAL/PDOResultRowCountReturnTypeTest.php +++ b/tests/Type/Doctrine/DBAL/PDOResultRowCountReturnTypeTest.php @@ -2,8 +2,6 @@ namespace PHPStan\Type\Doctrine\DBAL; -use Composer\InstalledVersions; -use Composer\Semver\VersionParser; use PHPStan\Testing\TypeInferenceTestCase; class PDOResultRowCountReturnTypeTest extends TypeInferenceTestCase @@ -12,12 +10,7 @@ class PDOResultRowCountReturnTypeTest extends TypeInferenceTestCase /** @return iterable */ public function dataFileAsserts(): iterable { - $versionParser = new VersionParser(); - if (InstalledVersions::satisfies($versionParser, 'doctrine/dbal', '<3')) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/pdo-result-row-count-dbal-2.php'); - } else { - yield from $this->gatherAssertTypes(__DIR__ . '/data/pdo-result-row-count.php'); - } + yield from $this->gatherAssertTypes(__DIR__ . '/data/pdo-result-row-count.php'); } /** diff --git a/tests/Type/Doctrine/DBAL/data/mysqli-result-row-count-dbal-2.php b/tests/Type/Doctrine/DBAL/data/mysqli-result-row-count-dbal-2.php deleted file mode 100644 index 235e6142..00000000 --- a/tests/Type/Doctrine/DBAL/data/mysqli-result-row-count-dbal-2.php +++ /dev/null @@ -1,15 +0,0 @@ -rowCount()); -}; - -function (DriverResult $r): void { - assertType('int|string', $r->rowCount()); -}; diff --git a/tests/Type/Doctrine/DBAL/data/pdo-result-row-count-dbal-2.php b/tests/Type/Doctrine/DBAL/data/pdo-result-row-count-dbal-2.php deleted file mode 100644 index ff6a8cf8..00000000 --- a/tests/Type/Doctrine/DBAL/data/pdo-result-row-count-dbal-2.php +++ /dev/null @@ -1,15 +0,0 @@ -rowCount()); -}; - -function (DriverResult $r): void { - assertType('int|string', $r->rowCount()); -}; From df300b9b2e61c4db6e5f8b760fe697991b15d390 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 14:55:04 +0200 Subject: [PATCH 23/71] Update build-cs --- .github/workflows/build.yml | 2 +- Makefile | 2 +- ...trineProxyForbiddenClassNamesExtension.php | 3 +- src/Doctrine/DoctrineDiagnoseExtension.php | 10 +-- src/Doctrine/Mapping/ClassMetadataFactory.php | 3 +- src/Doctrine/Mapping/MappingDriverChain.php | 2 +- ...LiteralStringTypeNodeResolverExtension.php | 3 +- .../QueryTypeNodeResolverExtension.php | 5 +- ...rineSelectableClassReflectionExtension.php | 3 +- src/Reflection/Doctrine/DummyParameter.php | 18 ++-- ...tityRepositoryClassReflectionExtension.php | 3 +- .../MagicRepositoryMethodReflection.php | 11 +-- src/Rules/Doctrine/ORM/DqlRule.php | 3 +- src/Rules/Doctrine/ORM/EntityColumnRule.php | 36 ++++---- .../ORM/EntityConstructorNotFinalRule.php | 5 +- .../ORM/EntityMappingExceptionRule.php | 3 +- src/Rules/Doctrine/ORM/EntityNotFinalRule.php | 5 +- src/Rules/Doctrine/ORM/EntityRelationRule.php | 19 ++--- .../Doctrine/ORM/PropertiesExtension.php | 3 +- .../Doctrine/ORM/QueryBuilderDqlRule.php | 6 +- .../Doctrine/ORM/RepositoryMethodCallRule.php | 5 +- src/Rules/Gedmo/PropertiesExtension.php | 6 +- .../Doctrine/StubFilesExtensionLoader.php | 3 +- src/Type/Doctrine/ArgumentsProcessor.php | 3 +- .../IsEmptyTypeSpecifyingExtension.php | 9 +- .../CreateQueryDynamicReturnTypeExtension.php | 16 ++-- .../QueryBuilderExecuteMethodExtension.php | 3 +- ...wCountMethodDynamicReturnTypeExtension.php | 12 +-- .../Doctrine/DefaultDescriptorRegistry.php | 2 +- .../Doctrine/DescriptorRegistryFactory.php | 3 +- src/Type/Doctrine/Descriptors/BooleanType.php | 7 +- src/Type/Doctrine/Descriptors/DecimalType.php | 3 +- src/Type/Doctrine/Descriptors/FloatType.php | 5 +- .../Descriptors/Ramsey/UuidTypeDescriptor.php | 7 +- .../Descriptors/ReflectionDescriptor.php | 6 +- ...tityManagerInterfaceThrowTypeExtension.php | 4 +- ...etRepositoryDynamicReturnTypeExtension.php | 26 +++--- .../HydrationModeReturnTypeResolver.php | 6 +- src/Type/Doctrine/ObjectMetadataResolver.php | 13 ++- .../QueryResultDynamicReturnTypeExtension.php | 10 +-- .../Doctrine/Query/QueryResultTypeBuilder.php | 17 ++-- .../Doctrine/Query/QueryResultTypeWalker.php | 70 +++++++--------- src/Type/Doctrine/Query/QueryType.php | 9 +- ...QueryBuilderDynamicReturnTypeExtension.php | 8 +- ...seExpressionDynamicReturnTypeExtension.php | 3 +- .../Doctrine/QueryBuilder/Expr/ExprType.php | 3 +- ...ssionBuilderDynamicReturnTypeExtension.php | 6 +- .../NewExprDynamicReturnTypeExtension.php | 13 ++- .../OtherMethodQueryBuilderParser.php | 11 +-- ...uilderGetDqlDynamicReturnTypeExtension.php | 5 +- ...lderGetQueryDynamicReturnTypeExtension.php | 18 ++-- ...uilderMethodDynamicReturnTypeExtension.php | 5 +- .../QueryBuilder/QueryBuilderType.php | 2 +- .../QueryBuilderTypeSpecifyingExtension.php | 10 +-- ...BuilderExpressionTypeResolverExtension.php | 3 +- ...eProxyForbiddenClassNamesExtensionTest.php | 2 +- tests/Classes/entity-manager.php | 8 +- .../ODM/document-manager.php | 6 +- .../ORM/entity-manager.php | 6 +- tests/Platform/Entity/PlatformEntity.php | 83 ++++++------------- .../Platform/Entity/PlatformRelatedEntity.php | 3 +- ...eryResultTypeWalkerFetchTypeMatrixTest.php | 48 +++++------ ...SelectableClassReflectionExtensionTest.php | 6 +- tests/Rules/DeadCode/entity-manager.php | 6 +- .../Doctrine/ORM/EntityColumnRuleTest.php | 8 +- .../ORM/EntityConstructorNotFinalRuleTest.php | 5 +- .../ORM/EntityMappingExceptionRuleTest.php | 2 +- .../Doctrine/ORM/EntityNotFinalRuleTest.php | 5 +- .../Doctrine/ORM/EntityRelationRuleTest.php | 8 +- .../ORM/QueryBuilderDqlRuleSlowTest.php | 2 +- .../Doctrine/ORM/QueryBuilderDqlRuleTest.php | 2 +- tests/Rules/Doctrine/ORM/entity-manager.php | 8 +- tests/Rules/Properties/entity-manager.php | 6 +- tests/Type/Doctrine/DBAL/mysqli.php | 4 +- tests/Type/Doctrine/DBAL/pdo.php | 4 +- ...lectableDynamicReturnTypeExtensionTest.php | 9 +- ...QueryResultTypeWalkerHydrationModeTest.php | 10 +-- .../Query/QueryResultTypeWalkerTest.php | 60 +++++++------- 78 files changed, 311 insertions(+), 467 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ebed3920..3230e295 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,7 +63,7 @@ jobs: with: repository: "phpstan/build-cs" path: "build-cs" - ref: "1.x" + ref: "2.x" - name: "Install PHP" uses: "shivammathur/setup-php@v2" diff --git a/Makefile b/Makefile index c62886f9..efc169db 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ lint: .PHONY: cs-install cs-install: git clone https://github.com/phpstan/build-cs.git || true - git -C build-cs fetch origin && git -C build-cs reset --hard origin/1.x + git -C build-cs fetch origin && git -C build-cs reset --hard origin/2.x composer install --working-dir build-cs .PHONY: cs diff --git a/src/Classes/DoctrineProxyForbiddenClassNamesExtension.php b/src/Classes/DoctrineProxyForbiddenClassNamesExtension.php index 1ff7f4f6..dbca26af 100644 --- a/src/Classes/DoctrineProxyForbiddenClassNamesExtension.php +++ b/src/Classes/DoctrineProxyForbiddenClassNamesExtension.php @@ -8,8 +8,7 @@ class DoctrineProxyForbiddenClassNamesExtension implements ForbiddenClassNameExtension { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; public function __construct(ObjectMetadataResolver $objectMetadataResolver) { diff --git a/src/Doctrine/DoctrineDiagnoseExtension.php b/src/Doctrine/DoctrineDiagnoseExtension.php index 58fda398..0502c736 100644 --- a/src/Doctrine/DoctrineDiagnoseExtension.php +++ b/src/Doctrine/DoctrineDiagnoseExtension.php @@ -15,11 +15,9 @@ class DoctrineDiagnoseExtension implements DiagnoseExtension { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; - /** @var DriverDetector */ - private $driverDetector; + private DriverDetector $driverDetector; public function __construct( ObjectMetadataResolver $objectMetadataResolver, @@ -34,7 +32,7 @@ public function print(Output $output): void { $output->writeLineFormatted(sprintf( 'Doctrine\'s objectManagerLoader: %s', - $this->objectMetadataResolver->hasObjectManagerLoader() ? 'In use' : 'No' + $this->objectMetadataResolver->hasObjectManagerLoader() ? 'In use' : 'No', )); $objectManager = $this->objectMetadataResolver->getObjectManager(); @@ -44,7 +42,7 @@ public function print(Output $output): void $output->writeLineFormatted(sprintf( 'Detected driver: %s', - $driver === null ? 'None' : $driver + $driver === null ? 'None' : $driver, )); } diff --git a/src/Doctrine/Mapping/ClassMetadataFactory.php b/src/Doctrine/Mapping/ClassMetadataFactory.php index b2f82388..764268f1 100644 --- a/src/Doctrine/Mapping/ClassMetadataFactory.php +++ b/src/Doctrine/Mapping/ClassMetadataFactory.php @@ -18,8 +18,7 @@ class ClassMetadataFactory extends \Doctrine\ORM\Mapping\ClassMetadataFactory { - /** @var string */ - private $tmpDir; + private string $tmpDir; public function __construct(string $tmpDir) { diff --git a/src/Doctrine/Mapping/MappingDriverChain.php b/src/Doctrine/Mapping/MappingDriverChain.php index ebcdc423..532b208b 100644 --- a/src/Doctrine/Mapping/MappingDriverChain.php +++ b/src/Doctrine/Mapping/MappingDriverChain.php @@ -11,7 +11,7 @@ class MappingDriverChain implements MappingDriver { /** @var MappingDriver[] */ - private $drivers; + private array $drivers; /** * @param MappingDriver[] $drivers diff --git a/src/PhpDoc/Doctrine/DoctrineLiteralStringTypeNodeResolverExtension.php b/src/PhpDoc/Doctrine/DoctrineLiteralStringTypeNodeResolverExtension.php index 3aa371d5..b034f57a 100644 --- a/src/PhpDoc/Doctrine/DoctrineLiteralStringTypeNodeResolverExtension.php +++ b/src/PhpDoc/Doctrine/DoctrineLiteralStringTypeNodeResolverExtension.php @@ -14,8 +14,7 @@ class DoctrineLiteralStringTypeNodeResolverExtension implements TypeNodeResolverExtension { - /** @var bool */ - private $enabled; + private bool $enabled; public function __construct(bool $enabled) { diff --git a/src/PhpDoc/Doctrine/QueryTypeNodeResolverExtension.php b/src/PhpDoc/Doctrine/QueryTypeNodeResolverExtension.php index 39cd65d9..c8193892 100644 --- a/src/PhpDoc/Doctrine/QueryTypeNodeResolverExtension.php +++ b/src/PhpDoc/Doctrine/QueryTypeNodeResolverExtension.php @@ -18,8 +18,7 @@ class QueryTypeNodeResolverExtension implements TypeNodeResolverExtension, TypeNodeResolverAwareExtension { - /** @var TypeNodeResolver */ - private $typeNodeResolver; + private TypeNodeResolver $typeNodeResolver; public function setTypeNodeResolver(TypeNodeResolver $typeNodeResolver): void { @@ -47,7 +46,7 @@ public function resolve(TypeNode $typeNode, NameScope $nameScope): ?Type [ new NullType(), $this->typeNodeResolver->resolve($typeNode->genericTypes[0], $nameScope), - ] + ], ); } diff --git a/src/Reflection/Doctrine/DoctrineSelectableClassReflectionExtension.php b/src/Reflection/Doctrine/DoctrineSelectableClassReflectionExtension.php index 16970a60..ca726d8c 100644 --- a/src/Reflection/Doctrine/DoctrineSelectableClassReflectionExtension.php +++ b/src/Reflection/Doctrine/DoctrineSelectableClassReflectionExtension.php @@ -10,8 +10,7 @@ class DoctrineSelectableClassReflectionExtension implements MethodsClassReflectionExtension { - /** @var ReflectionProvider */ - private $reflectionProvider; + private ReflectionProvider $reflectionProvider; public function __construct(ReflectionProvider $reflectionProvider) { diff --git a/src/Reflection/Doctrine/DummyParameter.php b/src/Reflection/Doctrine/DummyParameter.php index 725f29f6..00eefa2c 100644 --- a/src/Reflection/Doctrine/DummyParameter.php +++ b/src/Reflection/Doctrine/DummyParameter.php @@ -9,23 +9,17 @@ class DummyParameter implements ParameterReflection { - /** @var string */ - private $name; + private string $name; - /** @var Type */ - private $type; + private Type $type; - /** @var bool */ - private $optional; + private bool $optional; - /** @var PassedByReference */ - private $passedByReference; + private PassedByReference $passedByReference; - /** @var bool */ - private $variadic; + private bool $variadic; - /** @var Type|null */ - private $defaultValue; + private ?Type $defaultValue = null; public function __construct(string $name, Type $type, bool $optional, ?PassedByReference $passedByReference, bool $variadic, ?Type $defaultValue) { diff --git a/src/Reflection/Doctrine/EntityRepositoryClassReflectionExtension.php b/src/Reflection/Doctrine/EntityRepositoryClassReflectionExtension.php index 799349da..52a8dcff 100644 --- a/src/Reflection/Doctrine/EntityRepositoryClassReflectionExtension.php +++ b/src/Reflection/Doctrine/EntityRepositoryClassReflectionExtension.php @@ -22,8 +22,7 @@ class EntityRepositoryClassReflectionExtension implements MethodsClassReflectionExtension { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; public function __construct(ObjectMetadataResolver $objectMetadataResolver) { diff --git a/src/Reflection/Doctrine/MagicRepositoryMethodReflection.php b/src/Reflection/Doctrine/MagicRepositoryMethodReflection.php index 125f1440..94dc6011 100644 --- a/src/Reflection/Doctrine/MagicRepositoryMethodReflection.php +++ b/src/Reflection/Doctrine/MagicRepositoryMethodReflection.php @@ -21,14 +21,11 @@ class MagicRepositoryMethodReflection implements MethodReflection { - /** @var ClassReflection */ - private $declaringClass; + private ClassReflection $declaringClass; - /** @var string */ - private $name; + private string $name; - /** @var Type */ - private $type; + private Type $type; public function __construct( ClassReflection $declaringClass, @@ -104,7 +101,7 @@ public function getVariants(): array null, $arguments, false, - $this->type + $this->type, ), ]; } diff --git a/src/Rules/Doctrine/ORM/DqlRule.php b/src/Rules/Doctrine/ORM/DqlRule.php index 7a121525..77cd3664 100644 --- a/src/Rules/Doctrine/ORM/DqlRule.php +++ b/src/Rules/Doctrine/ORM/DqlRule.php @@ -21,8 +21,7 @@ class DqlRule implements Rule { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; public function __construct(ObjectMetadataResolver $objectMetadataResolver) { diff --git a/src/Rules/Doctrine/ORM/EntityColumnRule.php b/src/Rules/Doctrine/ORM/EntityColumnRule.php index 70c1fcea..c2ee7a15 100644 --- a/src/Rules/Doctrine/ORM/EntityColumnRule.php +++ b/src/Rules/Doctrine/ORM/EntityColumnRule.php @@ -34,23 +34,17 @@ class EntityColumnRule implements Rule { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; - /** @var DescriptorRegistry */ - private $descriptorRegistry; + private DescriptorRegistry $descriptorRegistry; - /** @var ReflectionProvider */ - private $reflectionProvider; + private ReflectionProvider $reflectionProvider; - /** @var bool */ - private $reportUnknownTypes; + private bool $reportUnknownTypes; - /** @var bool */ - private $allowNullablePropertyForRequiredField; + private bool $allowNullablePropertyForRequiredField; - /** @var bool */ - private $bleedingEdge; + private bool $bleedingEdge; public function __construct( ObjectMetadataResolver $objectMetadataResolver, @@ -106,7 +100,7 @@ public function processNode(Node $node, Scope $scope): array 'Property %s::$%s: Doctrine type "%s" does not have any registered descriptor.', $className, $propertyName, - $fieldMapping['type'] + $fieldMapping['type'], ))->identifier('doctrine.descriptorNotFound')->build(), ] : []; } @@ -128,7 +122,7 @@ public function processNode(Node $node, Scope $scope): array $propertyName, $backedEnumType->describe(VerbosityLevel::typeOnly()), $enumReflection->getDisplayName(), - $writableToDatabaseType->describe(VerbosityLevel::typeOnly()) + $writableToDatabaseType->describe(VerbosityLevel::typeOnly()), ))->identifier('doctrine.enumType')->build(); } } @@ -151,8 +145,8 @@ public function processNode(Node $node, Scope $scope): array $backedEnumType->describe(VerbosityLevel::typeOnly()), $enumReflection->getDisplayName(), $writableToDatabaseType->getIterableValueType()->describe(VerbosityLevel::typeOnly()), - $writableToDatabaseType->describe(VerbosityLevel::typeOnly()) - ) + $writableToDatabaseType->describe(VerbosityLevel::typeOnly()), + ), )->identifier('doctrine.enumType')->build(); } } @@ -160,11 +154,11 @@ public function processNode(Node $node, Scope $scope): array $writableToPropertyType = TypeCombinator::intersect(new ArrayType( $writableToPropertyType->getIterableKeyType(), - $enumType + $enumType, ), ...TypeUtils::getAccessoryTypes($writableToPropertyType)); $writableToDatabaseType = TypeCombinator::intersect(new ArrayType( $writableToDatabaseType->getIterableKeyType(), - $enumType + $enumType, ), ...TypeUtils::getAccessoryTypes($writableToDatabaseType)); } @@ -211,7 +205,7 @@ public function processNode(Node $node, Scope $scope): array $className, $propertyName, $writableToPropertyType->describe(VerbosityLevel::getRecommendedLevelByType($propertyTransformedType, $writableToPropertyType)), - $propertyType->describe(VerbosityLevel::getRecommendedLevelByType($propertyTransformedType, $writableToPropertyType)) + $propertyType->describe(VerbosityLevel::getRecommendedLevelByType($propertyTransformedType, $writableToPropertyType)), ))->identifier('doctrine.columnType')->build(); } @@ -219,7 +213,7 @@ public function processNode(Node $node, Scope $scope): array !$writableToDatabaseType->isSuperTypeOf( $this->allowNullablePropertyForRequiredField || (in_array($propertyName, $identifiers, true) && !$nullable) ? TypeCombinator::removeNull($propertyType) - : $propertyType + : $propertyType, )->yes() ) { $errors[] = RuleErrorBuilder::message(sprintf( @@ -227,7 +221,7 @@ public function processNode(Node $node, Scope $scope): array $className, $propertyName, $propertyTransformedType->describe(VerbosityLevel::getRecommendedLevelByType($writableToDatabaseType, $propertyType)), - $writableToDatabaseType->describe(VerbosityLevel::getRecommendedLevelByType($writableToDatabaseType, $propertyType)) + $writableToDatabaseType->describe(VerbosityLevel::getRecommendedLevelByType($writableToDatabaseType, $propertyType)), ))->identifier('doctrine.columnType')->build(); } return $errors; diff --git a/src/Rules/Doctrine/ORM/EntityConstructorNotFinalRule.php b/src/Rules/Doctrine/ORM/EntityConstructorNotFinalRule.php index 5fd01213..24876b38 100644 --- a/src/Rules/Doctrine/ORM/EntityConstructorNotFinalRule.php +++ b/src/Rules/Doctrine/ORM/EntityConstructorNotFinalRule.php @@ -17,8 +17,7 @@ class EntityConstructorNotFinalRule implements Rule { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; public function __construct(ObjectMetadataResolver $objectMetadataResolver) { @@ -57,7 +56,7 @@ public function processNode(Node $node, Scope $scope): array return [ RuleErrorBuilder::message(sprintf( 'Constructor of class %s is final which can cause problems with proxies.', - $classReflection->getDisplayName() + $classReflection->getDisplayName(), ))->identifier('doctrine.finalConstructor')->build(), ]; } diff --git a/src/Rules/Doctrine/ORM/EntityMappingExceptionRule.php b/src/Rules/Doctrine/ORM/EntityMappingExceptionRule.php index d74019ba..d0ce97f7 100644 --- a/src/Rules/Doctrine/ORM/EntityMappingExceptionRule.php +++ b/src/Rules/Doctrine/ORM/EntityMappingExceptionRule.php @@ -18,8 +18,7 @@ class EntityMappingExceptionRule implements Rule { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; public function __construct( ObjectMetadataResolver $objectMetadataResolver diff --git a/src/Rules/Doctrine/ORM/EntityNotFinalRule.php b/src/Rules/Doctrine/ORM/EntityNotFinalRule.php index b4dfe4c7..9dd39ff3 100644 --- a/src/Rules/Doctrine/ORM/EntityNotFinalRule.php +++ b/src/Rules/Doctrine/ORM/EntityNotFinalRule.php @@ -17,8 +17,7 @@ class EntityNotFinalRule implements Rule { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; public function __construct(ObjectMetadataResolver $objectMetadataResolver) { @@ -52,7 +51,7 @@ public function processNode(Node $node, Scope $scope): array return [ RuleErrorBuilder::message(sprintf( 'Entity class %s is final which can cause problems with proxies.', - $classReflection->getDisplayName() + $classReflection->getDisplayName(), ))->identifier('doctrine.finalEntity')->build(), ]; } diff --git a/src/Rules/Doctrine/ORM/EntityRelationRule.php b/src/Rules/Doctrine/ORM/EntityRelationRule.php index 69e0c2a5..97e3ae2f 100644 --- a/src/Rules/Doctrine/ORM/EntityRelationRule.php +++ b/src/Rules/Doctrine/ORM/EntityRelationRule.php @@ -28,14 +28,11 @@ class EntityRelationRule implements Rule { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; - /** @var bool */ - private $allowNullablePropertyForRequiredField; + private bool $allowNullablePropertyForRequiredField; - /** @var bool */ - private $bleedingEdge; + private bool $bleedingEdge; public function __construct( ObjectMetadataResolver $objectMetadataResolver, @@ -102,7 +99,7 @@ public function processNode(Node $node, Scope $scope): array $toMany = true; $columnType = TypeCombinator::intersect( new ObjectType('Doctrine\Common\Collections\Collection'), - new IterableType(new MixedType(), new ObjectType($associationMapping['targetEntity'])) + new IterableType(new MixedType(), new ObjectType($associationMapping['targetEntity'])), ); } @@ -125,7 +122,7 @@ public function processNode(Node $node, Scope $scope): array ) { $propertyTypeToCheckAgainst = TypeCombinator::intersect( $collectionObjectType, - new IterableType(new MixedType(true), $propertyType->getIterableValueType()) + new IterableType(new MixedType(true), $propertyType->getIterableValueType()), ); } if (!$propertyTypeToCheckAgainst->isSuperTypeOf($columnType)->yes()) { @@ -134,14 +131,14 @@ public function processNode(Node $node, Scope $scope): array $className, $propertyName, $columnType->describe(VerbosityLevel::typeOnly()), - $propertyType->describe(VerbosityLevel::typeOnly()) + $propertyType->describe(VerbosityLevel::typeOnly()), ))->identifier('doctrine.associationType')->build(); } if ( !$columnType->isSuperTypeOf( $this->allowNullablePropertyForRequiredField ? TypeCombinator::removeNull($propertyType) - : $propertyType + : $propertyType, )->yes() ) { $errors[] = RuleErrorBuilder::message(sprintf( @@ -149,7 +146,7 @@ public function processNode(Node $node, Scope $scope): array $className, $propertyName, $propertyType->describe(VerbosityLevel::typeOnly()), - $columnType->describe(VerbosityLevel::typeOnly()) + $columnType->describe(VerbosityLevel::typeOnly()), ))->identifier('doctrine.associationType')->build(); } } diff --git a/src/Rules/Doctrine/ORM/PropertiesExtension.php b/src/Rules/Doctrine/ORM/PropertiesExtension.php index 4fdbf57d..9eab7dd7 100644 --- a/src/Rules/Doctrine/ORM/PropertiesExtension.php +++ b/src/Rules/Doctrine/ORM/PropertiesExtension.php @@ -12,8 +12,7 @@ class PropertiesExtension implements ReadWritePropertiesExtension { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; public function __construct(ObjectMetadataResolver $objectMetadataResolver) { diff --git a/src/Rules/Doctrine/ORM/QueryBuilderDqlRule.php b/src/Rules/Doctrine/ORM/QueryBuilderDqlRule.php index 8596810d..62155d01 100644 --- a/src/Rules/Doctrine/ORM/QueryBuilderDqlRule.php +++ b/src/Rules/Doctrine/ORM/QueryBuilderDqlRule.php @@ -26,11 +26,9 @@ class QueryBuilderDqlRule implements Rule { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; - /** @var bool */ - private $reportDynamicQueryBuilders; + private bool $reportDynamicQueryBuilders; public function __construct( ObjectMetadataResolver $objectMetadataResolver, diff --git a/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php b/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php index 9061d6c4..9f5231fe 100644 --- a/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php +++ b/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php @@ -19,8 +19,7 @@ class RepositoryMethodCallRule implements Rule { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; public function __construct(ObjectMetadataResolver $objectMetadataResolver) { @@ -82,7 +81,7 @@ public function processNode(Node $node, Scope $scope): array $calledOnType->describe(VerbosityLevel::typeOnly()), $methodName, $entityClassNames[0], - $fieldName->getValue() + $fieldName->getValue(), ))->identifier(sprintf('doctrine.%sArgument', $methodName))->build(); } } diff --git a/src/Rules/Gedmo/PropertiesExtension.php b/src/Rules/Gedmo/PropertiesExtension.php index d43b4308..e82f3bc5 100644 --- a/src/Rules/Gedmo/PropertiesExtension.php +++ b/src/Rules/Gedmo/PropertiesExtension.php @@ -41,11 +41,9 @@ class PropertiesExtension implements ReadWritePropertiesExtension Gedmo\Language::class, ]; - /** @var AnnotationReader|null */ - private $annotationReader; + private ?AnnotationReader $annotationReader = null; - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; public function __construct(ObjectMetadataResolver $objectMetadataResolver) { diff --git a/src/Stubs/Doctrine/StubFilesExtensionLoader.php b/src/Stubs/Doctrine/StubFilesExtensionLoader.php index 0b3a69d8..cb93222d 100644 --- a/src/Stubs/Doctrine/StubFilesExtensionLoader.php +++ b/src/Stubs/Doctrine/StubFilesExtensionLoader.php @@ -14,8 +14,7 @@ class StubFilesExtensionLoader implements StubFilesExtension { - /** @var Reflector */ - private $reflector; + private Reflector $reflector; public function __construct( Reflector $reflector diff --git a/src/Type/Doctrine/ArgumentsProcessor.php b/src/Type/Doctrine/ArgumentsProcessor.php index 4ace7f16..77eeb5d7 100644 --- a/src/Type/Doctrine/ArgumentsProcessor.php +++ b/src/Type/Doctrine/ArgumentsProcessor.php @@ -13,8 +13,7 @@ class ArgumentsProcessor { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; public function __construct(ObjectMetadataResolver $objectMetadataResolver) { diff --git a/src/Type/Doctrine/Collection/IsEmptyTypeSpecifyingExtension.php b/src/Type/Doctrine/Collection/IsEmptyTypeSpecifyingExtension.php index 0d440940..d94a455a 100644 --- a/src/Type/Doctrine/Collection/IsEmptyTypeSpecifyingExtension.php +++ b/src/Type/Doctrine/Collection/IsEmptyTypeSpecifyingExtension.php @@ -19,11 +19,10 @@ final class IsEmptyTypeSpecifyingExtension implements MethodTypeSpecifyingExtens private const FIRST_METHOD_NAME = 'first'; private const LAST_METHOD_NAME = 'last'; - /** @var TypeSpecifier */ - private $typeSpecifier; + private TypeSpecifier $typeSpecifier; /** @var class-string */ - private $collectionClass; + private string $collectionClass; /** * @param class-string $collectionClass @@ -61,13 +60,13 @@ public function specifyTypes( $first = $this->typeSpecifier->create( new MethodCall($node->var, self::FIRST_METHOD_NAME), new ConstantBooleanType(false), - $context + $context, ); $last = $this->typeSpecifier->create( new MethodCall($node->var, self::LAST_METHOD_NAME), new ConstantBooleanType(false), - $context + $context, ); return $first->unionWith($last); diff --git a/src/Type/Doctrine/CreateQueryDynamicReturnTypeExtension.php b/src/Type/Doctrine/CreateQueryDynamicReturnTypeExtension.php index 1cf5d50a..b78f8467 100644 --- a/src/Type/Doctrine/CreateQueryDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/CreateQueryDynamicReturnTypeExtension.php @@ -33,17 +33,13 @@ final class CreateQueryDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; - /** @var DescriptorRegistry */ - private $descriptorRegistry; + private DescriptorRegistry $descriptorRegistry; - /** @var PhpVersion */ - private $phpVersion; + private PhpVersion $phpVersion; - /** @var DriverDetector */ - private $driverDetector; + private DriverDetector $driverDetector; public function __construct( ObjectMetadataResolver $objectMetadataResolver, @@ -80,7 +76,7 @@ public function getTypeFromMethodCall( if (!isset($args[$queryStringArgIndex])) { return new GenericObjectType( Query::class, - [new MixedType(), new MixedType()] + [new MixedType(), new MixedType()], ); } @@ -113,7 +109,7 @@ public function getTypeFromMethodCall( } return new GenericObjectType( Query::class, - [new MixedType(), new MixedType()] + [new MixedType(), new MixedType()], ); }); } diff --git a/src/Type/Doctrine/DBAL/QueryBuilder/QueryBuilderExecuteMethodExtension.php b/src/Type/Doctrine/DBAL/QueryBuilder/QueryBuilderExecuteMethodExtension.php index 31170cfc..a997c2da 100644 --- a/src/Type/Doctrine/DBAL/QueryBuilder/QueryBuilderExecuteMethodExtension.php +++ b/src/Type/Doctrine/DBAL/QueryBuilder/QueryBuilderExecuteMethodExtension.php @@ -19,8 +19,7 @@ class QueryBuilderExecuteMethodExtension implements DynamicMethodReturnTypeExtension { - /** @var ReflectionProvider */ - private $reflectionProvider; + private ReflectionProvider $reflectionProvider; public function __construct(ReflectionProvider $reflectionProvider) { diff --git a/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php b/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php index 8fe17348..03a988e1 100644 --- a/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php @@ -17,17 +17,13 @@ class RowCountMethodDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { - /** @var string */ - private $class; + private string $class; - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; - /** @var DriverDetector */ - private $driverDetector; + private DriverDetector $driverDetector; - /** @var ReflectionProvider */ - private $reflectionProvider; + private ReflectionProvider $reflectionProvider; public function __construct( string $class, diff --git a/src/Type/Doctrine/DefaultDescriptorRegistry.php b/src/Type/Doctrine/DefaultDescriptorRegistry.php index 83647e3b..74ea184d 100644 --- a/src/Type/Doctrine/DefaultDescriptorRegistry.php +++ b/src/Type/Doctrine/DefaultDescriptorRegistry.php @@ -9,7 +9,7 @@ class DefaultDescriptorRegistry implements DescriptorRegistry { /** @var array, DoctrineTypeDescriptor> */ - private $descriptors = []; + private array $descriptors = []; /** * @param DoctrineTypeDescriptor[] $descriptors diff --git a/src/Type/Doctrine/DescriptorRegistryFactory.php b/src/Type/Doctrine/DescriptorRegistryFactory.php index 3e7dd190..2dbf70aa 100644 --- a/src/Type/Doctrine/DescriptorRegistryFactory.php +++ b/src/Type/Doctrine/DescriptorRegistryFactory.php @@ -9,8 +9,7 @@ class DescriptorRegistryFactory public const TYPE_DESCRIPTOR_TAG = 'phpstan.doctrine.typeDescriptor'; - /** @var Container */ - private $container; + private Container $container; public function __construct(Container $container) { diff --git a/src/Type/Doctrine/Descriptors/BooleanType.php b/src/Type/Doctrine/Descriptors/BooleanType.php index b9e59574..54e7f662 100644 --- a/src/Type/Doctrine/Descriptors/BooleanType.php +++ b/src/Type/Doctrine/Descriptors/BooleanType.php @@ -12,8 +12,7 @@ class BooleanType implements DoctrineTypeDescriptor, DoctrineTypeDriverAwareDescriptor { - /** @var DriverDetector */ - private $driverDetector; + private DriverDetector $driverDetector; public function __construct(DriverDetector $driverDetector) { @@ -40,7 +39,7 @@ public function getDatabaseInternalType(): Type return TypeCombinator::union( new ConstantIntegerType(0), new ConstantIntegerType(1), - new \PHPStan\Type\BooleanType() + new \PHPStan\Type\BooleanType(), ); } @@ -60,7 +59,7 @@ public function getDatabaseInternalTypeForDriver(Connection $connection): Type ], true)) { return TypeCombinator::union( new ConstantIntegerType(0), - new ConstantIntegerType(1) + new ConstantIntegerType(1), ); } diff --git a/src/Type/Doctrine/Descriptors/DecimalType.php b/src/Type/Doctrine/Descriptors/DecimalType.php index 64184c45..08773fdc 100644 --- a/src/Type/Doctrine/Descriptors/DecimalType.php +++ b/src/Type/Doctrine/Descriptors/DecimalType.php @@ -16,8 +16,7 @@ class DecimalType implements DoctrineTypeDescriptor, DoctrineTypeDriverAwareDescriptor { - /** @var DriverDetector */ - private $driverDetector; + private DriverDetector $driverDetector; public function __construct(DriverDetector $driverDetector) { diff --git a/src/Type/Doctrine/Descriptors/FloatType.php b/src/Type/Doctrine/Descriptors/FloatType.php index 2518e72d..f435293b 100644 --- a/src/Type/Doctrine/Descriptors/FloatType.php +++ b/src/Type/Doctrine/Descriptors/FloatType.php @@ -15,8 +15,7 @@ class FloatType implements DoctrineTypeDescriptor, DoctrineTypeDriverAwareDescriptor { - /** @var DriverDetector */ - private $driverDetector; + private DriverDetector $driverDetector; public function __construct(DriverDetector $driverDetector) { @@ -45,7 +44,7 @@ public function getDatabaseInternalType(): Type new IntersectionType([ new StringType(), new AccessoryNumericStringType(), - ]) + ]), ); } diff --git a/src/Type/Doctrine/Descriptors/Ramsey/UuidTypeDescriptor.php b/src/Type/Doctrine/Descriptors/Ramsey/UuidTypeDescriptor.php index 78501f2c..549b14c4 100644 --- a/src/Type/Doctrine/Descriptors/Ramsey/UuidTypeDescriptor.php +++ b/src/Type/Doctrine/Descriptors/Ramsey/UuidTypeDescriptor.php @@ -23,8 +23,7 @@ class UuidTypeDescriptor implements DoctrineTypeDescriptor FakeTestingUuidType::class, ]; - /** @var string */ - private $uuidTypeName; + private string $uuidTypeName; public function __construct( string $uuidTypeName @@ -33,7 +32,7 @@ public function __construct( if (!in_array($uuidTypeName, self::SUPPORTED_UUID_TYPES, true)) { throw new ShouldNotHappenException(sprintf( 'Unexpected UUID column type "%s" provided', - $uuidTypeName + $uuidTypeName, )); } @@ -55,7 +54,7 @@ public function getWritableToDatabaseType(): Type { return TypeCombinator::union( new StringType(), - new ObjectType(UuidInterface::class) + new ObjectType(UuidInterface::class), ); } diff --git a/src/Type/Doctrine/Descriptors/ReflectionDescriptor.php b/src/Type/Doctrine/Descriptors/ReflectionDescriptor.php index 7d7cb778..f4b5dba0 100644 --- a/src/Type/Doctrine/Descriptors/ReflectionDescriptor.php +++ b/src/Type/Doctrine/Descriptors/ReflectionDescriptor.php @@ -21,11 +21,9 @@ class ReflectionDescriptor implements DoctrineTypeDescriptor, DoctrineTypeDriver /** @var class-string */ private $type; - /** @var ReflectionProvider */ - private $reflectionProvider; + private ReflectionProvider $reflectionProvider; - /** @var Container */ - private $container; + private Container $container; /** * @param class-string $type diff --git a/src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php b/src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php index 0070af9f..be3407c3 100644 --- a/src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php +++ b/src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php @@ -37,9 +37,7 @@ public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, M if ((new ObjectType(EntityManagerInterface::class))->isSuperTypeOf($type)->yes()) { return TypeCombinator::union( - ...array_map(static function ($class): Type { - return new ObjectType($class); - }, self::SUPPORTED_METHOD[$methodReflection->getName()]) + ...array_map(static fn ($class): Type => new ObjectType($class), self::SUPPORTED_METHOD[$methodReflection->getName()]), ); } diff --git a/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php b/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php index fd90fdd5..d514a12b 100644 --- a/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php @@ -27,23 +27,17 @@ class GetRepositoryDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { - /** @var ReflectionProvider */ - private $reflectionProvider; + private ReflectionProvider $reflectionProvider; - /** @var string|null */ - private $repositoryClass; + private ?string $repositoryClass = null; - /** @var string|null */ - private $ormRepositoryClass; + private ?string $ormRepositoryClass = null; - /** @var string|null */ - private $odmRepositoryClass; + private ?string $odmRepositoryClass = null; - /** @var string */ - private $managerClass; + private string $managerClass; - /** @var ObjectMetadataResolver */ - private $metadataResolver; + private ObjectMetadataResolver $metadataResolver; public function __construct( ReflectionProvider $reflectionProvider, @@ -87,7 +81,7 @@ public function getTypeFromMethodCall( if (count($methodCall->getArgs()) === 0) { return new GenericObjectType( $defaultRepositoryClass, - [new ObjectWithoutClassType()] + [new ObjectWithoutClassType()], ); } $argType = $scope->getType($methodCall->getArgs()[0]->value); @@ -101,7 +95,7 @@ public function getTypeFromMethodCall( if (count($objectNames) === 0) { return new GenericObjectType( $defaultRepositoryClass, - [$classType] + [$classType], ); } @@ -127,13 +121,13 @@ private function getDefaultReturnType(Scope $scope, array $args, MethodReflectio $defaultType = ParametersAcceptorSelector::selectFromArgs( $scope, $args, - $methodReflection->getVariants() + $methodReflection->getVariants(), )->getReturnType(); $entity = $defaultType->getTemplateType(ObjectRepository::class, 'TEntityClass'); if (!$entity instanceof ErrorType) { return new GenericObjectType( $defaultRepositoryClass, - [$entity] + [$entity], ); } diff --git a/src/Type/Doctrine/HydrationModeReturnTypeResolver.php b/src/Type/Doctrine/HydrationModeReturnTypeResolver.php index 2f1c8e36..c0522e7f 100644 --- a/src/Type/Doctrine/HydrationModeReturnTypeResolver.php +++ b/src/Type/Doctrine/HydrationModeReturnTypeResolver.php @@ -73,18 +73,18 @@ public function getMethodReturnTypeForHydrationMode( case 'toIterable': return new IterableType( $queryKeyType->isNull()->yes() ? new IntegerType() : $queryKeyType, - $queryResultType + $queryResultType, ); default: if ($queryKeyType->isNull()->yes()) { return AccessoryArrayListType::intersectWith(new ArrayType( new IntegerType(), - $queryResultType + $queryResultType, )); } return new ArrayType( $queryKeyType, - $queryResultType + $queryResultType, ); } } diff --git a/src/Type/Doctrine/ObjectMetadataResolver.php b/src/Type/Doctrine/ObjectMetadataResolver.php index 6a8b4fd2..e1d97106 100644 --- a/src/Type/Doctrine/ObjectMetadataResolver.php +++ b/src/Type/Doctrine/ObjectMetadataResolver.php @@ -17,17 +17,14 @@ final class ObjectMetadataResolver { - /** @var string|null */ - private $objectManagerLoader; + private ?string $objectManagerLoader = null; /** @var ObjectManager|false|null */ private $objectManager; - /** @var ClassMetadataFactory|null */ - private $metadataFactory; + private ?ClassMetadataFactory $metadataFactory = null; - /** @var string */ - private $tmpDir; + private string $tmpDir; public function __construct( ?string $objectManagerLoader, @@ -150,14 +147,14 @@ private function loadObjectManager(string $objectManagerLoader): ?ObjectManager if (!is_file($objectManagerLoader)) { throw new ShouldNotHappenException(sprintf( 'Object manager could not be loaded: file "%s" does not exist', - $objectManagerLoader + $objectManagerLoader, )); } if (!is_readable($objectManagerLoader)) { throw new ShouldNotHappenException(sprintf( 'Object manager could not be loaded: file "%s" is not readable', - $objectManagerLoader + $objectManagerLoader, )); } diff --git a/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php b/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php index 091e3716..24aecb38 100644 --- a/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php @@ -27,11 +27,9 @@ final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturn 'getSingleResult' => 0, ]; - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; - /** @var HydrationModeReturnTypeResolver */ - private $hydrationModeReturnTypeResolver; + private HydrationModeReturnTypeResolver $hydrationModeReturnTypeResolver; public function __construct( ObjectMetadataResolver $objectMetadataResolver, @@ -71,7 +69,7 @@ public function getTypeFromMethodCall( $hydrationMode = $scope->getType($args[$argIndex]->value); } else { $parametersAcceptor = ParametersAcceptorSelector::selectSingle( - $methodReflection->getVariants() + $methodReflection->getVariants(), ); $parameter = $parametersAcceptor->getParameters()[$argIndex]; $hydrationMode = $parameter->getDefaultValue() ?? new NullType(); @@ -84,7 +82,7 @@ public function getTypeFromMethodCall( $hydrationMode, $queryType->getTemplateType(AbstractQuery::class, 'TKey'), $queryType->getTemplateType(AbstractQuery::class, 'TResult'), - $this->objectMetadataResolver->getObjectManager() + $this->objectMetadataResolver->getObjectManager(), ); } diff --git a/src/Type/Doctrine/Query/QueryResultTypeBuilder.php b/src/Type/Doctrine/Query/QueryResultTypeBuilder.php index 30941cb3..2f6fae11 100644 --- a/src/Type/Doctrine/Query/QueryResultTypeBuilder.php +++ b/src/Type/Doctrine/Query/QueryResultTypeBuilder.php @@ -21,15 +21,13 @@ final class QueryResultTypeBuilder { - /** @var bool */ - private $selectQuery = false; + private bool $selectQuery = false; /** * Whether the result is an array shape or a single entity or NEW object * - * @var bool */ - private $isShape = false; + private bool $isShape = false; /** * Map from selected entity aliases to entity types @@ -38,7 +36,7 @@ final class QueryResultTypeBuilder * * @var array */ - private $entities = []; + private array $entities = []; /** * Map from selected entity alias to result alias @@ -47,24 +45,23 @@ final class QueryResultTypeBuilder * * @var array */ - private $entityResultAliases = []; + private array $entityResultAliases = []; /** * Map from selected scalar result alias to scalar type * * @var array */ - private $scalars = []; + private array $scalars = []; /** * Map from selected NEW objcet result alias to NEW object type * * @var array */ - private $newObjects = []; + private array $newObjects = []; - /** @var Type */ - private $indexedBy; + private Type $indexedBy; public function __construct() { diff --git a/src/Type/Doctrine/Query/QueryResultTypeWalker.php b/src/Type/Doctrine/Query/QueryResultTypeWalker.php index 4ef105d9..9e5c475f 100644 --- a/src/Type/Doctrine/Query/QueryResultTypeWalker.php +++ b/src/Type/Doctrine/Query/QueryResultTypeWalker.php @@ -84,53 +84,45 @@ class QueryResultTypeWalker extends SqlWalker /** * Counter for generating unique scalar result. * - * @var int */ - private $scalarResultCounter = 1; + private int $scalarResultCounter = 1; /** * Counter for generating indexes. * - * @var int */ - private $newObjectCounter = 0; + private int $newObjectCounter = 0; /** @var Query */ - private $query; + private Query $query; - /** @var EntityManagerInterface */ - private $em; + private EntityManagerInterface $em; - /** @var PhpVersion */ - private $phpVersion; + private PhpVersion $phpVersion; /** @var DriverDetector::*|null */ private $driverType; /** @var array */ - private $driverOptions; + private array $driverOptions; /** * Map of all components/classes that appear in the DQL query. * * @var array $queryComponents */ - private $queryComponents; + private array $queryComponents; /** @var array */ - private $nullableQueryComponents; + private array $nullableQueryComponents; - /** @var QueryResultTypeBuilder */ - private $typeBuilder; + private QueryResultTypeBuilder $typeBuilder; - /** @var DescriptorRegistry */ - private $descriptorRegistry; + private DescriptorRegistry $descriptorRegistry; - /** @var bool */ - private $hasAggregateFunction; + private bool $hasAggregateFunction; - /** @var bool */ - private $hasGroupByClause; + private bool $hasGroupByClause; /** @@ -182,7 +174,7 @@ public function __construct($query, $parserResult, array $queryComponents) 'Expected the query hint %s to contain a %s, but got a %s', self::HINT_TYPE_MAPPING, QueryResultTypeBuilder::class, - is_object($typeBuilder) ? get_class($typeBuilder) : gettype($typeBuilder) + is_object($typeBuilder) ? get_class($typeBuilder) : gettype($typeBuilder), )); } @@ -195,7 +187,7 @@ public function __construct($query, $parserResult, array $queryComponents) 'Expected the query hint %s to contain a %s, but got a %s', self::HINT_DESCRIPTOR_REGISTRY, DescriptorRegistry::class, - is_object($descriptorRegistry) ? get_class($descriptorRegistry) : gettype($descriptorRegistry) + is_object($descriptorRegistry) ? get_class($descriptorRegistry) : gettype($descriptorRegistry), )); } @@ -208,7 +200,7 @@ public function __construct($query, $parserResult, array $queryComponents) 'Expected the query hint %s to contain a %s, but got a %s', self::HINT_PHP_VERSION, PhpVersion::class, - is_object($phpVersion) ? get_class($phpVersion) : gettype($phpVersion) + is_object($phpVersion) ? get_class($phpVersion) : gettype($phpVersion), )); } @@ -221,7 +213,7 @@ public function __construct($query, $parserResult, array $queryComponents) 'Expected the query hint %s to contain a %s, but got a %s', self::HINT_DRIVER_DETECTOR, DriverDetector::class, - is_object($driverDetector) ? get_class($driverDetector) : gettype($driverDetector) + is_object($driverDetector) ? get_class($driverDetector) : gettype($driverDetector), )); } $connection = $this->em->getConnection(); @@ -818,7 +810,7 @@ private function inferSumFunction(AST\Functions\SumFunction $function): Type if ($exprTypeNoNull->isInteger()->yes()) { return TypeCombinator::union( $this->createInteger($nullable), - $this->createNumericString($nullable) + $this->createNumericString($nullable), ); } @@ -838,7 +830,7 @@ private function createFloatOrInt(bool $nullable): Type { $union = TypeCombinator::union( new FloatType(), - new IntegerType() + new IntegerType(), ); return $nullable ? TypeCombinator::addNull($union) : $union; } @@ -859,7 +851,7 @@ private function createNumericString(bool $nullable): Type { $numericString = TypeCombinator::intersect( new StringType(), - new AccessoryNumericStringType() + new AccessoryNumericStringType(), ); return $nullable ? TypeCombinator::addNull($numericString) : $numericString; @@ -1009,7 +1001,7 @@ public function walkCoalesceExpression($coalesceExpression): string if ($this->driverType === DriverDetector::MYSQLI || $this->driverType === DriverDetector::PDO_MYSQL) { return $this->marshalType( - $this->inferCoalesceForMySql($rawTypes, $generalizedUnion) + $this->inferCoalesceForMySql($rawTypes, $generalizedUnion), ); } @@ -1091,13 +1083,13 @@ public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCase } $types[] = $this->unmarshalType( - $thenScalarExpression->dispatch($this) + $thenScalarExpression->dispatch($this), ); } if ($elseScalarExpression instanceof AST\Node) { $types[] = $this->unmarshalType( - $elseScalarExpression->dispatch($this) + $elseScalarExpression->dispatch($this), ); } @@ -1128,13 +1120,13 @@ public function walkSimpleCaseExpression($simpleCaseExpression): string } $types[] = $this->unmarshalType( - $thenScalarExpression->dispatch($this) + $thenScalarExpression->dispatch($this), ); } if ($elseScalarExpression instanceof AST\Node) { $types[] = $this->unmarshalType( - $elseScalarExpression->dispatch($this) + $elseScalarExpression->dispatch($this), ); } @@ -1225,7 +1217,7 @@ public function walkSelectExpression($selectExpression): string $dbalTypeName = DbalType::getTypeRegistry()->lookupName($expr->getReturnType()); $type = TypeCombinator::intersect( // e.g. count is typed as int, but we infer int<0, max> $type, - $this->resolveDoctrineType($dbalTypeName, null, TypeCombinator::containsNull($type)) + $this->resolveDoctrineType($dbalTypeName, null, TypeCombinator::containsNull($type)), ); if ($this->hasAggregateWithoutGroupBy() && !$expr instanceof AST\Functions\CountFunction) { @@ -1689,7 +1681,7 @@ public function walkSimpleArithmeticExpression($simpleArithmeticExpr): string } $types[] = $this->castStringLiteralForNumericExpression( - $this->unmarshalType($this->walkArithmeticPrimary($term)) + $this->unmarshalType($this->walkArithmeticPrimary($term)), ); } @@ -1716,7 +1708,7 @@ public function walkArithmeticTerm($term): string } $types[] = $this->castStringLiteralForNumericExpression( - $this->unmarshalType($this->walkArithmeticPrimary($factor)) + $this->unmarshalType($this->walkArithmeticPrimary($factor)), ); } @@ -1917,7 +1909,7 @@ public function walkArithmeticFactor($factor): string } elseif ($type instanceof IntegerRangeType && $factor->sign === false) { $type = IntegerRangeType::fromInterval( $type->getMax() === null ? null : $type->getMax() * -1, - $type->getMin() === null ? null : $type->getMin() * -1 + $type->getMin() === null ? null : $type->getMin() * -1, ); } elseif ($type instanceof ConstantFloatType && $factor->sign === false) { @@ -2022,7 +2014,7 @@ private function resolveDoctrineType(string $typeName, ?string $enumType = null, } else { $type = TypeCombinator::intersect(new ArrayType( $type->getIterableKeyType(), - new ObjectType($enumType) + new ObjectType($enumType), ), ...TypeUtils::getAccessoryTypes($type)); } } @@ -2058,9 +2050,7 @@ private function resolveDatabaseInternalType(string $typeName, ?string $enumType } if ($enumType !== null) { - $enumTypes = array_map(static function ($enumType) { - return ConstantTypeHelper::getTypeFromValue($enumType->value); - }, $enumType::cases()); + $enumTypes = array_map(static fn ($enumType) => ConstantTypeHelper::getTypeFromValue($enumType->value), $enumType::cases()); $enumType = TypeCombinator::union(...$enumTypes); $enumType = TypeCombinator::union($enumType, $enumType->toString()); $type = TypeCombinator::intersect($enumType, $type); diff --git a/src/Type/Doctrine/Query/QueryType.php b/src/Type/Doctrine/Query/QueryType.php index b0cb7968..ead7ef2b 100644 --- a/src/Type/Doctrine/Query/QueryType.php +++ b/src/Type/Doctrine/Query/QueryType.php @@ -11,14 +11,11 @@ class QueryType extends GenericObjectType { - /** @var Type */ - private $indexType; + private Type $indexType; - /** @var Type */ - private $resultType; + private Type $resultType; - /** @var string */ - private $dql; + private string $dql; public function __construct(string $dql, ?Type $indexType = null, ?Type $resultType = null, ?Type $subtractedType = null) { diff --git a/src/Type/Doctrine/QueryBuilder/CreateQueryBuilderDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/CreateQueryBuilderDynamicReturnTypeExtension.php index 263f2284..8389395a 100644 --- a/src/Type/Doctrine/QueryBuilder/CreateQueryBuilderDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/CreateQueryBuilderDynamicReturnTypeExtension.php @@ -11,11 +11,9 @@ class CreateQueryBuilderDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { - /** @var string|null */ - private $queryBuilderClass; + private ?string $queryBuilderClass = null; - /** @var bool */ - private $fasterVersion; + private bool $fasterVersion; public function __construct( ?string $queryBuilderClass, @@ -48,7 +46,7 @@ public function getTypeFromMethodCall( } return new $class( - $this->queryBuilderClass ?? 'Doctrine\ORM\QueryBuilder' + $this->queryBuilderClass ?? 'Doctrine\ORM\QueryBuilder', ); } diff --git a/src/Type/Doctrine/QueryBuilder/Expr/BaseExpressionDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/Expr/BaseExpressionDynamicReturnTypeExtension.php index d797b45f..7dd9729e 100644 --- a/src/Type/Doctrine/QueryBuilder/Expr/BaseExpressionDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/Expr/BaseExpressionDynamicReturnTypeExtension.php @@ -17,8 +17,7 @@ class BaseExpressionDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { - /** @var ArgumentsProcessor */ - private $argumentsProcessor; + private ArgumentsProcessor $argumentsProcessor; public function __construct( ArgumentsProcessor $argumentsProcessor diff --git a/src/Type/Doctrine/QueryBuilder/Expr/ExprType.php b/src/Type/Doctrine/QueryBuilder/Expr/ExprType.php index 00610d03..30a97a59 100644 --- a/src/Type/Doctrine/QueryBuilder/Expr/ExprType.php +++ b/src/Type/Doctrine/QueryBuilder/Expr/ExprType.php @@ -8,8 +8,7 @@ class ExprType extends ObjectType { - /** @var object */ - private $exprObject; + private object $exprObject; /** * @param object $exprObject diff --git a/src/Type/Doctrine/QueryBuilder/Expr/ExpressionBuilderDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/Expr/ExpressionBuilderDynamicReturnTypeExtension.php index fae7cd28..a6a6896a 100644 --- a/src/Type/Doctrine/QueryBuilder/Expr/ExpressionBuilderDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/Expr/ExpressionBuilderDynamicReturnTypeExtension.php @@ -18,11 +18,9 @@ class ExpressionBuilderDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; - /** @var ArgumentsProcessor */ - private $argumentsProcessor; + private ArgumentsProcessor $argumentsProcessor; public function __construct( ObjectMetadataResolver $objectMetadataResolver, diff --git a/src/Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php index 1690c63c..6f9a69c8 100644 --- a/src/Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php @@ -18,14 +18,11 @@ class NewExprDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { - /** @var ArgumentsProcessor */ - private $argumentsProcessor; + private ArgumentsProcessor $argumentsProcessor; - /** @var string */ - private $class; + private string $class; - /** @var ReflectionProvider */ - private $reflectionProvider; + private ReflectionProvider $reflectionProvider; public function __construct( ArgumentsProcessor $argumentsProcessor, @@ -68,8 +65,8 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, ...$this->argumentsProcessor->processArgs( $scope, $methodReflection->getName(), - $methodCall->getArgs() - ) + $methodCall->getArgs(), + ), ); } catch (DynamicQueryBuilderArgumentException $e) { return new ObjectType($this->reflectionProvider->getClassName($className)); diff --git a/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php b/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php index 4375c17a..cddd3a93 100644 --- a/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php +++ b/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php @@ -28,21 +28,18 @@ class OtherMethodQueryBuilderParser { - /** @var bool */ - private $descendIntoOtherMethods; + private bool $descendIntoOtherMethods; - /** @var Parser */ - private $parser; + private Parser $parser; - /** @var Container */ - private $container; + private Container $container; /** * Null if the method is currently being processed * * @var array|null> */ - private $cache = []; + private array $cache = []; public function __construct(bool $descendIntoOtherMethods, Parser $parser, Container $container) { diff --git a/src/Type/Doctrine/QueryBuilder/QueryBuilderGetDqlDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/QueryBuilderGetDqlDynamicReturnTypeExtension.php index 3b7dfb19..c6f4a5a7 100644 --- a/src/Type/Doctrine/QueryBuilder/QueryBuilderGetDqlDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/QueryBuilderGetDqlDynamicReturnTypeExtension.php @@ -13,8 +13,7 @@ class QueryBuilderGetDqlDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { - /** @var string|null */ - private $queryBuilderClass; + private ?string $queryBuilderClass = null; public function __construct( ?string $queryBuilderClass @@ -41,7 +40,7 @@ public function getTypeFromMethodCall( { $type = $scope->getType(new MethodCall( new MethodCall($methodCall->var, new Identifier('getQuery')), - new Identifier('getDQL') + new Identifier('getDQL'), )); return TypeCombinator::removeNull($type); diff --git a/src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php index 366eaa60..b02018be 100644 --- a/src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php @@ -55,23 +55,17 @@ class QueryBuilderGetQueryDynamicReturnTypeExtension implements DynamicMethodRet 'orhaving', ]; - /** @var ObjectMetadataResolver */ - private $objectMetadataResolver; + private ObjectMetadataResolver $objectMetadataResolver; - /** @var ArgumentsProcessor */ - private $argumentsProcessor; + private ArgumentsProcessor $argumentsProcessor; - /** @var string|null */ - private $queryBuilderClass; + private ?string $queryBuilderClass = null; - /** @var DescriptorRegistry */ - private $descriptorRegistry; + private DescriptorRegistry $descriptorRegistry; - /** @var PhpVersion */ - private $phpVersion; + private PhpVersion $phpVersion; - /** @var DriverDetector */ - private $driverDetector; + private DriverDetector $driverDetector; public function __construct( ObjectMetadataResolver $objectMetadataResolver, diff --git a/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php index cb76ba29..4e9ea601 100644 --- a/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php @@ -23,8 +23,7 @@ class QueryBuilderMethodDynamicReturnTypeExtension implements DynamicMethodRetur private const MAX_COMBINATIONS = 16; - /** @var string|null */ - private $queryBuilderClass; + private ?string $queryBuilderClass = null; public function __construct( ?string $queryBuilderClass @@ -41,7 +40,7 @@ public function getClass(): string public function isMethodSupported(MethodReflection $methodReflection): bool { $returnType = ParametersAcceptorSelector::selectSingle( - $methodReflection->getVariants() + $methodReflection->getVariants(), )->getReturnType(); if ($returnType instanceof MixedType) { return false; diff --git a/src/Type/Doctrine/QueryBuilder/QueryBuilderType.php b/src/Type/Doctrine/QueryBuilder/QueryBuilderType.php index 729cda17..f707888c 100644 --- a/src/Type/Doctrine/QueryBuilder/QueryBuilderType.php +++ b/src/Type/Doctrine/QueryBuilder/QueryBuilderType.php @@ -14,7 +14,7 @@ abstract class QueryBuilderType extends ObjectType { /** @var array */ - private $methodCalls = []; + private array $methodCalls = []; final public function __construct( string $className, diff --git a/src/Type/Doctrine/QueryBuilder/QueryBuilderTypeSpecifyingExtension.php b/src/Type/Doctrine/QueryBuilder/QueryBuilderTypeSpecifyingExtension.php index 4b72f650..83966118 100644 --- a/src/Type/Doctrine/QueryBuilder/QueryBuilderTypeSpecifyingExtension.php +++ b/src/Type/Doctrine/QueryBuilder/QueryBuilderTypeSpecifyingExtension.php @@ -24,11 +24,9 @@ class QueryBuilderTypeSpecifyingExtension implements MethodTypeSpecifyingExtensi private const MAX_COMBINATIONS = 16; - /** @var string|null */ - private $queryBuilderClass; + private ?string $queryBuilderClass = null; - /** @var TypeSpecifier */ - private $typeSpecifier; + private TypeSpecifier $typeSpecifier; public function __construct(?string $queryBuilderClass) { @@ -62,7 +60,7 @@ public function specifyTypes(MethodReflection $methodReflection, MethodCall $nod $returnType = ParametersAcceptorSelector::selectFromArgs( $scope, $node->getArgs(), - $methodReflection->getVariants() + $methodReflection->getVariants(), )->getReturnType(); if ($returnType instanceof MixedType) { return new SpecifiedTypes([]); @@ -100,7 +98,7 @@ public function specifyTypes(MethodReflection $methodReflection, MethodCall $nod $queryBuilderNode, TypeCombinator::union(...$resultTypes), TypeSpecifierContext::createTruthy(), - true + true, ); } diff --git a/src/Type/Doctrine/QueryBuilder/ReturnQueryBuilderExpressionTypeResolverExtension.php b/src/Type/Doctrine/QueryBuilder/ReturnQueryBuilderExpressionTypeResolverExtension.php index 2f780f22..5f308ba1 100644 --- a/src/Type/Doctrine/QueryBuilder/ReturnQueryBuilderExpressionTypeResolverExtension.php +++ b/src/Type/Doctrine/QueryBuilder/ReturnQueryBuilderExpressionTypeResolverExtension.php @@ -23,8 +23,7 @@ class ReturnQueryBuilderExpressionTypeResolverExtension implements ExpressionTypeResolverExtension { - /** @var OtherMethodQueryBuilderParser */ - private $otherMethodQueryBuilderParser; + private OtherMethodQueryBuilderParser $otherMethodQueryBuilderParser; public function __construct( OtherMethodQueryBuilderParser $otherMethodQueryBuilderParser diff --git a/tests/Classes/DoctrineProxyForbiddenClassNamesExtensionTest.php b/tests/Classes/DoctrineProxyForbiddenClassNamesExtensionTest.php index f1f7dd32..7c92d905 100644 --- a/tests/Classes/DoctrineProxyForbiddenClassNamesExtensionTest.php +++ b/tests/Classes/DoctrineProxyForbiddenClassNamesExtensionTest.php @@ -33,7 +33,7 @@ public function testForbiddenClassNameExtension(): void 20, 'This is most likely unintentional. Did you mean to type \TestPhpStanEntity?', ], - ] + ], ); } diff --git a/tests/Classes/entity-manager.php b/tests/Classes/entity-manager.php index a84bdc54..9c0301d1 100644 --- a/tests/Classes/entity-manager.php +++ b/tests/Classes/entity-manager.php @@ -19,13 +19,13 @@ $metadataDriver = new MappingDriverChain(); $metadataDriver->addDriver(new AnnotationDriver( new AnnotationReader(), - [__DIR__ . '/data'] + [__DIR__ . '/data'], ), 'PHPStan\\Rules\\Doctrine\\ORM\\'); if (PHP_VERSION_ID >= 80100) { $metadataDriver->addDriver( new AttributeDriver([__DIR__ . '/data-attributes']), - 'PHPStan\\Rules\\Doctrine\\ORMAttributes\\' + 'PHPStan\\Rules\\Doctrine\\ORMAttributes\\', ); } @@ -33,7 +33,7 @@ Type::overrideType( 'date', - DateTimeImmutableType::class + DateTimeImmutableType::class, ); return new EntityManager( @@ -41,5 +41,5 @@ 'driver' => 'pdo_sqlite', 'memory' => true, ]), - $config + $config, ); diff --git a/tests/DoctrineIntegration/ODM/document-manager.php b/tests/DoctrineIntegration/ODM/document-manager.php index bfdb4141..d104e205 100644 --- a/tests/DoctrineIntegration/ODM/document-manager.php +++ b/tests/DoctrineIntegration/ODM/document-manager.php @@ -16,11 +16,11 @@ $config->setMetadataDriverImpl( new AnnotationDriver( new AnnotationReader(), - [__DIR__ . '/data'] - ) + [__DIR__ . '/data'], + ), ); return DocumentManager::create( null, - $config + $config, ); diff --git a/tests/DoctrineIntegration/ORM/entity-manager.php b/tests/DoctrineIntegration/ORM/entity-manager.php index 23f27436..fb21532a 100644 --- a/tests/DoctrineIntegration/ORM/entity-manager.php +++ b/tests/DoctrineIntegration/ORM/entity-manager.php @@ -15,8 +15,8 @@ $config->setMetadataDriverImpl( new AnnotationDriver( new AnnotationReader(), - [__DIR__ . '/data'] - ) + [__DIR__ . '/data'], + ), ); return new EntityManager( @@ -24,5 +24,5 @@ 'driver' => 'pdo_sqlite', 'memory' => true, ]), - $config + $config, ); diff --git a/tests/Platform/Entity/PlatformEntity.php b/tests/Platform/Entity/PlatformEntity.php index 6da280e9..efc1f68d 100644 --- a/tests/Platform/Entity/PlatformEntity.php +++ b/tests/Platform/Entity/PlatformEntity.php @@ -17,90 +17,58 @@ class PlatformEntity /** * @ORM\Id * @ORM\Column(type="string",nullable=false) - * @var string */ #[ORM\Id] #[ORM\Column(type: 'string', nullable: false)] - public $id; + public string $id; /** * @ORM\ManyToOne(targetEntity=PlatformRelatedEntity::class) * @ORM\JoinColumn(name="related_entity_id", referencedColumnName="id", nullable=false) - * @var PlatformRelatedEntity */ #[ORM\ManyToOne(targetEntity: PlatformRelatedEntity::class)] #[ORM\JoinColumn(name: 'related_entity_id', referencedColumnName: 'id', nullable: false)] - public $related_entity; + public PlatformRelatedEntity $related_entity; - /** - * @ORM\Column(type="string", name="col_string", nullable=false) - * @var string - */ + /** @ORM\Column(type="string", name="col_string", nullable=false) */ #[ORM\Column(type: 'string', name: 'col_string', nullable: false)] - public $col_string; + public string $col_string; - /** - * @ORM\Column(type="string", name="col_string_nullable", nullable=true) - * @var string|null - */ + /** @ORM\Column(type="string", name="col_string_nullable", nullable=true) */ #[ORM\Column(type: 'string', name: 'col_string_nullable', nullable: true)] - public $col_string_nullable; + public ?string $col_string_nullable = null; - /** - * @ORM\Column(type="boolean", name="col_bool", nullable=false) - * @var bool - */ + /** @ORM\Column(type="boolean", name="col_bool", nullable=false) */ #[ORM\Column(type: 'boolean', name: 'col_bool', nullable: false)] - public $col_bool; + public bool $col_bool; - /** - * @ORM\Column(type="boolean", name="col_bool_nullable", nullable=true) - * @var bool|null - */ + /** @ORM\Column(type="boolean", name="col_bool_nullable", nullable=true) */ #[ORM\Column(type: 'boolean', name: 'col_bool_nullable', nullable: true)] - public $col_bool_nullable; + public ?bool $col_bool_nullable = null; - /** - * @ORM\Column(type="float", name="col_float", nullable=false) - * @var float - */ + /** @ORM\Column(type="float", name="col_float", nullable=false) */ #[ORM\Column(type: 'float', name: 'col_float', nullable: false)] - public $col_float; + public float $col_float; - /** - * @ORM\Column(type="float", name="col_float_nullable", nullable=true) - * @var float|null - */ + /** @ORM\Column(type="float", name="col_float_nullable", nullable=true) */ #[ORM\Column(type: 'float', name: 'col_float_nullable', nullable: true)] - public $col_float_nullable; + public ?float $col_float_nullable = null; - /** - * @ORM\Column(type="decimal", name="col_decimal", nullable=false, scale=1, precision=2) - * @var string - */ + /** @ORM\Column(type="decimal", name="col_decimal", nullable=false, scale=1, precision=2) */ #[ORM\Column(type: 'decimal', name: 'col_decimal', nullable: false, scale: 1, precision: 2)] - public $col_decimal; + public string $col_decimal; - /** - * @ORM\Column(type="decimal", name="col_decimal_nullable", nullable=true, scale=1, precision=2) - * @var string|null - */ + /** @ORM\Column(type="decimal", name="col_decimal_nullable", nullable=true, scale=1, precision=2) */ #[ORM\Column(type: 'decimal', name: 'col_decimal_nullable', nullable: true, scale: 1, precision: 2)] - public $col_decimal_nullable; + public ?string $col_decimal_nullable = null; - /** - * @ORM\Column(type="integer", name="col_int", nullable=false) - * @var int - */ + /** @ORM\Column(type="integer", name="col_int", nullable=false) */ #[ORM\Column(type: 'integer', name: 'col_int', nullable: false)] - public $col_int; + public int $col_int; - /** - * @ORM\Column(type="integer", name="col_int_nullable", nullable=true) - * @var int|null - */ + /** @ORM\Column(type="integer", name="col_int_nullable", nullable=true) */ #[ORM\Column(type: 'integer', name: 'col_int_nullable', nullable: true)] - public $col_int_nullable; + public ?int $col_int_nullable = null; /** * @ORM\Column(type="bigint", name="col_bigint", nullable=false) @@ -123,11 +91,8 @@ class PlatformEntity #[ORM\Column(type: 'mixed', name: 'col_mixed', nullable: false)] public $col_mixed; - /** - * @ORM\Column(type="datetime", name="col_datetime", nullable=false) - * @var DateTimeInterface - */ + /** @ORM\Column(type="datetime", name="col_datetime", nullable=false) */ #[ORM\Column(type: 'datetime', name: 'col_datetime', nullable: false)] - public $col_datetime; + public DateTimeInterface $col_datetime; } diff --git a/tests/Platform/Entity/PlatformRelatedEntity.php b/tests/Platform/Entity/PlatformRelatedEntity.php index 86c4b00a..fec1a0a5 100644 --- a/tests/Platform/Entity/PlatformRelatedEntity.php +++ b/tests/Platform/Entity/PlatformRelatedEntity.php @@ -16,10 +16,9 @@ class PlatformRelatedEntity /** * @ORM\Id * @ORM\Column(type="integer", nullable=false) - * @var int */ #[ORM\Id] #[ORM\Column(type: 'integer', nullable: false)] - public $id; + public int $id; } diff --git a/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php b/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php index 04fdcba8..97df9e20 100644 --- a/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php +++ b/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php @@ -134,7 +134,7 @@ public function testPdoMysqlDefault( PHP_VERSION_ID, $mysqlExpectedType, $mysqlExpectedResult, - $stringify + $stringify, ); } @@ -174,7 +174,7 @@ public function testPdoMysqlStringify( PHP_VERSION_ID, $mysqlExpectedType, $mysqlExpectedResult, - $stringify + $stringify, ); } @@ -214,7 +214,7 @@ public function testPdoMysqlNoEmulate( PHP_VERSION_ID, $mysqlExpectedType, $mysqlExpectedResult, - $stringify + $stringify, ); } @@ -254,7 +254,7 @@ public function testPdoMysqlStringifyNoEmulate( PHP_VERSION_ID, $mysqlExpectedType, $mysqlExpectedResult, - $stringify + $stringify, ); } @@ -294,7 +294,7 @@ public function testPdoMysqliDefault( PHP_VERSION_ID, $mysqlExpectedType, $mysqlExpectedResult, - $stringify + $stringify, ); } @@ -334,7 +334,7 @@ public function testPdoSqliteDefault( PHP_VERSION_ID, $sqliteExpectedType, $sqliteExpectedResult, - $stringify + $stringify, ); } @@ -374,7 +374,7 @@ public function testPdoSqliteStringify( PHP_VERSION_ID, $sqliteExpectedType, $sqliteExpectedResult, - $stringify + $stringify, ); } @@ -414,7 +414,7 @@ public function testPdoSqlite3( PHP_VERSION_ID, $sqliteExpectedType, $sqliteExpectedResult, - $stringify + $stringify, ); } @@ -454,7 +454,7 @@ public function testPdoPgsqlDefault( PHP_VERSION_ID, $pdoPgsqlExpectedType, $pdoPgsqlExpectedResult, - $stringify + $stringify, ); } @@ -494,7 +494,7 @@ public function testPdoPgsqlStringify( PHP_VERSION_ID, $pdoPgsqlExpectedType, $pdoPgsqlExpectedResult, - $stringify + $stringify, ); } @@ -534,7 +534,7 @@ public function testPgsql( PHP_VERSION_ID, $pgsqlExpectedType, $pgsqlExpectedResult, - $stringify + $stringify, ); } @@ -574,7 +574,7 @@ public function testUnsupportedDriver( PHP_VERSION_ID, $mssqlExpectedType, $mssqlExpectedResult, - $stringify + $stringify, ); } @@ -615,7 +615,7 @@ public function testUnknownDriver( $this->determineTypeForUnknownDriverUnknownSetup($mysqlExpectedType, $stringify), $mysqlExpectedResult, $stringify, - true + true, ); } @@ -656,7 +656,7 @@ public function testUnknownDriverStringify( $this->determineTypeForUnknownDriverUnknownSetup($mysqlExpectedType, $stringify), $mysqlExpectedResult, $stringify, - true + true, ); } @@ -4469,7 +4469,7 @@ private function performDriverTest( $dql, $sql, $realResultType->describe(VerbosityLevel::precise()), - $inferredType->describe(VerbosityLevel::precise()) + $inferredType->describe(VerbosityLevel::precise()), )); } @@ -4575,7 +4575,7 @@ private function getInferredType(Query $query): Type $typeBuilder, self::getContainer()->getByType(DescriptorRegistry::class), $phpVersion, - new DriverDetector() + new DriverDetector(), ); return $typeBuilder->getResultType(); @@ -4617,8 +4617,8 @@ private function assertRealResultMatchesExpected( $dataset, $dql, $humanReadablePhpVersion, - $realFirstResult - ) + $realFirstResult, + ), ); } @@ -4634,8 +4634,8 @@ private function assertRealResultMatchesExpected( $sql, $humanReadablePhpVersion, $realFirstResult, - $expectedFirstResultExported - ) + $expectedFirstResultExported, + ), ); } @@ -4669,8 +4669,8 @@ private function assertRealResultMatchesInferred( $this->getHumanReadablePhpVersion($phpVersion), $realFirstResult, $inferredType->describe(VerbosityLevel::precise()), - $realType->describe(VerbosityLevel::precise()) - ) + $realType->describe(VerbosityLevel::precise()), + ), ); } @@ -4707,8 +4707,8 @@ private function assertInferredResultMatchesExpected( $this->getHumanReadablePhpVersion($phpVersion), $realFirstResult, $inferredFirstItemType->describe(VerbosityLevel::precise()), - $expectedFirstItemType->describe(VerbosityLevel::precise()) - ) + $expectedFirstItemType->describe(VerbosityLevel::precise()), + ), ); } diff --git a/tests/Reflection/Doctrine/DoctrineSelectableClassReflectionExtensionTest.php b/tests/Reflection/Doctrine/DoctrineSelectableClassReflectionExtensionTest.php index 1debe15e..61c90e4f 100644 --- a/tests/Reflection/Doctrine/DoctrineSelectableClassReflectionExtensionTest.php +++ b/tests/Reflection/Doctrine/DoctrineSelectableClassReflectionExtensionTest.php @@ -9,11 +9,9 @@ final class DoctrineSelectableClassReflectionExtensionTest extends PHPStanTestCase { - /** @var ReflectionProvider */ - private $reflectionProvider; + private ReflectionProvider $reflectionProvider; - /** @var DoctrineSelectableClassReflectionExtension */ - private $extension; + private DoctrineSelectableClassReflectionExtension $extension; protected function setUp(): void { diff --git a/tests/Rules/DeadCode/entity-manager.php b/tests/Rules/DeadCode/entity-manager.php index bc0d5ccb..30eeec97 100644 --- a/tests/Rules/DeadCode/entity-manager.php +++ b/tests/Rules/DeadCode/entity-manager.php @@ -17,12 +17,12 @@ $metadataDriver = new MappingDriverChain(); $metadataDriver->addDriver(new AnnotationDriver( new AnnotationReader(), - [__DIR__ . '/data'] + [__DIR__ . '/data'], ), 'PHPStan\\Rules\\Doctrine\\ORM\\'); if (PHP_VERSION_ID >= 80100) { $metadataDriver->addDriver( new AttributeDriver([__DIR__ . '/data']), - 'PHPStan\\Rules\\Doctrine\\ORMAttributes\\' + 'PHPStan\\Rules\\Doctrine\\ORMAttributes\\', ); } @@ -33,5 +33,5 @@ 'driver' => 'pdo_sqlite', 'memory' => true, ]), - $config + $config, ); diff --git a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php index c7de7957..091ef9c3 100644 --- a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php @@ -35,11 +35,9 @@ class EntityColumnRuleTest extends RuleTestCase { - /** @var bool */ - private $allowNullablePropertyForRequiredField; + private bool $allowNullablePropertyForRequiredField; - /** @var string|null */ - private $objectManagerLoader; + private ?string $objectManagerLoader = null; protected function getRule(): Rule { @@ -85,7 +83,7 @@ protected function getRule(): Rule $this->createReflectionProvider(), true, $this->allowNullablePropertyForRequiredField, - true + true, ); } diff --git a/tests/Rules/Doctrine/ORM/EntityConstructorNotFinalRuleTest.php b/tests/Rules/Doctrine/ORM/EntityConstructorNotFinalRuleTest.php index 7647704b..a94fdfde 100644 --- a/tests/Rules/Doctrine/ORM/EntityConstructorNotFinalRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityConstructorNotFinalRuleTest.php @@ -13,13 +13,12 @@ class EntityConstructorNotFinalRuleTest extends RuleTestCase { - /** @var string|null */ - private $objectManagerLoader; + private ?string $objectManagerLoader = null; protected function getRule(): Rule { return new EntityConstructorNotFinalRule( - new ObjectMetadataResolver($this->objectManagerLoader, __DIR__ . '/../../../../tmp') + new ObjectMetadataResolver($this->objectManagerLoader, __DIR__ . '/../../../../tmp'), ); } diff --git a/tests/Rules/Doctrine/ORM/EntityMappingExceptionRuleTest.php b/tests/Rules/Doctrine/ORM/EntityMappingExceptionRuleTest.php index 26a4e744..65b8f209 100644 --- a/tests/Rules/Doctrine/ORM/EntityMappingExceptionRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityMappingExceptionRuleTest.php @@ -16,7 +16,7 @@ class EntityMappingExceptionRuleTest extends RuleTestCase protected function getRule(): Rule { return new EntityMappingExceptionRule( - new ObjectMetadataResolver(__DIR__ . '/entity-manager.php', __DIR__ . '/../../../../tmp') + new ObjectMetadataResolver(__DIR__ . '/entity-manager.php', __DIR__ . '/../../../../tmp'), ); } diff --git a/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php b/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php index 9f1494a7..d3f05395 100644 --- a/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php @@ -13,13 +13,12 @@ class EntityNotFinalRuleTest extends RuleTestCase { - /** @var string|null */ - private $objectManagerLoader; + private ?string $objectManagerLoader = null; protected function getRule(): Rule { return new EntityNotFinalRule( - new ObjectMetadataResolver($this->objectManagerLoader, __DIR__ . '/../../../../tmp') + new ObjectMetadataResolver($this->objectManagerLoader, __DIR__ . '/../../../../tmp'), ); } diff --git a/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php b/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php index cd83bebe..5222b65f 100644 --- a/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php @@ -14,18 +14,16 @@ class EntityRelationRuleTest extends RuleTestCase { - /** @var bool */ - private $allowNullablePropertyForRequiredField; + private bool $allowNullablePropertyForRequiredField; - /** @var string|null */ - private $objectManagerLoader; + private ?string $objectManagerLoader = null; protected function getRule(): Rule { return new EntityRelationRule( new ObjectMetadataResolver($this->objectManagerLoader, __DIR__ . '/../../../../tmp'), $this->allowNullablePropertyForRequiredField, - true + true, ); } diff --git a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php index adad87ee..49cec42d 100644 --- a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php +++ b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php @@ -17,7 +17,7 @@ protected function getRule(): Rule { return new QueryBuilderDqlRule( new ObjectMetadataResolver(__DIR__ . '/entity-manager.php', __DIR__ . '/../../../../tmp'), - true + true, ); } diff --git a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php index ec3dafb7..bb7f6b87 100644 --- a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php +++ b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php @@ -17,7 +17,7 @@ protected function getRule(): Rule { return new QueryBuilderDqlRule( new ObjectMetadataResolver(__DIR__ . '/entity-manager.php', __DIR__ . '/../../../../tmp'), - true + true, ); } diff --git a/tests/Rules/Doctrine/ORM/entity-manager.php b/tests/Rules/Doctrine/ORM/entity-manager.php index 9181f8b8..500a2029 100644 --- a/tests/Rules/Doctrine/ORM/entity-manager.php +++ b/tests/Rules/Doctrine/ORM/entity-manager.php @@ -19,13 +19,13 @@ $metadataDriver = new MappingDriverChain(); $metadataDriver->addDriver(new AnnotationDriver( new AnnotationReader(), - [__DIR__ . '/data'] + [__DIR__ . '/data'], ), 'PHPStan\\Rules\\Doctrine\\ORM\\'); if (PHP_VERSION_ID >= 80100) { $metadataDriver->addDriver( new AttributeDriver([__DIR__ . '/data-attributes']), - 'PHPStan\\Rules\\Doctrine\\ORMAttributes\\' + 'PHPStan\\Rules\\Doctrine\\ORMAttributes\\', ); } @@ -33,7 +33,7 @@ Type::overrideType( 'date', - DateTimeImmutableType::class + DateTimeImmutableType::class, ); return new EntityManager( @@ -41,5 +41,5 @@ 'driver' => 'pdo_sqlite', 'memory' => true, ]), - $config + $config, ); diff --git a/tests/Rules/Properties/entity-manager.php b/tests/Rules/Properties/entity-manager.php index 99f7a07e..f36730f8 100644 --- a/tests/Rules/Properties/entity-manager.php +++ b/tests/Rules/Properties/entity-manager.php @@ -17,13 +17,13 @@ $metadataDriver = new MappingDriverChain(); $metadataDriver->addDriver(new AnnotationDriver( new AnnotationReader(), - [__DIR__ . '/data'] + [__DIR__ . '/data'], ), 'PHPStan\\Rules\\Doctrine\\ORM\\'); if (PHP_VERSION_ID >= 80100) { $metadataDriver->addDriver( new AttributeDriver([__DIR__ . '/data']), - 'PHPStan\\Rules\\Doctrine\\ORMAttributes\\' + 'PHPStan\\Rules\\Doctrine\\ORMAttributes\\', ); } @@ -34,5 +34,5 @@ 'driver' => 'pdo_sqlite', 'memory' => true, ]), - $config + $config, ); diff --git a/tests/Type/Doctrine/DBAL/mysqli.php b/tests/Type/Doctrine/DBAL/mysqli.php index 2bc11294..ce3859e5 100644 --- a/tests/Type/Doctrine/DBAL/mysqli.php +++ b/tests/Type/Doctrine/DBAL/mysqli.php @@ -13,7 +13,7 @@ $config->setMetadataCache(new ArrayCachePool()); $config->setMetadataDriverImpl(new AnnotationDriver( new AnnotationReader(), - [__DIR__ . '/data'] + [__DIR__ . '/data'], )); return new EntityManager( @@ -21,5 +21,5 @@ 'driver' => 'mysqli', 'memory' => true, ]), - $config + $config, ); diff --git a/tests/Type/Doctrine/DBAL/pdo.php b/tests/Type/Doctrine/DBAL/pdo.php index c7e48751..7b6b0da3 100644 --- a/tests/Type/Doctrine/DBAL/pdo.php +++ b/tests/Type/Doctrine/DBAL/pdo.php @@ -13,7 +13,7 @@ $config->setMetadataCache(new ArrayCachePool()); $config->setMetadataDriverImpl(new AnnotationDriver( new AnnotationReader(), - [__DIR__ . '/data'] + [__DIR__ . '/data'], )); return new EntityManager( @@ -21,5 +21,5 @@ 'driver' => 'pdo_pgsql', 'memory' => true, ]), - $config + $config, ); diff --git a/tests/Type/Doctrine/DoctrineSelectableDynamicReturnTypeExtensionTest.php b/tests/Type/Doctrine/DoctrineSelectableDynamicReturnTypeExtensionTest.php index a34c7b22..01b66d7b 100644 --- a/tests/Type/Doctrine/DoctrineSelectableDynamicReturnTypeExtensionTest.php +++ b/tests/Type/Doctrine/DoctrineSelectableDynamicReturnTypeExtensionTest.php @@ -15,8 +15,7 @@ final class DoctrineSelectableDynamicReturnTypeExtensionTest extends TestCase { - /** @var DoctrineSelectableDynamicReturnTypeExtension */ - private $extension; + private DoctrineSelectableDynamicReturnTypeExtension $extension; protected function setUp(): void { @@ -52,10 +51,8 @@ public function testGetTypeFromMethodCall(): void $scope = $this->createMock(Scope::class); $scope->method('getType')->will( self::returnCallback( - static function (): Type { - return new ObjectType(Collection::class); - } - ) + static fn (): Type => new ObjectType(Collection::class), + ), ); $var = $this->createMock(Expr::class); diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php index b9d02c85..52e08911 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php @@ -79,7 +79,7 @@ public function test(Type $expectedType, string $dql, string $methodName, ?int $ $typeBuilder, self::getContainer()->getByType(DescriptorRegistry::class), self::getContainer()->getByType(PhpVersion::class), - self::getContainer()->getByType(DriverDetector::class) + self::getContainer()->getByType(DriverDetector::class), ); $resolver = self::getContainer()->getByType(HydrationModeReturnTypeResolver::class); @@ -89,12 +89,12 @@ public function test(Type $expectedType, string $dql, string $methodName, ?int $ new ConstantIntegerType($this->getRealHydrationMode($methodName, $hydrationMode)), $typeBuilder->getIndexType(), $typeBuilder->getResultType(), - $entityManager + $entityManager, ) ?? new MixedType(); self::assertSame( $expectedType->describe(VerbosityLevel::precise()), - $type->describe(VerbosityLevel::precise()) + $type->describe(VerbosityLevel::precise()), ); $query = $entityManager->createQuery($dql); @@ -106,8 +106,8 @@ public function test(Type $expectedType, string $dql, string $methodName, ?int $ sprintf( "The inferred type\n%s\nshould accept actual type\n%s", $type->describe(VerbosityLevel::precise()), - $resultType->describe(VerbosityLevel::precise()) - ) + $resultType->describe(VerbosityLevel::precise()), + ), ); } diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php index fd6fee74..d7d42b14 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php @@ -61,11 +61,9 @@ final class QueryResultTypeWalkerTest extends PHPStanTestCase { - /** @var EntityManagerInterface */ - private static $em; + private static EntityManagerInterface $em; - /** @var DescriptorRegistry */ - private $descriptorRegistry; + private DescriptorRegistry $descriptorRegistry; public static function getAdditionalConfigFiles(): array { @@ -222,14 +220,14 @@ public function test(Type $expectedType, string $dql, ?string $expectedException $typeBuilder, $this->descriptorRegistry, self::getContainer()->getByType(PhpVersion::class), - self::getContainer()->getByType(DriverDetector::class) + self::getContainer()->getByType(DriverDetector::class), ); $type = $typeBuilder->getResultType(); self::assertSame( $expectedType->describe(VerbosityLevel::precise()), - $type->describe(VerbosityLevel::precise()) + $type->describe(VerbosityLevel::precise()), ); // Double-check our expectations @@ -246,8 +244,8 @@ public function test(Type $expectedType, string $dql, ?string $expectedException sprintf( "The inferred type\n%s\nshould accept actual type\n%s", $type->describe(VerbosityLevel::precise()), - $rowType->describe(VerbosityLevel::precise()) - ) + $rowType->describe(VerbosityLevel::precise()), + ), ); } } @@ -293,7 +291,7 @@ public function getTestData(): iterable yield 'arbitrary left join, selected' => [ TypeCombinator::union( new ObjectType(Many::class), - TypeCombinator::addNull(new ObjectType(One::class)) + TypeCombinator::addNull(new ObjectType(One::class)), ), ' SELECT m, o @@ -306,7 +304,7 @@ public function getTestData(): iterable yield 'arbitrary inner join, selected' => [ TypeCombinator::union( new ObjectType(Many::class), - new ObjectType(One::class) + new ObjectType(One::class), ), ' SELECT m, o @@ -323,7 +321,7 @@ public function getTestData(): iterable ]), $this->constantArray([ [new ConstantIntegerType(0), TypeCombinator::addNull(new ObjectType(One::class))], - ]) + ]), ), ' SELECT m AS many, o @@ -340,7 +338,7 @@ public function getTestData(): iterable ]), $this->constantArray([ [new ConstantStringType('one'), TypeCombinator::addNull(new ObjectType(One::class))], - ]) + ]), ), ' SELECT m AS many, o AS one @@ -357,7 +355,7 @@ public function getTestData(): iterable ]), $this->constantArray([ [new ConstantStringType('one'), new ObjectType(One::class)], - ]) + ]), ), ' SELECT m AS many, o AS one @@ -378,7 +376,7 @@ public function getTestData(): iterable [new ConstantIntegerType(0), new ObjectType(One::class)], [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString()], [new ConstantStringType('intColumn'), new IntegerType()], - ]) + ]), ), ' SELECT m, o, m.id, o.intColumn @@ -400,7 +398,7 @@ public function getTestData(): iterable [new ConstantIntegerType(0), new ObjectType(Many::class)], [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString()], [new ConstantStringType('intColumn'), new IntegerType()], - ]) + ]), ), ' SELECT o, m2, m, m.id, o.intColumn @@ -421,7 +419,7 @@ public function getTestData(): iterable [new ConstantStringType('one'), new ObjectType(One::class)], [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString()], [new ConstantStringType('intColumn'), new IntegerType()], - ]) + ]), ), ' SELECT m AS many, o AS one, m.id, o.intColumn @@ -674,42 +672,42 @@ public function getTestData(): iterable new ConstantIntegerType(1), TypeCombinator::union( $this->intOrStringified(), - new NullType() + new NullType(), ), ], [ new ConstantIntegerType(2), TypeCombinator::union( $this->intOrStringified(), - new NullType() + new NullType(), ), ], [ new ConstantIntegerType(3), TypeCombinator::union( $this->intOrStringified(), - new NullType() + new NullType(), ), ], [ new ConstantIntegerType(4), TypeCombinator::union( $this->intOrStringified(), - new NullType() + new NullType(), ), ], [ new ConstantIntegerType(5), TypeCombinator::union( $this->floatOrStringified(), - new NullType() + new NullType(), ), ], [ new ConstantIntegerType(6), TypeCombinator::union( $this->floatOrStringified(), - new NullType() + new NullType(), ), ], [ @@ -754,7 +752,7 @@ public function getTestData(): iterable new ConstantIntegerType(1), TypeCombinator::union( $this->stringifies() ? new ConstantStringType('1') : new ConstantIntegerType(1), - new NullType() + new NullType(), ), ], ]), @@ -770,14 +768,14 @@ public function getTestData(): iterable new ConstantIntegerType(1), TypeCombinator::union( new StringType(), - $this->intOrStringified() + $this->intOrStringified(), ), ], [ new ConstantIntegerType(2), TypeCombinator::union( new StringType(), - new NullType() + new NullType(), ), ], [ @@ -790,7 +788,7 @@ public function getTestData(): iterable ? $this->numericString() : TypeCombinator::union( new IntegerType(), - new FloatType() + new FloatType(), ), ], ]), @@ -809,7 +807,7 @@ public function getTestData(): iterable new ConstantIntegerType(1), TypeCombinator::union( new StringType(), - $this->stringifies() ? new ConstantStringType('0') : new ConstantIntegerType(0) + $this->stringifies() ? new ConstantStringType('0') : new ConstantIntegerType(0), ), ], ]), @@ -829,7 +827,7 @@ public function getTestData(): iterable new ConstantIntegerType(1), TypeCombinator::union( new StringType(), - $this->stringifies() ? new ConstantStringType('0') : new ConstantIntegerType(0) + $this->stringifies() ? new ConstantStringType('0') : new ConstantIntegerType(0), ), ], ]), @@ -849,7 +847,7 @@ public function getTestData(): iterable new ConstantIntegerType(1), TypeCombinator::union( $this->stringifies() ? new ConstantStringType('0') : new ConstantIntegerType(0), - $this->stringifies() ? new ConstantStringType('1') : new ConstantIntegerType(1) + $this->stringifies() ? new ConstantStringType('1') : new ConstantIntegerType(1), ), ], ]), @@ -868,7 +866,7 @@ public function getTestData(): iterable new ConstantIntegerType(1), TypeCombinator::union( $this->stringifies() ? new ConstantStringType('0') : new ConstantIntegerType(0), - $this->stringifies() ? new ConstantStringType('1') : new ConstantIntegerType(1) + $this->stringifies() ? new ConstantStringType('1') : new ConstantIntegerType(1), ), ], ]), @@ -1083,7 +1081,7 @@ public function getTestData(): iterable new ConstantIntegerType(1), new ObjectType(OneId::class), ], - ]) + ]), ), ' SELECT NEW QueryResult\Entities\ManyId(m.id), From 731ac4cdbebc7a08c7ca87f972f9b6c568412f16 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 14:55:55 +0200 Subject: [PATCH 24/71] Remove obsolete skips --- .../UnusedPrivatePropertyRuleTest.php | 4 -- .../ORM/QueryBuilderDqlRuleSlowTest.php | 4 -- .../Doctrine/ORM/QueryBuilderDqlRuleTest.php | 5 --- ...ingGedmoByPhpDocPropertyAssignRuleTest.php | 5 --- ...ReadOnlyByPhpDocPropertyAssignRuleTest.php | 5 --- .../Query/QueryResultTypeWalkerTest.php | 44 +++++++++---------- 6 files changed, 21 insertions(+), 46 deletions(-) diff --git a/tests/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index 8c582618..e7a88b28 100644 --- a/tests/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -27,10 +27,6 @@ public static function getAdditionalConfigFiles(): array public function testRule(): void { - if (PHP_VERSION_ID < 70400) { - self::markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/unused-private-property.php'], [ [ 'Property PHPStan\Rules\Doctrine\ORM\UnusedPrivateProperty\EntityWithAGeneratedId::$unused is never written, only read.', diff --git a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php index 49cec42d..2c4e03f7 100644 --- a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php +++ b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php @@ -5,7 +5,6 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use PHPStan\Type\Doctrine\ObjectMetadataResolver; -use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -23,9 +22,6 @@ protected function getRule(): Rule public function testRule(): void { - if (PHP_VERSION_ID < 70300) { - self::markTestSkipped('For some reason PHP 7.2 cannot recover from Trying to get property value of non-object'); - } $this->analyse([__DIR__ . '/data/query-builder-dql.php'], [ [ "QueryBuilder: [Syntax Error] line 0, col 66: Error: Expected end of string, got ')'\nDQL: SELECT e FROM PHPStan\Rules\Doctrine\ORM\MyEntity e WHERE e.id = 1)", diff --git a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php index bb7f6b87..65fd38d6 100644 --- a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php +++ b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php @@ -5,7 +5,6 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use PHPStan\Type\Doctrine\ObjectMetadataResolver; -use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -23,10 +22,6 @@ protected function getRule(): Rule public function testRule(): void { - if (PHP_VERSION_ID < 70300) { - self::markTestSkipped('For some reason PHP 7.2 cannot recover from Trying to get property value of non-object'); - } - $this->analyse([__DIR__ . '/data/query-builder-dql.php'], [ [ "QueryBuilder: [Syntax Error] line 0, col 66: Error: Expected end of string, got ')'\nDQL: SELECT e FROM PHPStan\Rules\Doctrine\ORM\MyEntity e WHERE e.id = 1)", diff --git a/tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php b/tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php index 683ddff3..7b66451e 100644 --- a/tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php +++ b/tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php @@ -7,7 +7,6 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use PHPStan\Type\Doctrine\ObjectMetadataResolver; -use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -34,10 +33,6 @@ public static function getAdditionalConfigFiles(): array public function testRule(): void { - if (PHP_VERSION_ID < 70400) { - self::markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/gedmo-property-assign-phpdoc.php'], [ // No errors expected ]); diff --git a/tests/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php b/tests/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php index 2f42a9a4..8a72494f 100644 --- a/tests/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php +++ b/tests/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php @@ -4,7 +4,6 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; -use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -24,10 +23,6 @@ public static function getAdditionalConfigFiles(): array public function testRule(): void { - if (PHP_VERSION_ID < 70400) { - self::markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/missing-readonly-property-assign-phpdoc.php'], [ [ 'Class MissingReadOnlyPropertyAssignPhpDoc\EntityWithAGeneratedId has an uninitialized @readonly property $unassigned. Assign it in the constructor.', diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php index d7d42b14..9b17c3c5 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php @@ -1533,35 +1533,33 @@ private function yieldConditionalDataset(): iterable ]; } - if (PHP_VERSION_ID >= 70400) { - yield 'locate function' => [ - $this->constantArray([ - [new ConstantIntegerType(1), $this->uintOrStringified()], - [new ConstantIntegerType(2), TypeCombinator::addNull($this->uintOrStringified())], - [new ConstantIntegerType(3), TypeCombinator::addNull($this->uintOrStringified())], - [new ConstantIntegerType(4), $this->uintOrStringified()], - ]), - ' + yield 'locate function' => [ + $this->constantArray([ + [new ConstantIntegerType(1), $this->uintOrStringified()], + [new ConstantIntegerType(2), TypeCombinator::addNull($this->uintOrStringified())], + [new ConstantIntegerType(3), TypeCombinator::addNull($this->uintOrStringified())], + [new ConstantIntegerType(4), $this->uintOrStringified()], + ]), + ' SELECT LOCATE(m.stringColumn, m.stringColumn, 0), LOCATE(m.stringNullColumn, m.stringColumn, 0), LOCATE(m.stringColumn, m.stringNullColumn, 0), LOCATE(\'f\', \'foo\', 0) FROM QueryResult\Entities\Many m ', - null, - InstalledVersions::satisfies(new VersionParser(), 'doctrine/dbal', '>=3.4') - ? null - : ( - PHP_VERSION_ID >= 80100 - ? 'strpos(): Passing null to parameter #2 ($needle) of type string is deprecated' - : ( - PHP_VERSION_ID < 80000 - ? 'strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior' - : null - ) - ), - ]; - } + null, + InstalledVersions::satisfies(new VersionParser(), 'doctrine/dbal', '>=3.4') + ? null + : ( + PHP_VERSION_ID >= 80100 + ? 'strpos(): Passing null to parameter #2 ($needle) of type string is deprecated' + : ( + PHP_VERSION_ID < 80000 + ? 'strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior' + : null + ) + ), + ]; $ormVersion = InstalledVersions::getVersion('doctrine/orm'); $hasOrm3 = $ormVersion !== null && strpos($ormVersion, '3.') === 0; From 9ddc146f78776d5eff79a354bebb0838ac593b86 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 15:00:32 +0200 Subject: [PATCH 25/71] Put doctrine/lexer:^2.0 back --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1bad25c5..58c0ac4e 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "doctrine/collections": "^2.1", "doctrine/common": "^2.7 || ^3.0", "doctrine/dbal": "^3.3.8", - "doctrine/lexer": "^3.0", + "doctrine/lexer": "^2.0 || ^3.0", "doctrine/mongodb-odm": "^2.4.3", "doctrine/orm": "^2.16.0", "doctrine/persistence": "^2.2.1 || ^3.2", From 22a33bc188f1228252597acb794e4d629daa3352 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 15:00:53 +0200 Subject: [PATCH 26/71] Do not run PHPStan on 7.3 --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3230e295..ad3633e9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -153,7 +153,6 @@ jobs: fail-fast: false matrix: php-version: - - "7.3" - "7.4" - "8.0" - "8.1" From da4e194fcdba5b141f095d0509d2e35d632fc12a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 15:02:17 +0200 Subject: [PATCH 27/71] Put doctrine/collections:^1.6 back --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 58c0ac4e..fc15096e 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "composer/semver": "^3.3.2", "cweagans/composer-patches": "^1.7.3", "doctrine/annotations": "^2.0", - "doctrine/collections": "^2.1", + "doctrine/collections": "^1.6 || ^2.1", "doctrine/common": "^2.7 || ^3.0", "doctrine/dbal": "^3.3.8", "doctrine/lexer": "^2.0 || ^3.0", From d462eb98c6d2b7c2311ac8a4a9ad33f20e99a623 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 15:02:54 +0200 Subject: [PATCH 28/71] Fix CS --- tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php index 9b17c3c5..bceab2fc 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php @@ -1554,10 +1554,10 @@ private function yieldConditionalDataset(): iterable PHP_VERSION_ID >= 80100 ? 'strpos(): Passing null to parameter #2 ($needle) of type string is deprecated' : ( - PHP_VERSION_ID < 80000 - ? 'strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior' - : null - ) + PHP_VERSION_ID < 80000 + ? 'strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior' + : null + ) ), ]; From 0f98bd177aac3eaf529d1aa9dee6456545c77016 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 15:08:34 +0200 Subject: [PATCH 29/71] Do not expect deprecation in test --- .../Doctrine/Query/QueryResultTypeWalkerTest.php | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php index bceab2fc..b5ac7ee8 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php @@ -199,7 +199,7 @@ public function setUp(): void } /** @dataProvider getTestData */ - public function test(Type $expectedType, string $dql, ?string $expectedExceptionMessage = null, ?string $expectedDeprecationMessage = null): void + public function test(Type $expectedType, string $dql, ?string $expectedExceptionMessage = null): void { $em = self::$em; @@ -210,9 +210,6 @@ public function test(Type $expectedType, string $dql, ?string $expectedException if ($expectedExceptionMessage !== null) { $this->expectException(Throwable::class); $this->expectExceptionMessage($expectedExceptionMessage); - } elseif ($expectedDeprecationMessage !== null) { - $this->expectDeprecation(); - $this->expectDeprecationMessage($expectedDeprecationMessage); } QueryResultTypeWalker::walk( @@ -1548,17 +1545,6 @@ private function yieldConditionalDataset(): iterable FROM QueryResult\Entities\Many m ', null, - InstalledVersions::satisfies(new VersionParser(), 'doctrine/dbal', '>=3.4') - ? null - : ( - PHP_VERSION_ID >= 80100 - ? 'strpos(): Passing null to parameter #2 ($needle) of type string is deprecated' - : ( - PHP_VERSION_ID < 80000 - ? 'strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior' - : null - ) - ), ]; $ormVersion = InstalledVersions::getVersion('doctrine/orm'); From 039d325e5aed2b047b6d8a5d143ec6753defceed Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 15:18:12 +0200 Subject: [PATCH 30/71] Remove test about deprecated LOCATE() function in DBAL SQLite platform See https://github.com/doctrine/dbal/pull/5749 --- .../Query/QueryResultTypeWalkerTest.php | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php index b5ac7ee8..33fa5bbd 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php @@ -1530,23 +1530,6 @@ private function yieldConditionalDataset(): iterable ]; } - yield 'locate function' => [ - $this->constantArray([ - [new ConstantIntegerType(1), $this->uintOrStringified()], - [new ConstantIntegerType(2), TypeCombinator::addNull($this->uintOrStringified())], - [new ConstantIntegerType(3), TypeCombinator::addNull($this->uintOrStringified())], - [new ConstantIntegerType(4), $this->uintOrStringified()], - ]), - ' - SELECT LOCATE(m.stringColumn, m.stringColumn, 0), - LOCATE(m.stringNullColumn, m.stringColumn, 0), - LOCATE(m.stringColumn, m.stringNullColumn, 0), - LOCATE(\'f\', \'foo\', 0) - FROM QueryResult\Entities\Many m - ', - null, - ]; - $ormVersion = InstalledVersions::getVersion('doctrine/orm'); $hasOrm3 = $ormVersion !== null && strpos($ormVersion, '3.') === 0; From a103cfebd51a25508bbe667793781775209486d1 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Mon, 9 Sep 2024 13:50:30 +0200 Subject: [PATCH 31/71] QueryResultTypeWalker: support pdo_pgsql float fetches on PHP 8.4 --- src/Type/Doctrine/Descriptors/FloatType.php | 8 +- .../Doctrine/Query/QueryResultTypeWalker.php | 38 +-- ...eryResultTypeWalkerFetchTypeMatrixTest.php | 266 ++++++++++-------- 3 files changed, 162 insertions(+), 150 deletions(-) diff --git a/src/Type/Doctrine/Descriptors/FloatType.php b/src/Type/Doctrine/Descriptors/FloatType.php index 2518e72d..e475c0a2 100644 --- a/src/Type/Doctrine/Descriptors/FloatType.php +++ b/src/Type/Doctrine/Descriptors/FloatType.php @@ -53,18 +53,12 @@ public function getDatabaseInternalTypeForDriver(Connection $connection): Type { $driverType = $this->driverDetector->detect($connection); - if ($driverType === DriverDetector::PDO_PGSQL) { - return new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]); - } - if (in_array($driverType, [ DriverDetector::SQLITE3, DriverDetector::PDO_SQLITE, DriverDetector::MYSQLI, DriverDetector::PDO_MYSQL, + DriverDetector::PDO_PGSQL, DriverDetector::PGSQL, ], true)) { return new \PHPStan\Type\FloatType(); diff --git a/src/Type/Doctrine/Query/QueryResultTypeWalker.php b/src/Type/Doctrine/Query/QueryResultTypeWalker.php index 4ef105d9..54d836c6 100644 --- a/src/Type/Doctrine/Query/QueryResultTypeWalker.php +++ b/src/Type/Doctrine/Query/QueryResultTypeWalker.php @@ -471,10 +471,6 @@ public function walkFunction($function): string } if ($this->containsOnlyNumericTypes($exprTypeNoNull)) { - if ($this->driverType === DriverDetector::PDO_PGSQL) { - return $this->marshalType($this->createNumericString($nullable)); - } - return $this->marshalType($exprType); // retains underlying type } @@ -627,13 +623,7 @@ public function walkFunction($function): string $type = TypeCombinator::addNull($type); } - } elseif ($this->driverType === DriverDetector::PDO_PGSQL) { - $type = new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]); - - } elseif ($this->driverType === DriverDetector::PGSQL) { + } elseif ($this->driverType === DriverDetector::PGSQL || $this->driverType === DriverDetector::PDO_PGSQL) { $castedExprType = $this->castStringLiteralForNumericExpression($exprTypeNoNull); if ($castedExprType->isInteger()->yes() || $castedExprType->isFloat()->yes()) { @@ -1771,12 +1761,6 @@ private function inferPlusMinusTimesType(array $termTypes): Type return $this->createInteger($nullable); } - if ($this->driverType === DriverDetector::PDO_PGSQL) { - if ($this->containsOnlyNumericTypes($unionWithoutNull)) { - return $this->createNumericString($nullable); - } - } - if ($this->driverType === DriverDetector::SQLITE3 || $this->driverType === DriverDetector::PDO_SQLITE) { if (!$this->containsOnlyNumericTypes(...$typesNoNull)) { return new MixedType(); @@ -1791,7 +1775,7 @@ private function inferPlusMinusTimesType(array $termTypes): Type return $this->createFloatOrInt($nullable); } - if ($this->driverType === DriverDetector::MYSQLI || $this->driverType === DriverDetector::PDO_MYSQL || $this->driverType === DriverDetector::PGSQL) { + if ($this->driverType === DriverDetector::MYSQLI || $this->driverType === DriverDetector::PDO_MYSQL || $this->driverType === DriverDetector::PGSQL || $this->driverType === DriverDetector::PDO_PGSQL) { if ($this->containsOnlyTypes($unionWithoutNull, [new IntegerType(), new FloatType()])) { return $this->createFloat($nullable); } @@ -1857,12 +1841,6 @@ private function inferDivisionType(array $termTypes): Type return new MixedType(); } - if ($this->driverType === DriverDetector::PDO_PGSQL) { - if ($this->containsOnlyTypes($unionWithoutNull, [new IntegerType(), new FloatType(), $this->createNumericString(false)])) { - return $this->createNumericString($nullable); - } - } - if ($this->driverType === DriverDetector::SQLITE3 || $this->driverType === DriverDetector::PDO_SQLITE) { if (!$this->containsOnlyNumericTypes(...$typesNoNull)) { return new MixedType(); @@ -1877,7 +1855,7 @@ private function inferDivisionType(array $termTypes): Type return $this->createFloatOrInt($nullable); } - if ($this->driverType === DriverDetector::MYSQLI || $this->driverType === DriverDetector::PDO_MYSQL || $this->driverType === DriverDetector::PGSQL) { + if ($this->driverType === DriverDetector::MYSQLI || $this->driverType === DriverDetector::PDO_MYSQL || $this->driverType === DriverDetector::PGSQL || $this->driverType === DriverDetector::PDO_PGSQL) { if ($this->containsOnlyTypes($unionWithoutNull, [new IntegerType(), new FloatType()])) { return $this->createFloat($nullable); } @@ -2100,6 +2078,9 @@ private function hasAggregateWithoutGroupBy(): bool * - pdo_sqlite: https://github.com/php/php-src/commit/438b025a28cda2935613af412fc13702883dd3a2 * - pdo_pgsql: https://github.com/php/php-src/commit/737195c3ae6ac53b9501cfc39cc80fd462909c82 * + * Notable 8.4 changes: + * - pdo_pgsql: https://github.com/php/php-src/commit/6d10a6989897e9089d62edf939344437128e93ad + * * @param IntegerType|FloatType|BooleanType $type */ private function shouldStringifyExpressions(Type $type): TrinaryLogic @@ -2144,7 +2125,14 @@ private function shouldStringifyExpressions(Type $type): TrinaryLogic } return TrinaryLogic::createNo(); + } + if ($type->isFloat()->yes()) { + if ($this->phpVersion->getVersionId() >= 80400) { + return TrinaryLogic::createFromBoolean($stringifyFetches); + } + + return TrinaryLogic::createYes(); } return TrinaryLogic::createFromBoolean($stringifyFetches); diff --git a/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php b/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php index 04fdcba8..3faae5a6 100644 --- a/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php +++ b/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php @@ -71,6 +71,7 @@ final class QueryResultTypeWalkerFetchTypeMatrixTest extends PHPStanTestCase private const STRINGIFY_NONE = 'none'; private const STRINGIFY_DEFAULT = 'default'; private const STRINGIFY_PG_BOOL = 'pg_bool'; + private const STRINGIFY_PG_FLOAT = 'pg_float'; private const CONFIG_DEFAULT = 'default'; private const CONFIG_STRINGIFY = 'pdo_stringify'; @@ -974,15 +975,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_int + t.col_float FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 9.125, 'sqliteResult' => 9.125, - 'pdoPgsqlResult' => '9.125', + 'pdoPgsqlResult' => 9.125, 'pgsqlResult' => 9.125, 'mssqlResult' => 9.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_int + t.col_mixed' => [ @@ -1006,15 +1007,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_bigint + t.col_float FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 2147483648.125, 'sqliteResult' => 2147483648.125, - 'pdoPgsqlResult' => '2147483648.125', + 'pdoPgsqlResult' => 2147483648.125, 'pgsqlResult' => 2147483648.125, 'mssqlResult' => 2147483648.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_bigint + t.col_float (int data)' => [ @@ -1022,15 +1023,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_bigint + t.col_float FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 2.0, 'sqliteResult' => 2.0, - 'pdoPgsqlResult' => '2', + 'pdoPgsqlResult' => 2.0, 'pgsqlResult' => 2.0, 'mssqlResult' => 2.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_float + t.col_float' => [ @@ -1038,15 +1039,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_float + t.col_float FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 0.25, 'sqliteResult' => 0.25, - 'pdoPgsqlResult' => '0.25', + 'pdoPgsqlResult' => 0.25, 'pgsqlResult' => 0.25, 'mssqlResult' => 0.25, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_int + t.col_decimal' => [ @@ -1086,15 +1087,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_float + t.col_decimal FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 0.225, 'sqliteResult' => 0.225, - 'pdoPgsqlResult' => '0.225', + 'pdoPgsqlResult' => 0.225, 'pgsqlResult' => 0.225, 'mssqlResult' => 0.225, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_float + t.col_decimal (int data)' => [ @@ -1102,15 +1103,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_float + t.col_decimal FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 2.0, 'sqliteResult' => 2.0, - 'pdoPgsqlResult' => '2', + 'pdoPgsqlResult' => 2.0, 'pgsqlResult' => 2.0, 'mssqlResult' => 2.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_decimal + t.col_decimal (int data)' => [ @@ -1134,15 +1135,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_int + t.col_float + t.col_decimal FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 9.225, 'sqliteResult' => 9.225, - 'pdoPgsqlResult' => '9.225', + 'pdoPgsqlResult' => 9.225, 'pgsqlResult' => 9.225, 'mssqlResult' => 9.225, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_decimal + t.col_decimal' => [ @@ -1310,15 +1311,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_int / t.col_float FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 72.0, 'sqliteResult' => 72.0, - 'pdoPgsqlResult' => '72', + 'pdoPgsqlResult' => 72.0, 'pgsqlResult' => 72.0, 'mssqlResult' => 72.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_int / t.col_float / t.col_decimal' => [ @@ -1326,15 +1327,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_int / t.col_float / t.col_decimal FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 720.0, 'sqliteResult' => 720.0, - 'pdoPgsqlResult' => '720', + 'pdoPgsqlResult' => 720.0, 'pgsqlResult' => 720.0, 'mssqlResult' => 720.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_bigint / t.col_float' => [ @@ -1342,15 +1343,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_bigint / t.col_float FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 17179869184.0, 'sqliteResult' => 17179869184.0, - 'pdoPgsqlResult' => '17179869184', + 'pdoPgsqlResult' => 17179869184.0, 'pgsqlResult' => 17179869184.0, 'mssqlResult' => 17179869184.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_float / t.col_float' => [ @@ -1358,15 +1359,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_float / t.col_float FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_int / t.col_decimal' => [ @@ -1406,15 +1407,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_float / t.col_decimal FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.25, 'sqliteResult' => 1.25, - 'pdoPgsqlResult' => '1.25', + 'pdoPgsqlResult' => 1.25, 'pgsqlResult' => 1.25, 'mssqlResult' => 1.25, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_decimal / t.col_decimal' => [ @@ -1961,20 +1962,36 @@ public static function provideCases(): iterable 'stringify' => self::STRINGIFY_DEFAULT, ]; + yield 'COALESCE(t.col_float, t.col_float)' => [ + 'data' => self::dataDefault(), + 'select' => 'SELECT COALESCE(t.col_float, t.col_float) FROM %s t', + 'mysql' => self::float(), + 'sqlite' => self::float(), + 'pdo_pgsql' => self::float(), + 'pgsql' => self::float(), + 'mssql' => self::mixed(), + 'mysqlResult' => 0.125, + 'sqliteResult' => 0.125, + 'pdoPgsqlResult' => 0.125, + 'pgsqlResult' => 0.125, + 'mssqlResult' => 0.125, + 'stringify' => self::STRINGIFY_PG_FLOAT, + ]; + yield 'COALESCE(t.col_float, t.col_float) + int data' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT COALESCE(t.col_float, t.col_float) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_decimal' => [ @@ -2046,15 +2063,15 @@ public static function provideCases(): iterable 'select' => 'SELECT AVG(t.col_float) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => 0.125, 'sqliteResult' => 0.125, - 'pdoPgsqlResult' => '0.125', + 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'AVG(t.col_float) + no data' => [ @@ -2062,7 +2079,7 @@ public static function provideCases(): iterable 'select' => 'SELECT AVG(t.col_float) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => null, @@ -2070,7 +2087,7 @@ public static function provideCases(): iterable 'pdoPgsqlResult' => null, 'pgsqlResult' => null, 'mssqlResult' => null, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'AVG(t.col_float) + GROUP BY' => [ @@ -2078,15 +2095,15 @@ public static function provideCases(): iterable 'select' => 'SELECT AVG(t.col_float) FROM %s t GROUP BY t.col_int', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 0.125, 'sqliteResult' => 0.125, - 'pdoPgsqlResult' => '0.125', + 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'AVG(t.col_float_nullable) + GROUP BY' => [ @@ -2094,7 +2111,7 @@ public static function provideCases(): iterable 'select' => 'SELECT AVG(t.col_float_nullable) FROM %s t GROUP BY t.col_int', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => null, @@ -2102,7 +2119,7 @@ public static function provideCases(): iterable 'pdoPgsqlResult' => null, 'pgsqlResult' => null, 'mssqlResult' => null, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'AVG(t.col_decimal)' => [ @@ -2350,15 +2367,15 @@ public static function provideCases(): iterable 'select' => 'SELECT SUM(t.col_float) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => 0.125, 'sqliteResult' => 0.125, - 'pdoPgsqlResult' => '0.125', + 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'SUM(t.col_float) + no data' => [ @@ -2366,7 +2383,7 @@ public static function provideCases(): iterable 'select' => 'SELECT SUM(t.col_float) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => null, @@ -2374,7 +2391,7 @@ public static function provideCases(): iterable 'pdoPgsqlResult' => null, 'pgsqlResult' => null, 'mssqlResult' => null, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'SUM(t.col_float) + GROUP BY' => [ @@ -2382,15 +2399,15 @@ public static function provideCases(): iterable 'select' => 'SELECT SUM(t.col_float) FROM %s t GROUP BY t.col_int', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 0.125, 'sqliteResult' => 0.125, - 'pdoPgsqlResult' => '0.125', + 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield '1 + -(CASE WHEN MIN(t.col_float) = 0 THEN SUM(t.col_float) ELSE 0 END)' => [ // agg function (causing null) deeply inside AST @@ -2398,15 +2415,15 @@ public static function provideCases(): iterable 'select' => 'SELECT 1 + -(CASE WHEN MIN(t.col_float) = 0 THEN SUM(t.col_float) ELSE 0 END) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrIntOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'SUM(t.col_decimal)' => [ @@ -2686,15 +2703,15 @@ public static function provideCases(): iterable 'select' => 'SELECT MAX(t.col_float) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => 0.125, 'sqliteResult' => 0.125, - 'pdoPgsqlResult' => '0.125', + 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'MAX(t.col_float) + no data' => [ @@ -2702,7 +2719,7 @@ public static function provideCases(): iterable 'select' => 'SELECT MAX(t.col_float) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => null, @@ -2710,7 +2727,7 @@ public static function provideCases(): iterable 'pdoPgsqlResult' => null, 'pgsqlResult' => null, 'mssqlResult' => null, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'MAX(t.col_float) + GROUP BY' => [ @@ -2718,12 +2735,12 @@ public static function provideCases(): iterable 'select' => 'SELECT MAX(t.col_float) FROM %s t GROUP BY t.col_int', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 0.125, 'sqliteResult' => 0.125, - 'pdoPgsqlResult' => '0.125', + 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, 'stringify' => self::STRINGIFY_DEFAULT, @@ -2958,15 +2975,15 @@ public static function provideCases(): iterable 'select' => 'SELECT ABS(t.col_float) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 0.125, 'sqliteResult' => 0.125, - 'pdoPgsqlResult' => '0.125', + 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'ABS(t.col_decimal)' => [ @@ -3166,15 +3183,15 @@ public static function provideCases(): iterable 'select' => "SELECT ABS('1.0') FROM %s t", 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield "ABS('1')" => [ @@ -3182,15 +3199,15 @@ public static function provideCases(): iterable 'select' => "SELECT ABS('1') FROM %s t", 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'ABS(t.col_bigint)' => [ @@ -3614,15 +3631,15 @@ public static function provideCases(): iterable 'select' => 'SELECT SQRT(t.col_float) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'SQRT(t.col_decimal)' => [ @@ -3646,15 +3663,15 @@ public static function provideCases(): iterable 'select' => 'SELECT SQRT(t.col_int) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 3.0, 'sqliteResult' => 3.0, - 'pdoPgsqlResult' => '3', + 'pdoPgsqlResult' => 3.0, 'pgsqlResult' => 3.0, 'mssqlResult' => 3.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'SQRT(t.col_mixed)' => [ @@ -3667,10 +3684,10 @@ public static function provideCases(): iterable 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'SQRT(t.col_int_nullable)' => [ @@ -3678,7 +3695,7 @@ public static function provideCases(): iterable 'select' => 'SELECT SQRT(t.col_int_nullable) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => PHP_VERSION_ID >= 80100 && !self::hasDbal4() ? null : self::floatOrNull(), // fails in UDF since PHP 8.1: sqrt(): Passing null to parameter #1 ($num) of type float is deprecated - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => null, @@ -3686,7 +3703,7 @@ public static function provideCases(): iterable 'pdoPgsqlResult' => null, 'pgsqlResult' => null, 'mssqlResult' => null, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'SQRT(-1)' => [ @@ -3710,15 +3727,15 @@ public static function provideCases(): iterable 'select' => 'SELECT SQRT(1) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield "SQRT('1')" => [ @@ -3726,15 +3743,15 @@ public static function provideCases(): iterable 'select' => "SELECT SQRT('1') FROM %s t", 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield "SQRT('1.0')" => [ @@ -3742,15 +3759,15 @@ public static function provideCases(): iterable 'select' => "SELECT SQRT('1.0') FROM %s t", 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield "SQRT('1e0')" => [ @@ -3758,15 +3775,15 @@ public static function provideCases(): iterable 'select' => "SELECT SQRT('1e0') FROM %s t", 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield "SQRT('foo')" => [ @@ -4238,15 +4255,15 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_float_nullable, 0) FROM %s t', 'mysql' => self::float(), 'sqlite' => TypeCombinator::union(self::float(), self::int()), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int()), + 'pdo_pgsql' => TypeCombinator::union(self::float(), self::int()), 'pgsql' => TypeCombinator::union(self::float(), self::int()), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0, - 'pdoPgsqlResult' => '0', + 'pdoPgsqlResult' => 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'COALESCE(t.col_float_nullable, 0.0)' => [ @@ -4254,15 +4271,15 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_float_nullable, 0.0) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => TypeCombinator::union(self::float(), self::numericString()), 'pgsql' => TypeCombinator::union(self::float(), self::numericString()), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0.0, - 'pdoPgsqlResult' => '0', + 'pdoPgsqlResult' => 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'COALESCE(t.col_int_nullable, t.col_decimal_nullable, 0)' => [ @@ -4286,15 +4303,15 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, 0) FROM %s t', 'mysql' => self::float(), 'sqlite' => TypeCombinator::union(self::float(), self::int()), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int()), + 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0, - 'pdoPgsqlResult' => '0', + 'pdoPgsqlResult' => 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, 0.0)' => [ @@ -4302,15 +4319,15 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, 0.0) FROM %s t', 'mysql' => self::float(), 'sqlite' => TypeCombinator::union(self::float(), self::int()), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int()), + 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0.0, - 'pdoPgsqlResult' => '0', + 'pdoPgsqlResult' => 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, 0e0)' => [ @@ -4318,15 +4335,15 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, 0e0) FROM %s t', 'mysql' => self::float(), 'sqlite' => TypeCombinator::union(self::float(), self::int()), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int()), + 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0.0, - 'pdoPgsqlResult' => '0', + 'pdoPgsqlResult' => 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield "COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, '0')" => [ @@ -4334,15 +4351,15 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, \'0\') FROM %s t', 'mysql' => self::numericString(), 'sqlite' => TypeCombinator::union(self::float(), self::int(), self::numericString()), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int()), + 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'mssql' => self::mixed(), 'mysqlResult' => '0', 'sqliteResult' => '0', - 'pdoPgsqlResult' => '0', + 'pdoPgsqlResult' => 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, t.col_string)' => [ @@ -4371,10 +4388,10 @@ public static function provideCases(): iterable 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'COALESCE(t.col_string_nullable, t.col_int)' => [ @@ -4996,6 +5013,15 @@ private function resolveDefaultBooleanStringification(?string $driver, int $php, return $this->resolveDefaultStringification($driver, $php, $configName); } + private function resolveDefaultFloatStringification(?string $driver, int $php, string $configName): bool + { + if ($php < 80400 && $driver === DriverDetector::PDO_PGSQL) { + return true; // pdo_pgsql does stringify floats even without ATTR_STRINGIFY_FETCHES prior to PHP 8.4 + } + + return $this->resolveDefaultStringification($driver, $php, $configName); + } + private function getHumanReadablePhpVersion(int $phpVersion): string { return floor($phpVersion / 10000) . '.' . floor(($phpVersion % 10000) / 100); @@ -5024,6 +5050,10 @@ private function shouldStringify(string $stringification, ?string $driverType, i return $this->resolveDefaultBooleanStringification($driverType, $phpVersion, $configName); } + if ($stringification === self::STRINGIFY_PG_FLOAT) { + return $this->resolveDefaultFloatStringification($driverType, $phpVersion, $configName); + } + throw new LogicException('Unknown stringification: ' . $stringification); } From 516967588266f7476fdff05350ff60130fa76b0c Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Mon, 9 Sep 2024 15:54:38 +0200 Subject: [PATCH 32/71] Fix asserts, add Dockerfile for PHP 8.4 --- .../Doctrine/Query/QueryResultTypeWalker.php | 5 ++ ...eryResultTypeWalkerFetchTypeMatrixTest.php | 74 +++++++++++++++---- tests/Platform/README.md | 10 ++- tests/Platform/docker/Dockerfile84 | 24 ++++++ tests/Platform/docker/docker-compose.yml | 14 ++++ 5 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 tests/Platform/docker/Dockerfile84 diff --git a/src/Type/Doctrine/Query/QueryResultTypeWalker.php b/src/Type/Doctrine/Query/QueryResultTypeWalker.php index 54d836c6..970523de 100644 --- a/src/Type/Doctrine/Query/QueryResultTypeWalker.php +++ b/src/Type/Doctrine/Query/QueryResultTypeWalker.php @@ -1587,6 +1587,11 @@ public function walkLiteral($literal): string if (stripos($value, 'e') !== false) { $type = new DqlConstantStringType((string) (float) $value, $literal->type); } else { + // if ($this->phpVersion->getVersionId() >= 80400) { + // $type = new ConstantFloatType((float) $value); + // } else { + // $type = new DqlConstantStringType($value, $literal->type); + // } $type = new DqlConstantStringType($value, $literal->type); } diff --git a/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php b/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php index 3faae5a6..28f9c2ed 100644 --- a/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php +++ b/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php @@ -2743,7 +2743,7 @@ public static function provideCases(): iterable 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'MAX(t.col_decimal)' => [ @@ -4170,6 +4170,38 @@ public static function provideCases(): iterable 'stringify' => self::STRINGIFY_DEFAULT, ]; + yield 'COALESCE(0, 0)' => [ + 'data' => self::dataDefault(), + 'select' => 'SELECT COALESCE(0, 0) FROM %s t', + 'mysql' => self::int(), + 'sqlite' => self::int(), + 'pdo_pgsql' => self::int(), + 'pgsql' => self::int(), + 'mssql' => self::mixed(), + 'mysqlResult' => 0, + 'sqliteResult' => 0, + 'pdoPgsqlResult' => 0, + 'pgsqlResult' => 0, + 'mssqlResult' => 0, + 'stringify' => self::STRINGIFY_DEFAULT, + ]; + + yield 'COALESCE(1.0, 1.0)' => [ + 'data' => self::dataDefault(), + 'select' => 'SELECT COALESCE(1.0, 1.0) FROM %s t', + 'mysql' => self::numericString(), + 'sqlite' => self::float(), + 'pdo_pgsql' => self::numericString(), + 'pgsql' => self::numericString(), + 'mssql' => self::mixed(), + 'mysqlResult' => '1.0', + 'sqliteResult' => 1.0, + 'pdoPgsqlResult' => '1.0', + 'pgsqlResult' => '1.0', + 'mssqlResult' => '1.0', + 'stringify' => self::STRINGIFY_DEFAULT, + ]; + yield 'COALESCE(1e0, 1.0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT COALESCE(1e0, 1.0) FROM %s t', @@ -4255,15 +4287,17 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_float_nullable, 0) FROM %s t', 'mysql' => self::float(), 'sqlite' => TypeCombinator::union(self::float(), self::int()), - 'pdo_pgsql' => TypeCombinator::union(self::float(), self::int()), + 'pdo_pgsql' => PHP_VERSION_ID < 80400 + ? TypeCombinator::union(self::numericString(), self::int()) + : TypeCombinator::union(self::float(), self::int()), 'pgsql' => TypeCombinator::union(self::float(), self::int()), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0, - 'pdoPgsqlResult' => 0.0, + 'pdoPgsqlResult' => PHP_VERSION_ID < 80400 ? '0' : 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, - 'stringify' => self::STRINGIFY_PG_FLOAT, + 'stringify' => self::STRINGIFY_DEFAULT, ]; yield 'COALESCE(t.col_float_nullable, 0.0)' => [ @@ -4303,15 +4337,17 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, 0) FROM %s t', 'mysql' => self::float(), 'sqlite' => TypeCombinator::union(self::float(), self::int()), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), + 'pdo_pgsql' => PHP_VERSION_ID < 80400 + ? TypeCombinator::union(self::numericString(), self::int()) + : TypeCombinator::union(self::numericString(), self::int(), self::float()), 'pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0, - 'pdoPgsqlResult' => 0.0, + 'pdoPgsqlResult' => PHP_VERSION_ID < 80400 ? '0' : 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, - 'stringify' => self::STRINGIFY_PG_FLOAT, + 'stringify' => self::STRINGIFY_DEFAULT, ]; yield 'COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, 0.0)' => [ @@ -4319,15 +4355,17 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, 0.0) FROM %s t', 'mysql' => self::float(), 'sqlite' => TypeCombinator::union(self::float(), self::int()), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), + 'pdo_pgsql' => PHP_VERSION_ID < 80400 + ? TypeCombinator::union(self::numericString(), self::int()) + : TypeCombinator::union(self::numericString(), self::int(), self::float()), 'pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0.0, - 'pdoPgsqlResult' => 0.0, + 'pdoPgsqlResult' => PHP_VERSION_ID < 80400 ? '0' : 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, - 'stringify' => self::STRINGIFY_PG_FLOAT, + 'stringify' => self::STRINGIFY_DEFAULT, ]; yield 'COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, 0e0)' => [ @@ -4335,15 +4373,17 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, 0e0) FROM %s t', 'mysql' => self::float(), 'sqlite' => TypeCombinator::union(self::float(), self::int()), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), + 'pdo_pgsql' => PHP_VERSION_ID < 80400 + ? TypeCombinator::union(self::numericString(), self::int()) + : TypeCombinator::union(self::numericString(), self::int(), self::float()), 'pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0.0, - 'pdoPgsqlResult' => 0.0, + 'pdoPgsqlResult' => PHP_VERSION_ID < 80400 ? '0' : 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, - 'stringify' => self::STRINGIFY_PG_FLOAT, + 'stringify' => self::STRINGIFY_DEFAULT, ]; yield "COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, '0')" => [ @@ -4351,15 +4391,17 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, \'0\') FROM %s t', 'mysql' => self::numericString(), 'sqlite' => TypeCombinator::union(self::float(), self::int(), self::numericString()), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), + 'pdo_pgsql' => PHP_VERSION_ID < 80400 + ? TypeCombinator::union(self::numericString(), self::int()) + : TypeCombinator::union(self::numericString(), self::int(), self::float()), 'pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'mssql' => self::mixed(), 'mysqlResult' => '0', 'sqliteResult' => '0', - 'pdoPgsqlResult' => 0.0, + 'pdoPgsqlResult' => PHP_VERSION_ID < 80400 ? '0' : 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, - 'stringify' => self::STRINGIFY_PG_FLOAT, + 'stringify' => self::STRINGIFY_DEFAULT, ]; yield 'COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, t.col_string)' => [ diff --git a/tests/Platform/README.md b/tests/Platform/README.md index d3678117..06d8b843 100644 --- a/tests/Platform/README.md +++ b/tests/Platform/README.md @@ -8,18 +8,22 @@ Set current working directory to project root. - `printf "UID=$(id -u)\nGID=$(id -g)" > .env` - `docker-compose -f tests/Platform/docker/docker-compose.yml up -d` -# Test behaviour with old stringification +# Test behaviour for PHP 8.0 (old stringification) - `docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php80 composer update` - `docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php80 php -d memory_limit=1G vendor/bin/phpunit --group=platform` -# Test behaviour with new stringification +# Test behaviour for PHP 8.1 (adjusted stringification) - `docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php81 composer update` - `docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php81 php -d memory_limit=1G vendor/bin/phpunit --group=platform` + +# Test behaviour for PHP 8.4 (pdo_pgsql float stringification fix) +- `docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php84 composer update` +- `docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php84 php -d memory_limit=1G vendor/bin/phpunit --group=platform` ``` You can also run utilize those containers for PHPStorm PHPUnit configuration. Since the dataset is huge and takes few minutes to run, you can filter only functions you are interested in: ```sh -docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php81 php -d memory_limit=1G vendor/bin/phpunit --group=platform --filter "AVG" +docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php84 php -d memory_limit=1G vendor/bin/phpunit --group=platform --filter "AVG" ``` diff --git a/tests/Platform/docker/Dockerfile84 b/tests/Platform/docker/Dockerfile84 new file mode 100644 index 00000000..81ac9834 --- /dev/null +++ b/tests/Platform/docker/Dockerfile84 @@ -0,0 +1,24 @@ +FROM php:8.4.0beta4-cli + +# MSSQL +RUN apt update \ + && apt install -y gnupg2 \ + && apt install -y unixodbc-dev unixodbc \ + && curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \ + && curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list | tee /etc/apt/sources.list.d/mssql-tools.list \ + && apt update \ + && ACCEPT_EULA=Y apt install -y msodbcsql17 \ + && pecl install sqlsrv \ + && pecl install pdo_sqlsrv \ + && docker-php-ext-enable sqlsrv pdo_sqlsrv + +RUN set -ex \ + && apt update \ + && apt install -y bash zip libpq-dev libsqlite3-dev \ + && pecl install xdebug-3.4 mongodb \ + && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \ + && docker-php-ext-install pdo mysqli pgsql pdo_mysql pdo_pgsql pdo_sqlite \ + && docker-php-ext-enable mongodb # TODO xdebug not yet supported here + +COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer + diff --git a/tests/Platform/docker/docker-compose.yml b/tests/Platform/docker/docker-compose.yml index 73596b72..4a3b0f48 100644 --- a/tests/Platform/docker/docker-compose.yml +++ b/tests/Platform/docker/docker-compose.yml @@ -63,3 +63,17 @@ services: user: ${UID:-1000}:${GID:-1000} volumes: - ../../../:/app + + php84: + depends_on: [mysql, pgsql] + build: + context: . + dockerfile: ./Dockerfile84 + environment: + MYSQL_HOST: mysql + PGSQL_HOST: pgsql + MSSQL_HOST: mssql + working_dir: /app + user: ${UID:-1000}:${GID:-1000} + volumes: + - ../../../:/app From 0d8866981a1662629a0559e969ad0c1408faaf45 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Mon, 9 Sep 2024 15:55:54 +0200 Subject: [PATCH 33/71] Remove commented code --- src/Type/Doctrine/Query/QueryResultTypeWalker.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Type/Doctrine/Query/QueryResultTypeWalker.php b/src/Type/Doctrine/Query/QueryResultTypeWalker.php index 970523de..54d836c6 100644 --- a/src/Type/Doctrine/Query/QueryResultTypeWalker.php +++ b/src/Type/Doctrine/Query/QueryResultTypeWalker.php @@ -1587,11 +1587,6 @@ public function walkLiteral($literal): string if (stripos($value, 'e') !== false) { $type = new DqlConstantStringType((string) (float) $value, $literal->type); } else { - // if ($this->phpVersion->getVersionId() >= 80400) { - // $type = new ConstantFloatType((float) $value); - // } else { - // $type = new DqlConstantStringType($value, $literal->type); - // } $type = new DqlConstantStringType($value, $literal->type); } From d198a78b0b3ad70758c43c822f9bb86ebbcd0a48 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 13 Sep 2024 14:51:58 +0200 Subject: [PATCH 34/71] Fixes after TypeSpecifier BC break --- .../Doctrine/Collection/IsEmptyTypeSpecifyingExtension.php | 2 ++ .../QueryBuilder/QueryBuilderTypeSpecifyingExtension.php | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Type/Doctrine/Collection/IsEmptyTypeSpecifyingExtension.php b/src/Type/Doctrine/Collection/IsEmptyTypeSpecifyingExtension.php index d94a455a..32765e79 100644 --- a/src/Type/Doctrine/Collection/IsEmptyTypeSpecifyingExtension.php +++ b/src/Type/Doctrine/Collection/IsEmptyTypeSpecifyingExtension.php @@ -61,12 +61,14 @@ public function specifyTypes( new MethodCall($node->var, self::FIRST_METHOD_NAME), new ConstantBooleanType(false), $context, + $scope, ); $last = $this->typeSpecifier->create( new MethodCall($node->var, self::LAST_METHOD_NAME), new ConstantBooleanType(false), $context, + $scope, ); return $first->unionWith($last); diff --git a/src/Type/Doctrine/QueryBuilder/QueryBuilderTypeSpecifyingExtension.php b/src/Type/Doctrine/QueryBuilder/QueryBuilderTypeSpecifyingExtension.php index 83966118..a6ab404d 100644 --- a/src/Type/Doctrine/QueryBuilder/QueryBuilderTypeSpecifyingExtension.php +++ b/src/Type/Doctrine/QueryBuilder/QueryBuilderTypeSpecifyingExtension.php @@ -98,8 +98,8 @@ public function specifyTypes(MethodReflection $methodReflection, MethodCall $nod $queryBuilderNode, TypeCombinator::union(...$resultTypes), TypeSpecifierContext::createTruthy(), - true, - ); + $scope, + )->setAlwaysOverwriteTypes(); } } From d45a6401fb657e43a90965ed5adbf486b630580f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 17:55:23 +0200 Subject: [PATCH 35/71] Uncover everything behind the bleedingEdge flag --- rules.neon | 16 ++-------------- src/Rules/Doctrine/ORM/EntityColumnRule.php | 7 ------- src/Rules/Doctrine/ORM/EntityRelationRule.php | 10 +--------- .../Rules/Doctrine/ORM/EntityColumnRuleTest.php | 1 - .../Doctrine/ORM/EntityRelationRuleTest.php | 1 - 5 files changed, 3 insertions(+), 32 deletions(-) diff --git a/rules.neon b/rules.neon index de338d2a..cf2396b2 100644 --- a/rules.neon +++ b/rules.neon @@ -23,14 +23,10 @@ parametersSchema: rules: - PHPStan\Rules\Doctrine\ORM\DqlRule - PHPStan\Rules\Doctrine\ORM\RepositoryMethodCallRule + - PHPStan\Rules\Doctrine\ORM\EntityConstructorNotFinalRule + - PHPStan\Rules\Doctrine\ORM\EntityMappingExceptionRule - PHPStan\Rules\Doctrine\ORM\EntityNotFinalRule -conditionalTags: - PHPStan\Rules\Doctrine\ORM\EntityMappingExceptionRule: - phpstan.rules.rule: %featureToggles.bleedingEdge% - PHPStan\Rules\Doctrine\ORM\EntityConstructorNotFinalRule: - phpstan.rules.rule: %featureToggles.bleedingEdge% - services: - class: PHPStan\Rules\Doctrine\ORM\QueryBuilderDqlRule @@ -43,23 +39,15 @@ services: arguments: reportUnknownTypes: %doctrine.reportUnknownTypes% allowNullablePropertyForRequiredField: %doctrine.allowNullablePropertyForRequiredField% - bleedingEdge: %featureToggles.bleedingEdge% descriptorRegistry: @doctrineTypeDescriptorRegistry tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Doctrine\ORM\EntityMappingExceptionRule - - - class: PHPStan\Rules\Doctrine\ORM\EntityNotFinalRule - class: PHPStan\Rules\Doctrine\ORM\EntityRelationRule arguments: allowNullablePropertyForRequiredField: %doctrine.allowNullablePropertyForRequiredField% - bleedingEdge: %featureToggles.bleedingEdge% tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Doctrine\ORM\EntityConstructorNotFinalRule - class: PHPStan\Classes\DoctrineProxyForbiddenClassNamesExtension tags: diff --git a/src/Rules/Doctrine/ORM/EntityColumnRule.php b/src/Rules/Doctrine/ORM/EntityColumnRule.php index c2ee7a15..86cde306 100644 --- a/src/Rules/Doctrine/ORM/EntityColumnRule.php +++ b/src/Rules/Doctrine/ORM/EntityColumnRule.php @@ -44,15 +44,12 @@ class EntityColumnRule implements Rule private bool $allowNullablePropertyForRequiredField; - private bool $bleedingEdge; - public function __construct( ObjectMetadataResolver $objectMetadataResolver, DescriptorRegistry $descriptorRegistry, ReflectionProvider $reflectionProvider, bool $reportUnknownTypes, bool $allowNullablePropertyForRequiredField, - bool $bleedingEdge ) { $this->objectMetadataResolver = $objectMetadataResolver; @@ -60,7 +57,6 @@ public function __construct( $this->reflectionProvider = $reflectionProvider; $this->reportUnknownTypes = $reportUnknownTypes; $this->allowNullablePropertyForRequiredField = $allowNullablePropertyForRequiredField; - $this->bleedingEdge = $bleedingEdge; } public function getNodeType(): string @@ -70,9 +66,6 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if (!$this->bleedingEdge && !$this->objectMetadataResolver->hasObjectManagerLoader()) { - return []; - } $class = $scope->getClassReflection(); if ($class === null) { return []; diff --git a/src/Rules/Doctrine/ORM/EntityRelationRule.php b/src/Rules/Doctrine/ORM/EntityRelationRule.php index 97e3ae2f..62f8a22f 100644 --- a/src/Rules/Doctrine/ORM/EntityRelationRule.php +++ b/src/Rules/Doctrine/ORM/EntityRelationRule.php @@ -32,17 +32,13 @@ class EntityRelationRule implements Rule private bool $allowNullablePropertyForRequiredField; - private bool $bleedingEdge; - public function __construct( ObjectMetadataResolver $objectMetadataResolver, - bool $allowNullablePropertyForRequiredField, - bool $bleedingEdge + bool $allowNullablePropertyForRequiredField ) { $this->objectMetadataResolver = $objectMetadataResolver; $this->allowNullablePropertyForRequiredField = $allowNullablePropertyForRequiredField; - $this->bleedingEdge = $bleedingEdge; } public function getNodeType(): string @@ -52,10 +48,6 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if (!$this->bleedingEdge && !$this->objectMetadataResolver->hasObjectManagerLoader()) { - return []; - } - $class = $scope->getClassReflection(); if ($class === null) { return []; diff --git a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php index 091ef9c3..60a05db7 100644 --- a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php @@ -83,7 +83,6 @@ protected function getRule(): Rule $this->createReflectionProvider(), true, $this->allowNullablePropertyForRequiredField, - true, ); } diff --git a/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php b/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php index 5222b65f..7e81545f 100644 --- a/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php @@ -23,7 +23,6 @@ protected function getRule(): Rule return new EntityRelationRule( new ObjectMetadataResolver($this->objectManagerLoader, __DIR__ . '/../../../../tmp'), $this->allowNullablePropertyForRequiredField, - true, ); } From 674c9d44b5d2e385eaa5061615aa8a0ffac6e1fd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 18:00:11 +0200 Subject: [PATCH 36/71] Fixes after PHPStan update --- src/Type/Doctrine/Descriptors/SimpleArrayType.php | 3 ++- src/Type/Doctrine/HydrationModeReturnTypeResolver.php | 4 ++-- tests/Platform/data/config.neon | 3 --- .../Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php | 4 +--- tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php | 2 +- tests/Type/Doctrine/data/QueryResult/config.neon | 2 -- 6 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Type/Doctrine/Descriptors/SimpleArrayType.php b/src/Type/Doctrine/Descriptors/SimpleArrayType.php index 8044caca..2154eb91 100644 --- a/src/Type/Doctrine/Descriptors/SimpleArrayType.php +++ b/src/Type/Doctrine/Descriptors/SimpleArrayType.php @@ -8,6 +8,7 @@ use PHPStan\Type\MixedType; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; class SimpleArrayType implements DoctrineTypeDescriptor { @@ -19,7 +20,7 @@ public function getType(): string public function getWritableToPropertyType(): Type { - return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new StringType())); + return TypeCombinator::intersect(new ArrayType(new IntegerType(), new StringType()), new AccessoryArrayListType()); } public function getWritableToDatabaseType(): Type diff --git a/src/Type/Doctrine/HydrationModeReturnTypeResolver.php b/src/Type/Doctrine/HydrationModeReturnTypeResolver.php index c0522e7f..e978ae2f 100644 --- a/src/Type/Doctrine/HydrationModeReturnTypeResolver.php +++ b/src/Type/Doctrine/HydrationModeReturnTypeResolver.php @@ -77,10 +77,10 @@ public function getMethodReturnTypeForHydrationMode( ); default: if ($queryKeyType->isNull()->yes()) { - return AccessoryArrayListType::intersectWith(new ArrayType( + return TypeCombinator::intersect(new ArrayType( new IntegerType(), $queryResultType, - )); + ), new AccessoryArrayListType()); } return new ArrayType( $queryKeyType, diff --git a/tests/Platform/data/config.neon b/tests/Platform/data/config.neon index 38f26ed7..e6dd2808 100644 --- a/tests/Platform/data/config.neon +++ b/tests/Platform/data/config.neon @@ -1,5 +1,2 @@ includes: - ../../../extension.neon -parameters: - featureToggles: - listType: true diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php index 52e08911..307a867d 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php @@ -116,8 +116,6 @@ public function test(Type $expectedType, string $dql, string $methodName, ?int $ */ public static function getTestData(): iterable { - AccessoryArrayListType::setListTypeEnabled(true); - yield 'getResult(object), full entity' => [ self::list(new ObjectType(Simple::class)), ' @@ -305,7 +303,7 @@ private static function constantArray(array $elements): Type private static function list(Type $values): Type { - return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $values)); + return TypeCombinator::intersect(new ArrayType(new IntegerType(), $values), new AccessoryArrayListType()); } private static function numericString(): Type diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php index 33fa5bbd..427212a4 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php @@ -1497,7 +1497,7 @@ private function yieldConditionalDataset(): iterable $this->constantArray([ [new ConstantStringType('stringEnumColumn'), new ObjectType(StringEnum::class)], [new ConstantStringType('intEnumColumn'), new ObjectType(IntEnum::class)], - [new ConstantStringType('stringEnumListColumn'), AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new ObjectType(StringEnum::class)))], + [new ConstantStringType('stringEnumListColumn'), TypeCombinator::intersect(new ArrayType(new IntegerType(), new ObjectType(StringEnum::class)), new AccessoryArrayListType())], ]), ' SELECT e.stringEnumColumn, e.intEnumColumn, e.stringEnumListColumn diff --git a/tests/Type/Doctrine/data/QueryResult/config.neon b/tests/Type/Doctrine/data/QueryResult/config.neon index 5ce3210a..147e4f94 100644 --- a/tests/Type/Doctrine/data/QueryResult/config.neon +++ b/tests/Type/Doctrine/data/QueryResult/config.neon @@ -3,5 +3,3 @@ includes: parameters: doctrine: objectManagerLoader: entity-manager.php - featureToggles: - listType: true From a110d8a13ace9c5fcdfd63cc3c6e081ab1cc4da4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 20:51:55 +0200 Subject: [PATCH 37/71] Fix --- src/Rules/Doctrine/ORM/EntityColumnRule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/Doctrine/ORM/EntityColumnRule.php b/src/Rules/Doctrine/ORM/EntityColumnRule.php index 86cde306..06b9343f 100644 --- a/src/Rules/Doctrine/ORM/EntityColumnRule.php +++ b/src/Rules/Doctrine/ORM/EntityColumnRule.php @@ -49,7 +49,7 @@ public function __construct( DescriptorRegistry $descriptorRegistry, ReflectionProvider $reflectionProvider, bool $reportUnknownTypes, - bool $allowNullablePropertyForRequiredField, + bool $allowNullablePropertyForRequiredField ) { $this->objectMetadataResolver = $objectMetadataResolver; From c17a736f8c5d4c0d7ac2179e109541a9f4a7ba2d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 14:53:22 +0200 Subject: [PATCH 38/71] Fixes after PHPStan updates --- .../QueryBuilder/QueryBuilderExecuteMethodExtension.php | 6 +++++- .../DBAL/RowCountMethodDynamicReturnTypeExtension.php | 3 +-- .../Query/QueryResultDynamicReturnTypeExtension.php | 6 ++++-- src/Type/Doctrine/Query/QueryResultTypeWalker.php | 2 +- .../QueryBuilderMethodDynamicReturnTypeExtension.php | 5 +---- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Type/Doctrine/DBAL/QueryBuilder/QueryBuilderExecuteMethodExtension.php b/src/Type/Doctrine/DBAL/QueryBuilder/QueryBuilderExecuteMethodExtension.php index a997c2da..ad30e430 100644 --- a/src/Type/Doctrine/DBAL/QueryBuilder/QueryBuilderExecuteMethodExtension.php +++ b/src/Type/Doctrine/DBAL/QueryBuilder/QueryBuilderExecuteMethodExtension.php @@ -38,7 +38,11 @@ public function isMethodSupported(MethodReflection $methodReflection): bool public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { - $defaultReturnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + $defaultReturnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants() + )->getReturnType(); $queryBuilderType = new ObjectType(QueryBuilder::class); $var = $methodCall->var; diff --git a/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php b/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php index 03a988e1..60c17e79 100644 --- a/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php @@ -8,7 +8,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Doctrine\Driver\DriverDetector; use PHPStan\Reflection\MethodReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Doctrine\ObjectMetadataResolver; use PHPStan\Type\DynamicMethodReturnTypeExtension; @@ -76,7 +75,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method } $rowCountMethod = $resultReflection->getNativeMethod('rowCount'); - $variant = ParametersAcceptorSelector::selectSingle($rowCountMethod->getVariants()); + $variant = $rowCountMethod->getOnlyVariant(); return $variant->getReturnType(); } diff --git a/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php b/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php index 24aecb38..dda9e637 100644 --- a/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php @@ -68,8 +68,10 @@ public function getTypeFromMethodCall( if (isset($args[$argIndex])) { $hydrationMode = $scope->getType($args[$argIndex]->value); } else { - $parametersAcceptor = ParametersAcceptorSelector::selectSingle( - $methodReflection->getVariants(), + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants() ); $parameter = $parametersAcceptor->getParameters()[$argIndex]; $hydrationMode = $parameter->getDefaultValue() ?? new NullType(); diff --git a/src/Type/Doctrine/Query/QueryResultTypeWalker.php b/src/Type/Doctrine/Query/QueryResultTypeWalker.php index 4b96044d..e508d925 100644 --- a/src/Type/Doctrine/Query/QueryResultTypeWalker.php +++ b/src/Type/Doctrine/Query/QueryResultTypeWalker.php @@ -609,7 +609,7 @@ public function walkFunction($function): string if ($this->driverType === DriverDetector::MYSQLI || $this->driverType === DriverDetector::PDO_MYSQL || $this->driverType === DriverDetector::SQLITE3 || $this->driverType === DriverDetector::PDO_SQLITE) { $type = new FloatType(); - $cannotBeNegative = $exprType->isSmallerThan(new ConstantIntegerType(0))->no(); + $cannotBeNegative = $exprType->isSmallerThan(new ConstantIntegerType(0), $this->phpVersion)->no(); $canBeNegative = !$cannotBeNegative; if ($canBeNegative) { $type = TypeCombinator::addNull($type); diff --git a/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php index 4e9ea601..67003dc6 100644 --- a/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php @@ -7,7 +7,6 @@ use PhpParser\Node\Identifier; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Doctrine\DoctrineTypeUtils; use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\MixedType; @@ -39,9 +38,7 @@ public function getClass(): string public function isMethodSupported(MethodReflection $methodReflection): bool { - $returnType = ParametersAcceptorSelector::selectSingle( - $methodReflection->getVariants(), - )->getReturnType(); + $returnType = $methodReflection->getVariants()[0]->getReturnType(); if ($returnType instanceof MixedType) { return false; } From c5856104344ad18ad5e405ada91d384be5ca0e26 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 16:58:44 +0200 Subject: [PATCH 39/71] [BCB] Remove options that existed only for performance reasons --- extension.neon | 6 - rules.neon | 2 - ...QueryBuilderDynamicReturnTypeExtension.php | 13 +- .../OtherMethodQueryBuilderParser.php | 9 +- .../QueryBuilder/SimpleQueryBuilderType.php | 33 ---- .../ORM/QueryBuilderDqlRuleSlowTest.php | 147 ------------------ tests/Rules/Doctrine/ORM/slow.neon | 3 - 7 files changed, 3 insertions(+), 210 deletions(-) delete mode 100644 src/Type/Doctrine/QueryBuilder/SimpleQueryBuilderType.php delete mode 100644 tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php delete mode 100644 tests/Rules/Doctrine/ORM/slow.neon diff --git a/extension.neon b/extension.neon index 63365d37..b8f68a86 100644 --- a/extension.neon +++ b/extension.neon @@ -9,8 +9,6 @@ parameters: queryBuilderClass: null allCollectionsSelectable: true objectManagerLoader: null - searchOtherMethodsForQueryBuilderBeginning: true - queryBuilderFastAlgorithm: false literalString: false featureToggles: skipCheckGenericClasses: @@ -79,8 +77,6 @@ parametersSchema: queryBuilderClass: schema(string(), nullable()) allCollectionsSelectable: bool() objectManagerLoader: schema(string(), nullable()) - searchOtherMethodsForQueryBuilderBeginning: bool() - queryBuilderFastAlgorithm: bool() reportDynamicQueryBuilders: bool() reportUnknownTypes: bool() allowNullablePropertyForRequiredField: bool() @@ -117,7 +113,6 @@ services: class: PHPStan\Type\Doctrine\QueryBuilder\CreateQueryBuilderDynamicReturnTypeExtension arguments: queryBuilderClass: %doctrine.queryBuilderClass% - fasterVersion: %doctrine.queryBuilderFastAlgorithm% tags: - phpstan.broker.dynamicMethodReturnTypeExtension - @@ -194,7 +189,6 @@ services: - class: PHPStan\Type\Doctrine\QueryBuilder\OtherMethodQueryBuilderParser arguments: - descendIntoOtherMethods: %doctrine.searchOtherMethodsForQueryBuilderBeginning% parser: @defaultAnalysisParser - diff --git a/rules.neon b/rules.neon index cf2396b2..0853184d 100644 --- a/rules.neon +++ b/rules.neon @@ -12,8 +12,6 @@ parametersSchema: queryBuilderClass: schema(string(), nullable()) allCollectionsSelectable: bool() objectManagerLoader: schema(string(), nullable()) - searchOtherMethodsForQueryBuilderBeginning: bool() - queryBuilderFastAlgorithm: bool() reportDynamicQueryBuilders: bool() reportUnknownTypes: bool() allowNullablePropertyForRequiredField: bool() diff --git a/src/Type/Doctrine/QueryBuilder/CreateQueryBuilderDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/CreateQueryBuilderDynamicReturnTypeExtension.php index 8389395a..3fe7e9b1 100644 --- a/src/Type/Doctrine/QueryBuilder/CreateQueryBuilderDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/CreateQueryBuilderDynamicReturnTypeExtension.php @@ -13,15 +13,11 @@ class CreateQueryBuilderDynamicReturnTypeExtension implements DynamicMethodRetur private ?string $queryBuilderClass = null; - private bool $fasterVersion; - public function __construct( - ?string $queryBuilderClass, - bool $fasterVersion + ?string $queryBuilderClass ) { $this->queryBuilderClass = $queryBuilderClass; - $this->fasterVersion = $fasterVersion; } public function getClass(): string @@ -40,12 +36,7 @@ public function getTypeFromMethodCall( Scope $scope ): Type { - $class = SimpleQueryBuilderType::class; - if (!$this->fasterVersion) { - $class = BranchingQueryBuilderType::class; - } - - return new $class( + return new BranchingQueryBuilderType( $this->queryBuilderClass ?? 'Doctrine\ORM\QueryBuilder', ); } diff --git a/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php b/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php index cddd3a93..153291f5 100644 --- a/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php +++ b/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php @@ -28,8 +28,6 @@ class OtherMethodQueryBuilderParser { - private bool $descendIntoOtherMethods; - private Parser $parser; private Container $container; @@ -41,9 +39,8 @@ class OtherMethodQueryBuilderParser */ private array $cache = []; - public function __construct(bool $descendIntoOtherMethods, Parser $parser, Container $container) + public function __construct(Parser $parser, Container $container) { - $this->descendIntoOtherMethods = $descendIntoOtherMethods; $this->parser = $parser; $this->container = $container; } @@ -53,10 +50,6 @@ public function __construct(bool $descendIntoOtherMethods, Parser $parser, Conta */ public function findQueryBuilderTypesInCalledMethod(Scope $scope, MethodReflection $methodReflection): array { - if (!$this->descendIntoOtherMethods) { - return []; - } - $methodName = $methodReflection->getName(); $className = $methodReflection->getDeclaringClass()->getName(); $fileName = $methodReflection->getDeclaringClass()->getFileName(); diff --git a/src/Type/Doctrine/QueryBuilder/SimpleQueryBuilderType.php b/src/Type/Doctrine/QueryBuilder/SimpleQueryBuilderType.php deleted file mode 100644 index 98660de8..00000000 --- a/src/Type/Doctrine/QueryBuilder/SimpleQueryBuilderType.php +++ /dev/null @@ -1,33 +0,0 @@ -getMethodCalls()) === count($type->getMethodCalls()); - } - - return parent::equals($type); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - if ($type instanceof parent) { - $thisCount = count($this->getMethodCalls()); - $thatCount = count($type->getMethodCalls()); - - return TrinaryLogic::createFromBoolean($thisCount === $thatCount); - } - - return parent::isSuperTypeOf($type); - } - -} diff --git a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php deleted file mode 100644 index 2c4e03f7..00000000 --- a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php +++ /dev/null @@ -1,147 +0,0 @@ - - */ -class QueryBuilderDqlRuleSlowTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new QueryBuilderDqlRule( - new ObjectMetadataResolver(__DIR__ . '/entity-manager.php', __DIR__ . '/../../../../tmp'), - true, - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/query-builder-dql.php'], [ - [ - "QueryBuilder: [Syntax Error] line 0, col 66: Error: Expected end of string, got ')'\nDQL: SELECT e FROM PHPStan\Rules\Doctrine\ORM\MyEntity e WHERE e.id = 1)", - 31, - ], - [ - "QueryBuilder: [Syntax Error] line 0, col 68: Error: Expected end of string, got ')'\nDQL: SELECT e FROM PHPStan\Rules\Doctrine\ORM\MyEntity e WHERE e.id = :id)", - 43, - ], - [ - "QueryBuilder: [Syntax Error] line 0, col 68: Error: Expected end of string, got ')'\nDQL: SELECT e FROM PHPStan\Rules\Doctrine\ORM\MyEntity e WHERE e.id = :id)", - 55, - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 60 near \'transient = \': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named transient', - 62, - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 14 near \'Foo e\': Error: Class \'Foo\' is not defined.', - 71, - ], - [ - 'Could not analyse QueryBuilder with dynamic arguments.', - 99, - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 60 near \'transient = \': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named transient', - 107, - ], - [ - "QueryBuilder: [Syntax Error] line 0, col 82: Error: Expected end of string, got ')'\nDQL: SELECT e FROM PHPStan\Rules\Doctrine\ORM\MyEntity e WHERE e.id = 1 ORDER BY e.name) ASC", - 129, - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 78 near \'name ASC\': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named name', - 139, - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 78 near \'name ASC\': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named name', - 160, - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 60 near \'transient = 1\': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named transient', - 170, - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 72 near \'nickname LIKE\': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named nickname', - 194, - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 72 near \'nickname IS \': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named nickname', - 206, - ], - [ - "QueryBuilder: [Syntax Error] line 0, col 80: Error: Expected =, <, <=, <>, >, >=, !=, got ')'\nDQL: SELECT e FROM PHPStan\Rules\Doctrine\ORM\MyEntity e WHERE e.id = 1 OR e.nickname) IS NULL", - 218, - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 60 near \'transient = \': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named transient', - 234, - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 60 near \'nonexistent =\': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named nonexistent', - 251, - ], - [ - "QueryBuilder: [Syntax Error] line 0, col -1: Error: Expected =, <, <=, <>, >, >=, !=, got end of string.\nDQL: SELECT e FROM PHPStan\Rules\Doctrine\ORM\MyEntity e WHERE foo", - 281, - ], - ]); - } - - public function testRuleBranches(): void - { - $errors = [ - [ - 'QueryBuilder: [Semantical Error] line 0, col 58 near \'p.id = 1\': Error: \'p\' is not defined.', - 31, - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 58 near \'p.id = 1\': Error: \'p\' is not defined.', - 45, - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 58 near \'p.id = 1\': Error: \'p\' is not defined.', - 59, - 'Detected from DQL branch: SELECT e FROM PHPStan\Rules\Doctrine\ORM\MyEntity e WHERE p.id = 1', - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 93 near \'t.id = 1\': Error: \'t\' is not defined.', - 90, - 'Detected from DQL branch: SELECT e FROM PHPStan\Rules\Doctrine\ORM\MyEntity e INNER JOIN e.parent p WHERE p.id = 1 AND t.id = 1', - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 95 near \'foo = 1\': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named foo', - 107, - 'Detected from DQL branch: SELECT e FROM PHPStan\Rules\Doctrine\ORM\MyEntity e INNER JOIN e.parent p WHERE p.id = 1 AND e.foo = 1', - ], - [ - 'QueryBuilder: [Semantical Error] line 0, col 93 near \'t.id = 1\': Error: \'t\' is not defined.', - 107, - 'Detected from DQL branch: SELECT e FROM PHPStan\Rules\Doctrine\ORM\MyEntity e INNER JOIN e.parent p WHERE p.id = 1 AND t.id = 1', - ], - ]; - $this->analyse([__DIR__ . '/data/query-builder-branches-dql.php'], $errors); - } - - public static function getAdditionalConfigFiles(): array - { - return [ - __DIR__ . '/../../../../extension.neon', - __DIR__ . '/entity-manager.neon', - __DIR__ . '/slow.neon', - ]; - } - - protected function shouldFailOnPhpErrors(): bool - { - // doctrine/orm/src/Query/Parser.php throws assert($peek !== null) failed - return false; - } - -} diff --git a/tests/Rules/Doctrine/ORM/slow.neon b/tests/Rules/Doctrine/ORM/slow.neon deleted file mode 100644 index bfda5b8a..00000000 --- a/tests/Rules/Doctrine/ORM/slow.neon +++ /dev/null @@ -1,3 +0,0 @@ -parameters: - doctrine: - queryBuilderFastAlgorithm: false From 493457ac434afdbfa5604f01f98261cdd4e45d6b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 17:01:10 +0200 Subject: [PATCH 40/71] Fix CS --- .../DBAL/QueryBuilder/QueryBuilderExecuteMethodExtension.php | 2 +- .../Doctrine/Query/QueryResultDynamicReturnTypeExtension.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Type/Doctrine/DBAL/QueryBuilder/QueryBuilderExecuteMethodExtension.php b/src/Type/Doctrine/DBAL/QueryBuilder/QueryBuilderExecuteMethodExtension.php index ad30e430..0a644f3a 100644 --- a/src/Type/Doctrine/DBAL/QueryBuilder/QueryBuilderExecuteMethodExtension.php +++ b/src/Type/Doctrine/DBAL/QueryBuilder/QueryBuilderExecuteMethodExtension.php @@ -41,7 +41,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method $defaultReturnType = ParametersAcceptorSelector::selectFromArgs( $scope, $methodCall->getArgs(), - $methodReflection->getVariants() + $methodReflection->getVariants(), )->getReturnType(); $queryBuilderType = new ObjectType(QueryBuilder::class); diff --git a/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php b/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php index dda9e637..22ed0b5a 100644 --- a/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php @@ -71,7 +71,7 @@ public function getTypeFromMethodCall( $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( $scope, $methodCall->getArgs(), - $methodReflection->getVariants() + $methodReflection->getVariants(), ); $parameter = $parametersAcceptor->getParameters()[$argIndex]; $hydrationMode = $parameter->getDefaultValue() ?? new NullType(); From 261b19d3a59d02caa4f7d1da8c78976d01a20913 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 17:03:34 +0200 Subject: [PATCH 41/71] Fixes after PHPStan update --- src/Rules/Doctrine/ORM/DqlRule.php | 3 +-- src/Rules/Doctrine/ORM/QueryBuilderDqlRule.php | 3 +-- src/Type/Doctrine/ArgumentsProcessor.php | 2 +- .../Doctrine/GetRepositoryDynamicReturnTypeExtension.php | 2 +- ...sitoryCreateQueryBuilderDynamicReturnTypeExtension.php | 2 +- tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php | 8 ++++---- 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Rules/Doctrine/ORM/DqlRule.php b/src/Rules/Doctrine/ORM/DqlRule.php index 77cd3664..23f4da41 100644 --- a/src/Rules/Doctrine/ORM/DqlRule.php +++ b/src/Rules/Doctrine/ORM/DqlRule.php @@ -11,7 +11,6 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Doctrine\ObjectMetadataResolver; use PHPStan\Type\ObjectType; -use PHPStan\Type\TypeUtils; use function count; use function sprintf; @@ -54,7 +53,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - $dqls = TypeUtils::getConstantStrings($scope->getType($node->getArgs()[0]->value)); + $dqls = $scope->getType($node->getArgs()[0]->value)->getConstantStrings(); if (count($dqls) === 0) { return []; } diff --git a/src/Rules/Doctrine/ORM/QueryBuilderDqlRule.php b/src/Rules/Doctrine/ORM/QueryBuilderDqlRule.php index 62155d01..69f9791a 100644 --- a/src/Rules/Doctrine/ORM/QueryBuilderDqlRule.php +++ b/src/Rules/Doctrine/ORM/QueryBuilderDqlRule.php @@ -13,7 +13,6 @@ use PHPStan\Type\Doctrine\DoctrineTypeUtils; use PHPStan\Type\Doctrine\ObjectMetadataResolver; use PHPStan\Type\ObjectType; -use PHPStan\Type\TypeUtils; use Throwable; use function array_values; use function count; @@ -81,7 +80,7 @@ public function processNode(Node $node, Scope $scope): array ]; } - $dqls = TypeUtils::getConstantStrings($dqlType); + $dqls = $dqlType->getConstantStrings(); if (count($dqls) === 0) { if ($this->reportDynamicQueryBuilders) { return [ diff --git a/src/Type/Doctrine/ArgumentsProcessor.php b/src/Type/Doctrine/ArgumentsProcessor.php index 77eeb5d7..2cd94fd7 100644 --- a/src/Type/Doctrine/ArgumentsProcessor.php +++ b/src/Type/Doctrine/ArgumentsProcessor.php @@ -58,7 +58,7 @@ public function processArgs( continue; } - if ($value->isClassStringType()->yes() && count($value->getClassStringObjectType()->getObjectClassNames()) === 1) { + if ($value->isClassString()->yes() && count($value->getClassStringObjectType()->getObjectClassNames()) === 1) { /** @var class-string $className */ $className = $value->getClassStringObjectType()->getObjectClassNames()[0]; if ($this->objectMetadataResolver->isTransient($className)) { diff --git a/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php b/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php index d514a12b..8daaece8 100644 --- a/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php @@ -85,7 +85,7 @@ public function getTypeFromMethodCall( ); } $argType = $scope->getType($methodCall->getArgs()[0]->value); - if (!$argType->isClassStringType()->yes()) { + if (!$argType->isClassString()->yes()) { return $this->getDefaultReturnType($scope, $methodCall->getArgs(), $methodReflection, $defaultRepositoryClass); } diff --git a/src/Type/Doctrine/QueryBuilder/EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension.php index f170b550..44201e45 100644 --- a/src/Type/Doctrine/QueryBuilder/EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension.php @@ -35,7 +35,7 @@ public function getTypeFromMethodCall( $entityNameExpr = new MethodCall($methodCall->var, new Identifier('getEntityName')); $entityNameExprType = $scope->getType($entityNameExpr); - if ($entityNameExprType->isClassStringType()->yes() && count($entityNameExprType->getClassStringObjectType()->getObjectClassNames()) === 1) { + if ($entityNameExprType->isClassString()->yes() && count($entityNameExprType->getClassStringObjectType()->getObjectClassNames()) === 1) { $entityNameExpr = new String_($entityNameExprType->getClassStringObjectType()->getObjectClassNames()[0]); } diff --git a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php index 60a05db7..d03da719 100644 --- a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php @@ -75,10 +75,10 @@ protected function getRule(): Rule new StringType(), new SimpleArrayType(), new UuidTypeDescriptor(FakeTestingUuidType::class), - new ReflectionDescriptor(CarbonImmutableType::class, $this->createBroker(), self::getContainer()), - new ReflectionDescriptor(CarbonType::class, $this->createBroker(), self::getContainer()), - new ReflectionDescriptor(CustomType::class, $this->createBroker(), self::getContainer()), - new ReflectionDescriptor(CustomNumericType::class, $this->createBroker(), self::getContainer()), + new ReflectionDescriptor(CarbonImmutableType::class, $this->createReflectionProvider(), self::getContainer()), + new ReflectionDescriptor(CarbonType::class, $this->createReflectionProvider(), self::getContainer()), + new ReflectionDescriptor(CustomType::class, $this->createReflectionProvider(), self::getContainer()), + new ReflectionDescriptor(CustomNumericType::class, $this->createReflectionProvider(), self::getContainer()), ]), $this->createReflectionProvider(), true, From 4f513337c20586277cdc76871eb482b614396bf2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 4 Oct 2024 13:36:14 +0200 Subject: [PATCH 42/71] Fixes after PHPStan update --- src/Rules/Doctrine/ORM/EntityColumnRule.php | 3 +-- src/Rules/Doctrine/ORM/EntityRelationRule.php | 3 +-- .../DBAL/RowCountMethodDynamicReturnTypeExtension.php | 4 ++++ .../Doctrine/GetRepositoryDynamicReturnTypeExtension.php | 4 ++++ .../QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php | 4 ++++ .../QueryBuilderGetDqlDynamicReturnTypeExtension.php | 4 ++++ .../QueryBuilderGetQueryDynamicReturnTypeExtension.php | 4 ++++ .../QueryBuilderMethodDynamicReturnTypeExtension.php | 4 ++++ .../QueryBuilder/QueryBuilderTypeSpecifyingExtension.php | 4 ++++ stubs/MongoClassMetadataInfo.stub | 5 ----- .../ORM/EntityRepositoryDynamicReturnIntegrationTest.php | 2 +- ...ithoutObjectManagerLoaderDynamicReturnIntegrationTest.php | 2 +- tests/Rules/Doctrine/ORM/FakeTestingUuidType.php | 1 + 13 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/Rules/Doctrine/ORM/EntityColumnRule.php b/src/Rules/Doctrine/ORM/EntityColumnRule.php index 06b9343f..b5f42bb6 100644 --- a/src/Rules/Doctrine/ORM/EntityColumnRule.php +++ b/src/Rules/Doctrine/ORM/EntityColumnRule.php @@ -16,7 +16,6 @@ use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\ObjectType; -use PHPStan\Type\ParserNodeTypeToPHPStanType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypehintHelper; @@ -176,7 +175,7 @@ public function processNode(Node $node, Scope $scope): array } $phpDocType = $node->getPhpDocType(); - $nativeType = $node->getNativeType() !== null ? ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection()) : new MixedType(); + $nativeType = $node->getNativeType() ?? new MixedType(); $propertyType = TypehintHelper::decideType($nativeType, $phpDocType); if (get_class($propertyType) === MixedType::class || $propertyType instanceof ErrorType || $propertyType instanceof NeverType) { diff --git a/src/Rules/Doctrine/ORM/EntityRelationRule.php b/src/Rules/Doctrine/ORM/EntityRelationRule.php index 62f8a22f..84ce9120 100644 --- a/src/Rules/Doctrine/ORM/EntityRelationRule.php +++ b/src/Rules/Doctrine/ORM/EntityRelationRule.php @@ -13,7 +13,6 @@ use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\ObjectType; -use PHPStan\Type\ParserNodeTypeToPHPStanType; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypehintHelper; use PHPStan\Type\VerbosityLevel; @@ -96,7 +95,7 @@ public function processNode(Node $node, Scope $scope): array } $phpDocType = $node->getPhpDocType(); - $nativeType = $node->getNativeType() !== null ? ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection()) : new MixedType(); + $nativeType = $node->getNativeType() ?? new MixedType(); $propertyType = TypehintHelper::decideType($nativeType, $phpDocType); $errors = []; diff --git a/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php b/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php index 60c17e79..7781fc9f 100644 --- a/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php @@ -16,6 +16,7 @@ class RowCountMethodDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { + /** @var class-string */ private string $class; private ObjectMetadataResolver $objectMetadataResolver; @@ -24,6 +25,9 @@ class RowCountMethodDynamicReturnTypeExtension implements DynamicMethodReturnTyp private ReflectionProvider $reflectionProvider; + /** + * @param class-string $class + */ public function __construct( string $class, ObjectMetadataResolver $objectMetadataResolver, diff --git a/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php b/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php index 8daaece8..07d40ffa 100644 --- a/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php @@ -35,10 +35,14 @@ class GetRepositoryDynamicReturnTypeExtension implements DynamicMethodReturnType private ?string $odmRepositoryClass = null; + /** @var class-string */ private string $managerClass; private ObjectMetadataResolver $metadataResolver; + /** + * @param class-string $managerClass + */ public function __construct( ReflectionProvider $reflectionProvider, ?string $repositoryClass, diff --git a/src/Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php index 6f9a69c8..79206e1c 100644 --- a/src/Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php @@ -20,10 +20,14 @@ class NewExprDynamicReturnTypeExtension implements DynamicStaticMethodReturnType private ArgumentsProcessor $argumentsProcessor; + /** @var class-string */ private string $class; private ReflectionProvider $reflectionProvider; + /** + * @param class-string $class + */ public function __construct( ArgumentsProcessor $argumentsProcessor, string $class, diff --git a/src/Type/Doctrine/QueryBuilder/QueryBuilderGetDqlDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/QueryBuilderGetDqlDynamicReturnTypeExtension.php index c6f4a5a7..69ceb824 100644 --- a/src/Type/Doctrine/QueryBuilder/QueryBuilderGetDqlDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/QueryBuilderGetDqlDynamicReturnTypeExtension.php @@ -13,8 +13,12 @@ class QueryBuilderGetDqlDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { + /** @var class-string|null */ private ?string $queryBuilderClass = null; + /** + * @param class-string|null $queryBuilderClass + */ public function __construct( ?string $queryBuilderClass ) diff --git a/src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php index b02018be..98fdefb3 100644 --- a/src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php @@ -59,6 +59,7 @@ class QueryBuilderGetQueryDynamicReturnTypeExtension implements DynamicMethodRet private ArgumentsProcessor $argumentsProcessor; + /** @var class-string|null */ private ?string $queryBuilderClass = null; private DescriptorRegistry $descriptorRegistry; @@ -67,6 +68,9 @@ class QueryBuilderGetQueryDynamicReturnTypeExtension implements DynamicMethodRet private DriverDetector $driverDetector; + /** + * @param class-string|null $queryBuilderClass + */ public function __construct( ObjectMetadataResolver $objectMetadataResolver, ArgumentsProcessor $argumentsProcessor, diff --git a/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php index 67003dc6..0c6d9266 100644 --- a/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php @@ -22,8 +22,12 @@ class QueryBuilderMethodDynamicReturnTypeExtension implements DynamicMethodRetur private const MAX_COMBINATIONS = 16; + /** @var class-string|null */ private ?string $queryBuilderClass = null; + /** + * @param class-string|null $queryBuilderClass + */ public function __construct( ?string $queryBuilderClass ) diff --git a/src/Type/Doctrine/QueryBuilder/QueryBuilderTypeSpecifyingExtension.php b/src/Type/Doctrine/QueryBuilder/QueryBuilderTypeSpecifyingExtension.php index a6ab404d..66b18f71 100644 --- a/src/Type/Doctrine/QueryBuilder/QueryBuilderTypeSpecifyingExtension.php +++ b/src/Type/Doctrine/QueryBuilder/QueryBuilderTypeSpecifyingExtension.php @@ -24,10 +24,14 @@ class QueryBuilderTypeSpecifyingExtension implements MethodTypeSpecifyingExtensi private const MAX_COMBINATIONS = 16; + /** @var class-string|null */ private ?string $queryBuilderClass = null; private TypeSpecifier $typeSpecifier; + /** + * @param class-string|null $queryBuilderClass + */ public function __construct(?string $queryBuilderClass) { $this->queryBuilderClass = $queryBuilderClass; diff --git a/stubs/MongoClassMetadataInfo.stub b/stubs/MongoClassMetadataInfo.stub index cfdba938..433ef2a4 100644 --- a/stubs/MongoClassMetadataInfo.stub +++ b/stubs/MongoClassMetadataInfo.stub @@ -15,11 +15,6 @@ class ClassMetadata implements BaseClassMetadata /** @var string|null */ public $customRepositoryClassName; - /** - * @var class-string - */ - public $name; - /** * @param class-string $documentName */ diff --git a/tests/DoctrineIntegration/ORM/EntityRepositoryDynamicReturnIntegrationTest.php b/tests/DoctrineIntegration/ORM/EntityRepositoryDynamicReturnIntegrationTest.php index e64f096d..de7910fc 100644 --- a/tests/DoctrineIntegration/ORM/EntityRepositoryDynamicReturnIntegrationTest.php +++ b/tests/DoctrineIntegration/ORM/EntityRepositoryDynamicReturnIntegrationTest.php @@ -10,7 +10,7 @@ final class EntityRepositoryDynamicReturnIntegrationTest extends LevelsTestCase /** * @return string[][] */ - public function dataTopics(): array + public static function dataTopics(): array { return [ ['entityRepositoryDynamicReturn'], diff --git a/tests/DoctrineIntegration/ORM/EntityRepositoryWithoutObjectManagerLoaderDynamicReturnIntegrationTest.php b/tests/DoctrineIntegration/ORM/EntityRepositoryWithoutObjectManagerLoaderDynamicReturnIntegrationTest.php index bb2d04c1..ea596a6e 100644 --- a/tests/DoctrineIntegration/ORM/EntityRepositoryWithoutObjectManagerLoaderDynamicReturnIntegrationTest.php +++ b/tests/DoctrineIntegration/ORM/EntityRepositoryWithoutObjectManagerLoaderDynamicReturnIntegrationTest.php @@ -10,7 +10,7 @@ final class EntityRepositoryWithoutObjectManagerLoaderDynamicReturnIntegrationTe /** * @return string[][] */ - public function dataTopics(): array + public static function dataTopics(): array { return [ ['entityRepositoryDynamicReturn'], diff --git a/tests/Rules/Doctrine/ORM/FakeTestingUuidType.php b/tests/Rules/Doctrine/ORM/FakeTestingUuidType.php index fcb28f7e..f6255563 100644 --- a/tests/Rules/Doctrine/ORM/FakeTestingUuidType.php +++ b/tests/Rules/Doctrine/ORM/FakeTestingUuidType.php @@ -47,6 +47,7 @@ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?str return null; } + /** @throws ConversionException */ return (string) $value; } From 14a59bb5b719d3740224b6ba8aec80c0294142f9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 7 Oct 2024 09:02:25 +0200 Subject: [PATCH 43/71] Fix stub --- stubs/MongoClassMetadataInfo.stub | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/stubs/MongoClassMetadataInfo.stub b/stubs/MongoClassMetadataInfo.stub index 433ef2a4..65cfa40f 100644 --- a/stubs/MongoClassMetadataInfo.stub +++ b/stubs/MongoClassMetadataInfo.stub @@ -15,6 +15,12 @@ class ClassMetadata implements BaseClassMetadata /** @var string|null */ public $customRepositoryClassName; + /** + * @readonly + * @var class-string + */ + public $name; + /** * @param class-string $documentName */ From bcb88b6f1690052fcc45e6bfbc65921fe4385457 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 7 Oct 2024 09:10:55 +0200 Subject: [PATCH 44/71] Fix tests after PHPStan update --- tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php index d03da719..f0a799e6 100644 --- a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php @@ -159,7 +159,7 @@ public function testRule(?string $objectManagerLoader): void 156, ], [ - 'Property PHPStan\Rules\Doctrine\ORM\MyBrokenEntity::$invalidSimpleArray type mapping mismatch: database can contain array but property expects array.', + 'Property PHPStan\Rules\Doctrine\ORM\MyBrokenEntity::$invalidSimpleArray type mapping mismatch: database can contain list but property expects array.', 162, ], [ @@ -230,7 +230,7 @@ public function testRuleWithAllowedNullableProperty(?string $objectManagerLoader 156, ], [ - 'Property PHPStan\Rules\Doctrine\ORM\MyBrokenEntity::$invalidSimpleArray type mapping mismatch: database can contain array but property expects array.', + 'Property PHPStan\Rules\Doctrine\ORM\MyBrokenEntity::$invalidSimpleArray type mapping mismatch: database can contain list but property expects array.', 162, ], [ @@ -399,7 +399,7 @@ public function testEnumType(?string $objectManagerLoader): void 45, ], [ - 'Property PHPStan\Rules\Doctrine\ORMAttributes\Foo::$type5 type mapping mismatch: database can contain array but property expects PHPStan\Rules\Doctrine\ORMAttributes\FooEnum.', + 'Property PHPStan\Rules\Doctrine\ORMAttributes\Foo::$type5 type mapping mismatch: database can contain list but property expects PHPStan\Rules\Doctrine\ORMAttributes\FooEnum.', 51, ], [ From de811c781e27b16b37407577f685beff3e53a057 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 7 Oct 2024 10:57:10 +0200 Subject: [PATCH 45/71] Fix tests for PHPStan 1.12.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Mirtes Co-authored-by: Markus Staab --- composer.json | 2 +- .../QueryResultTypeWalkerHydrationModeTest.php | 12 +++++++++--- .../Query/QueryResultTypeWalkerTest.php | 18 ++++++++++++------ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 94c15b34..3d2655d7 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.12" + "phpstan/phpstan": "^1.12.6" }, "conflict": { "doctrine/collections": "<1.0", diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php index b9d02c85..be915cad 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php @@ -11,6 +11,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; @@ -308,12 +309,17 @@ private static function list(Type $values): Type return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $values)); } - private static function numericString(): Type + private static function numericString(bool $lowercase = false): Type { - return new IntersectionType([ + $types = [ new StringType(), new AccessoryNumericStringType(), - ]); + ]; + if ($lowercase) { + $types[] = new AccessoryLowercaseStringType(); + } + + return new IntersectionType($types); } /** diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php index fd6fee74..905c5221 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php @@ -15,6 +15,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; @@ -1474,7 +1475,7 @@ public function getTestData(): iterable $this->constantArray([ [new ConstantStringType('minusInt'), $this->stringifies() ? new ConstantStringType('-1') : new ConstantIntegerType(-1)], [new ConstantStringType('minusFloat'), $this->stringifies() ? $this->numericString() : new ConstantFloatType(-0.1)], - [new ConstantStringType('minusIntRange'), $this->stringifies() ? $this->numericString() : IntegerRangeType::fromInterval(null, 0)], + [new ConstantStringType('minusIntRange'), $this->stringifies() ? $this->numericString(true) : IntegerRangeType::fromInterval(null, 0)], ]), ' SELECT -1 as minusInt, @@ -1622,12 +1623,17 @@ private function constantArray(array $elements): Type return $builder->getArray(); } - private function numericString(): Type + private function numericString(bool $lowercase = false): Type { - return new IntersectionType([ + $types = [ new StringType(), new AccessoryNumericStringType(), - ]); + ]; + if ($lowercase) { + $types[] = new AccessoryLowercaseStringType(); + } + + return new IntersectionType($types); } private function uint(): Type @@ -1673,14 +1679,14 @@ private function stringifies(): bool private function intOrStringified(): Type { return $this->stringifies() - ? $this->numericString() + ? $this->numericString(true) : new IntegerType(); } private function uintOrStringified(): Type { return $this->stringifies() - ? $this->numericString() + ? $this->numericString(true) : $this->uint(); } From 4a00482cf3b27c9ee6ec41970d543554f0c716fe Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 7 Oct 2024 11:15:08 +0200 Subject: [PATCH 46/71] Fixes after PHPStan update --- src/Type/Doctrine/Query/QueryType.php | 6 +++--- .../Doctrine/QueryBuilder/BranchingQueryBuilderType.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Type/Doctrine/Query/QueryType.php b/src/Type/Doctrine/Query/QueryType.php index ead7ef2b..53842dd2 100644 --- a/src/Type/Doctrine/Query/QueryType.php +++ b/src/Type/Doctrine/Query/QueryType.php @@ -2,8 +2,8 @@ namespace PHPStan\Type\Doctrine\Query; -use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\GenericObjectType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\Type; @@ -44,10 +44,10 @@ public function changeSubtractedType(?Type $subtractedType): Type return new self('Doctrine\ORM\Query', $this->indexType, $this->resultType, $subtractedType); } - public function isSuperTypeOf(Type $type): TrinaryLogic + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createFromBoolean($this->equals($type)); + return IsSuperTypeOfResult::createFromBoolean($this->equals($type)); } return parent::isSuperTypeOf($type); diff --git a/src/Type/Doctrine/QueryBuilder/BranchingQueryBuilderType.php b/src/Type/Doctrine/QueryBuilder/BranchingQueryBuilderType.php index e9c7bfb8..473dffb5 100644 --- a/src/Type/Doctrine/QueryBuilder/BranchingQueryBuilderType.php +++ b/src/Type/Doctrine/QueryBuilder/BranchingQueryBuilderType.php @@ -2,7 +2,7 @@ namespace PHPStan\Type\Doctrine\QueryBuilder; -use PHPStan\TrinaryLogic; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\Type; use function array_keys; use function count; @@ -35,10 +35,10 @@ public function equals(Type $type): bool return parent::equals($type); } - public function isSuperTypeOf(Type $type): TrinaryLogic + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof parent) { - return TrinaryLogic::createFromBoolean($this->equals($type)); + return IsSuperTypeOfResult::createFromBoolean($this->equals($type)); } return parent::isSuperTypeOf($type); From 8b557dec231a93bc7aaab9d449ce4d28d4cee5ff Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 8 Oct 2024 11:05:12 +0200 Subject: [PATCH 47/71] Readme: `index by` is supported --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 83bc5364..c8a587b1 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ $query->getResult(); // array Queries are analyzed statically and do not require a running database server. This makes use of the Doctrine DQL parser and entities metadata. -Most DQL features are supported, including `GROUP BY`, `DISTINCT`, all flavors of `JOIN`, arithmetic expressions, functions, aggregations, `NEW`, etc. Sub queries and `INDEX BY` are not yet supported (infered type will be `mixed`). +Most DQL features are supported, including `GROUP BY`, `INDEX BY`, `DISTINCT`, all flavors of `JOIN`, arithmetic expressions, functions, aggregations, `NEW`, etc. Sub queries are not yet supported (infered type will be `mixed`). ### Query type inference of expressions From b129887388641766b5d306492351b96a4ee7cbe4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 9 Oct 2024 16:39:36 +0200 Subject: [PATCH 48/71] Cleanup `skipCheckGenericClasses` --- extension.neon | 9 --------- 1 file changed, 9 deletions(-) diff --git a/extension.neon b/extension.neon index b8f68a86..6b09c624 100644 --- a/extension.neon +++ b/extension.neon @@ -10,15 +10,6 @@ parameters: allCollectionsSelectable: true objectManagerLoader: null literalString: false - featureToggles: - skipCheckGenericClasses: - - Doctrine\ODM\MongoDB\Mapping\ClassMetadata - - Doctrine\ORM\Mapping\ClassMetadata - - Doctrine\ORM\Mapping\ClassMetadataInfo - - Doctrine\Persistence\Mapping\ClassMetadata - - Doctrine\ORM\AbstractQuery - - Doctrine\ORM\Query - - Doctrine\ORM\Tools\Pagination\Paginator stubFiles: - stubs/Criteria.stub - stubs/DBAL/Cache/CacheException.stub From 992a62e337eff90308e88c67f5a807ab21ffe644 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 14 Oct 2024 05:21:12 +0200 Subject: [PATCH 49/71] Fix after PHPStan update --- tests/Type/Doctrine/data/QueryResult/queryResult.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Type/Doctrine/data/QueryResult/queryResult.php b/tests/Type/Doctrine/data/QueryResult/queryResult.php index de4cc5bf..54eef205 100644 --- a/tests/Type/Doctrine/data/QueryResult/queryResult.php +++ b/tests/Type/Doctrine/data/QueryResult/queryResult.php @@ -169,7 +169,7 @@ public function testReturnTypeOfQueryMethodsWithExplicitArrayHydrationMode(Entit ); assertType( - 'array', + 'array', $query->getArrayResult() ); assertType( From e26fd3c8969d7e2a4c3056f6a2585cb7d5a752db Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 14 Oct 2024 05:51:47 +0200 Subject: [PATCH 50/71] Fix nonexistent BackedEnum in stubs --- compatibility/BackedEnum.stub | 6 ++++++ phpstan.neon | 6 ++++++ tests/BackedEnumStubExtension.php | 22 ++++++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 compatibility/BackedEnum.stub create mode 100644 tests/BackedEnumStubExtension.php diff --git a/compatibility/BackedEnum.stub b/compatibility/BackedEnum.stub new file mode 100644 index 00000000..2376a2fc --- /dev/null +++ b/compatibility/BackedEnum.stub @@ -0,0 +1,6 @@ += 80100) { + return []; + } + + return [ + __DIR__ . '/../compatibility/BackedEnum.stub', + ]; + } + +} From f61f963a80e823462146d76b57b73788c367cc0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mor=C3=A1vek?= Date: Sun, 27 Oct 2024 12:52:54 +0100 Subject: [PATCH 51/71] Mark immediately invoked callback params --- stubs/Collections/ReadableCollection.stub | 70 +++++++++++++++++++++++ stubs/DBAL/Connection.stub | 13 +++++ stubs/DBAL/Connection4.stub | 13 +++++ 3 files changed, 96 insertions(+) diff --git a/stubs/Collections/ReadableCollection.stub b/stubs/Collections/ReadableCollection.stub index df07c526..d9fba5ae 100644 --- a/stubs/Collections/ReadableCollection.stub +++ b/stubs/Collections/ReadableCollection.stub @@ -2,6 +2,7 @@ namespace Doctrine\Common\Collections; +use Closure; use Countable; use IteratorAggregate; @@ -13,4 +14,73 @@ use IteratorAggregate; interface ReadableCollection extends Countable, IteratorAggregate { + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(TKey, T):bool $p + * + * @return bool + */ + public function exists(Closure $p); + + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(T, TKey):bool $p + * + * @return ReadableCollection + */ + public function filter(Closure $p); + + /** + * @param-immediately-invoked-callable $func + * + * @param Closure(T):U $func + * + * @return ReadableCollection + * + * @template U + */ + public function map(Closure $func); + + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(TKey, T):bool $p + * + * @return array{0: ReadableCollection, 1: ReadableCollection} + */ + public function partition(Closure $p); + + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(TKey, T):bool $p + * + * @return bool TRUE, if the predicate yields TRUE for all elements, FALSE otherwise. + */ + public function forAll(Closure $p); + + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(TKey, T):bool $p + * + * @return T|null + */ + public function findFirst(Closure $p); + + /** + * @param-immediately-invoked-callable $func + * + * @param Closure(TReturn|TInitial, T):TReturn $func + * @param TInitial $initial + * + * @return TReturn|TInitial + * + * @template TReturn + * @template TInitial + */ + public function reduce(Closure $func, mixed $initial = null); + } diff --git a/stubs/DBAL/Connection.stub b/stubs/DBAL/Connection.stub index d11b040f..15c8e6e1 100644 --- a/stubs/DBAL/Connection.stub +++ b/stubs/DBAL/Connection.stub @@ -2,9 +2,11 @@ namespace Doctrine\DBAL; +use Closure; use Doctrine\DBAL\Cache\CacheException; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\Types\Type; +use Throwable; class Connection { @@ -61,4 +63,15 @@ class Connection */ public function executeCacheQuery($sql, $params, $types, QueryCacheProfile $qcp): Result; + /** + * @param-immediately-invoked-callable $func + * @param Closure(self): T $func + * @return T + * + * @template T + * + * @throws Throwable + */ + public function transactional(Closure $func); + } diff --git a/stubs/DBAL/Connection4.stub b/stubs/DBAL/Connection4.stub index 2e80b001..77b13d78 100644 --- a/stubs/DBAL/Connection4.stub +++ b/stubs/DBAL/Connection4.stub @@ -2,9 +2,11 @@ namespace Doctrine\DBAL; +use Closure; use Doctrine\DBAL\Cache\CacheException; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\Types\Type; +use Throwable; /** * @phpstan-type WrapperParameterType = string|Type|ParameterType|ArrayParameterType @@ -65,4 +67,15 @@ class Connection */ public function executeCacheQuery($sql, $params, $types, QueryCacheProfile $qcp): Result; + /** + * @param-immediately-invoked-callable $func + * @param Closure(self): T $func + * @return T + * + * @template T + * + * @throws Throwable + */ + public function transactional(Closure $func); + } From 80f3b5f30803dadcd96722380be6f254d2c88b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mor=C3=A1vek?= Date: Tue, 29 Oct 2024 09:00:14 +0100 Subject: [PATCH 52/71] Add phpdoc for transformation methods into other Collection stubs (fixes #621) --- stubs/Collections/ArrayCollection.stub | 31 ++++++++++ stubs/Collections/Collection.stub | 30 ++++++++++ stubs/Collections/Collection1.stub | 30 ++++++++++ .../DoctrineIntegration/TypeInferenceTest.php | 1 + tests/DoctrineIntegration/data/Collection.php | 56 +++++++++++++++++++ 5 files changed, 148 insertions(+) create mode 100644 tests/DoctrineIntegration/data/Collection.php diff --git a/stubs/Collections/ArrayCollection.stub b/stubs/Collections/ArrayCollection.stub index 2ecb9ea6..34d07450 100644 --- a/stubs/Collections/ArrayCollection.stub +++ b/stubs/Collections/ArrayCollection.stub @@ -2,6 +2,8 @@ namespace Doctrine\Common\Collections; +use Closure; + /** * @template TKey of array-key * @template T @@ -11,4 +13,33 @@ namespace Doctrine\Common\Collections; class ArrayCollection implements Collection, Selectable { + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(T, TKey):bool $p + * + * @return static + */ + public function filter(Closure $p); + + /** + * @param-immediately-invoked-callable $func + * + * @param Closure(T):U $func + * + * @return static + * + * @template U + */ + public function map(Closure $func); + + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(TKey, T):bool $p + * + * @return array{0: static, 1: static} + */ + public function partition(Closure $p); + } diff --git a/stubs/Collections/Collection.stub b/stubs/Collections/Collection.stub index be162ef6..05139edd 100644 --- a/stubs/Collections/Collection.stub +++ b/stubs/Collections/Collection.stub @@ -3,6 +3,7 @@ namespace Doctrine\Common\Collections; use ArrayAccess; +use Closure; use Countable; use IteratorAggregate; @@ -43,4 +44,33 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess, Readable */ public function removeElement($element) {} + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(T, TKey):bool $p + * + * @return Collection + */ + public function filter(Closure $p); + + /** + * @param-immediately-invoked-callable $func + * + * @param Closure(T):U $func + * + * @return Collection + * + * @template U + */ + public function map(Closure $func); + + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(TKey, T):bool $p + * + * @return array{0: Collection, 1: Collection} + */ + public function partition(Closure $p); + } diff --git a/stubs/Collections/Collection1.stub b/stubs/Collections/Collection1.stub index 455733c8..0f5ad708 100644 --- a/stubs/Collections/Collection1.stub +++ b/stubs/Collections/Collection1.stub @@ -3,6 +3,7 @@ namespace Doctrine\Common\Collections; use ArrayAccess; +use Closure; use Countable; use IteratorAggregate; @@ -43,4 +44,33 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess, Readable */ public function removeElement($element) {} + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(T, TKey):bool $p + * + * @return Collection + */ + public function filter(Closure $p); + + /** + * @param-immediately-invoked-callable $func + * + * @param Closure(T):U $func + * + * @return Collection + * + * @template U + */ + public function map(Closure $func); + + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(TKey, T):bool $p + * + * @return array{0: Collection, 1: Collection} + */ + public function partition(Closure $p); + } diff --git a/tests/DoctrineIntegration/TypeInferenceTest.php b/tests/DoctrineIntegration/TypeInferenceTest.php index 82a4c236..ba2c4fd6 100644 --- a/tests/DoctrineIntegration/TypeInferenceTest.php +++ b/tests/DoctrineIntegration/TypeInferenceTest.php @@ -14,6 +14,7 @@ public function dataFileAsserts(): iterable { yield from $this->gatherAssertTypes(__DIR__ . '/data/getRepository.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/isEmpty.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/Collection.php'); } /** diff --git a/tests/DoctrineIntegration/data/Collection.php b/tests/DoctrineIntegration/data/Collection.php new file mode 100644 index 00000000..f56e69d7 --- /dev/null +++ b/tests/DoctrineIntegration/data/Collection.php @@ -0,0 +1,56 @@ + */ + private $items; + + public function __construct() + { + /** @var ArrayCollection $numbers */ + $numbers = new ArrayCollection([1, 2, 3]); + + $filteredNumbers = $numbers->filter(function (int $number): bool { + return $number % 2 === 1; + }); + assertType('Doctrine\Common\Collections\ArrayCollection', $filteredNumbers); + + $items = $filteredNumbers->map(static function (int $number): Item { + return new Item(); + }); + assertType('Doctrine\Common\Collections\ArrayCollection', $items); + + $this->items = $items; + } + + public function removeOdd(): void + { + $this->items = $this->items->filter(function (Item $item, int $idx): bool { + return $idx % 2 === 1; + }); + assertType('Doctrine\Common\Collections\Collection', $this->items); + } + + public function __clone() + { + $this->items = $this->items->map( + static function (Item $item): Item { + return clone $item; + } + ); + assertType('Doctrine\Common\Collections\Collection', $this->items); + } + +} + +class Item +{ + +} From 4e9c77fc7bc3293f773fb2d8155c99572a3c89a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mor=C3=A1vek?= Date: Tue, 29 Oct 2024 10:09:45 +0100 Subject: [PATCH 53/71] Add modified stub for ReadableCollection from doctrine/collections 1.x --- extension.neon | 1 - .../Doctrine/StubFilesExtensionLoader.php | 2 + stubs/Collections/ReadableCollection1.stub | 86 +++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 stubs/Collections/ReadableCollection1.stub diff --git a/extension.neon b/extension.neon index 63365d37..35108264 100644 --- a/extension.neon +++ b/extension.neon @@ -43,7 +43,6 @@ parameters: - stubs/Persistence/ObjectRepository.stub - stubs/RepositoryFactory.stub - stubs/Collections/ArrayCollection.stub - - stubs/Collections/ReadableCollection.stub - stubs/Collections/Selectable.stub - stubs/ORM/AbstractQuery.stub - stubs/ORM/Exception/ORMException.stub diff --git a/src/Stubs/Doctrine/StubFilesExtensionLoader.php b/src/Stubs/Doctrine/StubFilesExtensionLoader.php index 0b3a69d8..390e35a5 100644 --- a/src/Stubs/Doctrine/StubFilesExtensionLoader.php +++ b/src/Stubs/Doctrine/StubFilesExtensionLoader.php @@ -64,8 +64,10 @@ public function getFiles(): array $collectionVersion = null; } if ($collectionVersion !== null && strpos($collectionVersion, '1.') === 0) { + $files[] = $stubsDir . '/Collections/ReadableCollection1.stub'; $files[] = $stubsDir . '/Collections/Collection1.stub'; } else { + $files[] = $stubsDir . '/Collections/ReadableCollection.stub'; $files[] = $stubsDir . '/Collections/Collection.stub'; } diff --git a/stubs/Collections/ReadableCollection1.stub b/stubs/Collections/ReadableCollection1.stub new file mode 100644 index 00000000..dec73af0 --- /dev/null +++ b/stubs/Collections/ReadableCollection1.stub @@ -0,0 +1,86 @@ + + */ +interface ReadableCollection extends Countable, IteratorAggregate +{ + + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(TKey, T):bool $p + * + * @return bool + */ + public function exists(Closure $p); + + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(T, TKey):bool $p + * + * @return ReadableCollection + */ + public function filter(Closure $p); + + /** + * @param-immediately-invoked-callable $func + * + * @param Closure(T):U $func + * + * @return Collection + * + * @template U + */ + public function map(Closure $func); + + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(TKey, T):bool $p + * + * @return array{0: ReadableCollection, 1: ReadableCollection} + */ + public function partition(Closure $p); + + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(TKey, T):bool $p + * + * @return bool TRUE, if the predicate yields TRUE for all elements, FALSE otherwise. + */ + public function forAll(Closure $p); + + /** + * @param-immediately-invoked-callable $p + * + * @param Closure(TKey, T):bool $p + * + * @return T|null + */ + public function findFirst(Closure $p); + + /** + * @param-immediately-invoked-callable $func + * + * @param Closure(TReturn|TInitial, T):TReturn $func + * @param TInitial $initial + * + * @return TReturn|TInitial + * + * @template TReturn + * @template TInitial + */ + public function reduce(Closure $func, mixed $initial = null); + +} From 8ba022846e79238872e315fff61e19b42ba2f139 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 7 Nov 2024 22:51:36 +0100 Subject: [PATCH 54/71] Add api annotation --- src/Type/Doctrine/ObjectMetadataResolver.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Type/Doctrine/ObjectMetadataResolver.php b/src/Type/Doctrine/ObjectMetadataResolver.php index 6a8b4fd2..68024891 100644 --- a/src/Type/Doctrine/ObjectMetadataResolver.php +++ b/src/Type/Doctrine/ObjectMetadataResolver.php @@ -106,6 +106,8 @@ private function getMetadataFactory(): ?ClassMetadataFactory } /** + * @api + * * @template T of object * @param class-string $className * @return ClassMetadata|null From f36b6d609fca0f68b8180a601b4a81b5e38ac43e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 20 Nov 2024 16:50:52 +0100 Subject: [PATCH 55/71] Fix tests --- composer.json | 2 +- src/Type/Doctrine/Descriptors/BigIntType.php | 3 +- src/Type/Doctrine/Descriptors/DecimalType.php | 9 +- src/Type/Doctrine/Descriptors/FloatType.php | 8 +- .../Doctrine/Query/QueryResultTypeWalker.php | 76 +++- ...eryResultTypeWalkerFetchTypeMatrixTest.php | 353 +++++++++--------- ...QueryResultTypeWalkerHydrationModeTest.php | 14 +- .../Query/QueryResultTypeWalkerTest.php | 30 +- 8 files changed, 269 insertions(+), 226 deletions(-) diff --git a/composer.json b/composer.json index 3d2655d7..8bd0d8f0 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.12.6" + "phpstan/phpstan": "^1.12.12" }, "conflict": { "doctrine/collections": "<1.0", diff --git a/src/Type/Doctrine/Descriptors/BigIntType.php b/src/Type/Doctrine/Descriptors/BigIntType.php index 14b3ca2a..01b85eff 100644 --- a/src/Type/Doctrine/Descriptors/BigIntType.php +++ b/src/Type/Doctrine/Descriptors/BigIntType.php @@ -3,7 +3,6 @@ namespace PHPStan\Type\Doctrine\Descriptors; use Composer\InstalledVersions; -use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\IntegerType; use PHPStan\Type\StringType; use PHPStan\Type\Type; @@ -25,7 +24,7 @@ public function getWritableToPropertyType(): Type return new IntegerType(); } - return TypeCombinator::intersect(new StringType(), new AccessoryNumericStringType()); + return (new IntegerType())->toString(); } public function getWritableToDatabaseType(): Type diff --git a/src/Type/Doctrine/Descriptors/DecimalType.php b/src/Type/Doctrine/Descriptors/DecimalType.php index 64184c45..066c0d93 100644 --- a/src/Type/Doctrine/Descriptors/DecimalType.php +++ b/src/Type/Doctrine/Descriptors/DecimalType.php @@ -4,10 +4,8 @@ use Doctrine\DBAL\Connection; use PHPStan\Doctrine\Driver\DriverDetector; -use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; -use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -31,7 +29,7 @@ public function getType(): string public function getWritableToPropertyType(): Type { - return TypeCombinator::intersect(new StringType(), new AccessoryNumericStringType()); + return (new FloatType())->toString(); } public function getWritableToDatabaseType(): Type @@ -58,10 +56,7 @@ public function getDatabaseInternalTypeForDriver(Connection $connection): Type DriverDetector::PGSQL, DriverDetector::PDO_PGSQL, ], true)) { - return new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]); + return (new FloatType())->toString(); } // not yet supported driver, return the old implementation guess diff --git a/src/Type/Doctrine/Descriptors/FloatType.php b/src/Type/Doctrine/Descriptors/FloatType.php index e475c0a2..92accffa 100644 --- a/src/Type/Doctrine/Descriptors/FloatType.php +++ b/src/Type/Doctrine/Descriptors/FloatType.php @@ -4,10 +4,7 @@ use Doctrine\DBAL\Connection; use PHPStan\Doctrine\Driver\DriverDetector; -use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\IntegerType; -use PHPStan\Type\IntersectionType; -use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use function in_array; @@ -42,10 +39,7 @@ public function getDatabaseInternalType(): Type { return TypeCombinator::union( new \PHPStan\Type\FloatType(), - new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]) + (new \PHPStan\Type\FloatType())->toString() ); } diff --git a/src/Type/Doctrine/Query/QueryResultTypeWalker.php b/src/Type/Doctrine/Query/QueryResultTypeWalker.php index 54d836c6..d6a898dc 100644 --- a/src/Type/Doctrine/Query/QueryResultTypeWalker.php +++ b/src/Type/Doctrine/Query/QueryResultTypeWalker.php @@ -18,7 +18,9 @@ use PHPStan\Php\PhpVersion; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -630,10 +632,10 @@ public function walkFunction($function): string $type = $this->createFloat(false); } elseif ($castedExprType->isNumericString()->yes()) { - $type = $this->createNumericString(false); + $type = $this->createNumericString(false, $castedExprType->isLowercaseString()->yes(), $castedExprType->isUppercaseString()->yes()); } else { - $type = TypeCombinator::union($this->createFloat(false), $this->createNumericString(false)); + $type = TypeCombinator::union($this->createFloat(false), $this->createNumericString(false, false, true)); } } else { @@ -746,7 +748,7 @@ private function inferAvgFunction(AST\Functions\AvgFunction $function): Type if ($this->driverType === DriverDetector::PDO_MYSQL || $this->driverType === DriverDetector::MYSQLI) { if ($exprTypeNoNull->isInteger()->yes()) { - return $this->createNumericString($nullable); + return $this->createNumericString($nullable, true, true); } if ($exprTypeNoNull->isString()->yes() && !$exprTypeNoNull->isNumericString()->yes()) { @@ -758,7 +760,7 @@ private function inferAvgFunction(AST\Functions\AvgFunction $function): Type if ($this->driverType === DriverDetector::PGSQL || $this->driverType === DriverDetector::PDO_PGSQL) { if ($exprTypeNoNull->isInteger()->yes()) { - return $this->createNumericString($nullable); + return $this->createNumericString($nullable, true, true); } return $this->generalizeConstantType($exprType, $nullable); @@ -794,7 +796,7 @@ private function inferSumFunction(AST\Functions\SumFunction $function): Type if ($this->driverType === DriverDetector::PDO_MYSQL || $this->driverType === DriverDetector::MYSQLI) { if ($exprTypeNoNull->isInteger()->yes()) { - return $this->createNumericString($nullable); + return $this->createNumericString($nullable, true, true); } if ($exprTypeNoNull->isString()->yes() && !$exprTypeNoNull->isNumericString()->yes()) { @@ -808,7 +810,7 @@ private function inferSumFunction(AST\Functions\SumFunction $function): Type if ($exprTypeNoNull->isInteger()->yes()) { return TypeCombinator::union( $this->createInteger($nullable), - $this->createNumericString($nullable) + $this->createNumericString($nullable, true, true) ); } @@ -845,19 +847,41 @@ private function createNonNegativeInteger(bool $nullable): Type return $nullable ? TypeCombinator::addNull($integer) : $integer; } - private function createNumericString(bool $nullable): Type + private function createNumericString(bool $nullable, bool $lowercase = false, bool $uppercase = false): Type { - $numericString = TypeCombinator::intersect( + $types = [ new StringType(), - new AccessoryNumericStringType() - ); + new AccessoryNumericStringType(), + ]; + if ($lowercase) { + $types[] = new AccessoryLowercaseStringType(); + } + if ($uppercase) { + $types[] = new AccessoryUppercaseStringType(); + } + + $numericString = new IntersectionType($types); return $nullable ? TypeCombinator::addNull($numericString) : $numericString; } - private function createString(bool $nullable): Type + private function createString(bool $nullable, bool $lowercase = false, bool $uppercase = false): Type { - $string = new StringType(); + if ($lowercase || $uppercase) { + $types = [ + new StringType(), + ]; + if ($lowercase) { + $types[] = new AccessoryLowercaseStringType(); + } + if ($uppercase) { + $types[] = new AccessoryUppercaseStringType(); + } + $string = new IntersectionType($types); + } else { + $string = new StringType(); + } + return $nullable ? TypeCombinator::addNull($string) : $string; } @@ -903,10 +927,18 @@ private function generalizeConstantType(Type $type, bool $makeNullable): Type $result = $this->createFloat($containsNull); } elseif ($typeNoNull->isNumericString()->yes()) { - $result = $this->createNumericString($containsNull); + $result = $this->createNumericString( + $containsNull, + $typeNoNull->isLowercaseString()->yes(), + $typeNoNull->isUppercaseString()->yes() + ); } elseif ($typeNoNull->isString()->yes()) { - $result = $this->createString($containsNull); + $result = $this->createString( + $containsNull, + $typeNoNull->isLowercaseString()->yes(), + $typeNoNull->isUppercaseString()->yes() + ); } else { $result = $type; @@ -1249,7 +1281,7 @@ public function walkSelectExpression($selectExpression): string // e.g. 1.0 on sqlite results to '1' with pdo_stringify on PHP 8.1, but '1.0' on PHP 8.0 with no setup // so we relax constant types and return just numeric-string to avoid those issues - $stringifiedFloat = $this->createNumericString(false); + $stringifiedFloat = $this->createNumericString(false, false, true); if ($stringify->yes()) { return $stringifiedFloat; @@ -1781,7 +1813,11 @@ private function inferPlusMinusTimesType(array $termTypes): Type } if ($this->containsOnlyTypes($unionWithoutNull, [new IntegerType(), $this->createNumericString(false)])) { - return $this->createNumericString($nullable); + return $this->createNumericString( + $nullable, + $unionWithoutNull->toString()->isLowercaseString()->yes(), + $unionWithoutNull->toString()->isUppercaseString()->yes() + ); } if ($this->containsOnlyNumericTypes($unionWithoutNull)) { @@ -1833,7 +1869,7 @@ private function inferDivisionType(array $termTypes): Type if ($unionWithoutNull->isInteger()->yes()) { if ($this->driverType === DriverDetector::MYSQLI || $this->driverType === DriverDetector::PDO_MYSQL) { - return $this->createNumericString($nullable); + return $this->createNumericString($nullable, true, true); } elseif ($this->driverType === DriverDetector::PDO_PGSQL || $this->driverType === DriverDetector::PGSQL || $this->driverType === DriverDetector::SQLITE3 || $this->driverType === DriverDetector::PDO_SQLITE) { return $this->createInteger($nullable); } @@ -1861,7 +1897,11 @@ private function inferDivisionType(array $termTypes): Type } if ($this->containsOnlyTypes($unionWithoutNull, [new IntegerType(), $this->createNumericString(false)])) { - return $this->createNumericString($nullable); + return $this->createNumericString( + $nullable, + $unionWithoutNull->toString()->isLowercaseString()->yes(), + $unionWithoutNull->toString()->isUppercaseString()->yes() + ); } if ($this->containsOnlyTypes($unionWithoutNull, [new FloatType(), $this->createNumericString(false)])) { diff --git a/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php b/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php index 28f9c2ed..03e5ed12 100644 --- a/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php +++ b/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php @@ -25,7 +25,9 @@ use PHPStan\Platform\Entity\PlatformEntity; use PHPStan\Platform\Entity\PlatformRelatedEntity; use PHPStan\Testing\PHPStanTestCase; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; @@ -925,7 +927,7 @@ public static function provideCases(): iterable yield '1 + 1 * 1 / 1 - 1' => [ 'data' => self::dataDefault(), 'select' => 'SELECT (1 + 1 * 1 / 1 - 1) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), 'pdo_pgsql' => self::int(), 'pgsql' => self::int(), @@ -1053,10 +1055,10 @@ public static function provideCases(): iterable yield 't.col_int + t.col_decimal' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_int + t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '9.1', 'sqliteResult' => 9.1, @@ -1069,10 +1071,10 @@ public static function provideCases(): iterable yield 't.col_int + t.col_decimal (int data)' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT t.col_int + t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '2.0', 'sqliteResult' => 2, @@ -1117,10 +1119,10 @@ public static function provideCases(): iterable yield 't.col_decimal + t.col_decimal (int data)' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT t.col_decimal + t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '2.0', 'sqliteResult' => 2, @@ -1149,10 +1151,10 @@ public static function provideCases(): iterable yield 't.col_decimal + t.col_decimal' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_decimal + t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '0.2', 'sqliteResult' => 0.2, @@ -1229,7 +1231,7 @@ public static function provideCases(): iterable yield 't.col_decimal + t.col_bool' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_decimal + t.col_bool FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), 'pdo_pgsql' => null, // Undefined function 'pgsql' => null, // Undefined function @@ -1277,7 +1279,7 @@ public static function provideCases(): iterable yield 't.col_int / t.col_int' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_int / t.col_int FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), 'pdo_pgsql' => self::int(), 'pgsql' => self::int(), @@ -1293,7 +1295,7 @@ public static function provideCases(): iterable yield 't.col_bigint / t.col_bigint' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_bigint / t.col_bigint FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), 'pdo_pgsql' => self::int(), 'pgsql' => self::int(), @@ -1373,10 +1375,10 @@ public static function provideCases(): iterable yield 't.col_int / t.col_decimal' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_int / t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '90.0000', 'sqliteResult' => 90.0, @@ -1389,10 +1391,10 @@ public static function provideCases(): iterable yield 't.col_int / t.col_decimal (int data)' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT t.col_int / t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0000', 'sqliteResult' => 1, @@ -1421,10 +1423,10 @@ public static function provideCases(): iterable yield 't.col_decimal / t.col_decimal' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_decimal / t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.00000', 'sqliteResult' => 1.0, @@ -1437,10 +1439,10 @@ public static function provideCases(): iterable yield 't.col_decimal / t.col_decimal (int data)' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT t.col_decimal / t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.00000', 'sqliteResult' => 1, @@ -1517,7 +1519,7 @@ public static function provideCases(): iterable yield 't.col_int / t.col_bool' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_int / t.col_bool FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), 'pdo_pgsql' => null, // Undefined function 'pgsql' => null, // Undefined function @@ -1565,7 +1567,7 @@ public static function provideCases(): iterable yield 't.col_decimal / t.col_bool' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_decimal / t.col_bool FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), 'pdo_pgsql' => null, // Undefined function 'pgsql' => null, // Undefined function @@ -1581,7 +1583,7 @@ public static function provideCases(): iterable yield 't.col_decimal / t.col_bool (int data)' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT t.col_decimal / t.col_bool FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), 'pdo_pgsql' => null, // Undefined function 'pgsql' => null, // Undefined function @@ -1629,7 +1631,7 @@ public static function provideCases(): iterable yield 't.col_int / t.col_int_nullable' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_int / t.col_int_nullable FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::intOrNull(), 'pdo_pgsql' => self::intOrNull(), 'pgsql' => self::intOrNull(), @@ -1709,7 +1711,7 @@ public static function provideCases(): iterable yield '1 / 1' => [ 'data' => self::dataDefault(), 'select' => 'SELECT (1 / 1) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), 'pdo_pgsql' => self::int(), 'pgsql' => self::int(), @@ -1725,10 +1727,10 @@ public static function provideCases(): iterable yield '1 / 1.0' => [ 'data' => self::dataDefault(), 'select' => 'SELECT (1 / 1.0) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0000', 'sqliteResult' => 1.0, @@ -1743,8 +1745,8 @@ public static function provideCases(): iterable 'select' => 'SELECT (1 / 1e0) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, @@ -1949,10 +1951,10 @@ public static function provideCases(): iterable yield 'COALESCE(t.col_decimal, t.col_decimal) + int data' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT COALESCE(t.col_decimal, t.col_decimal) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1, @@ -1997,11 +1999,11 @@ public static function provideCases(): iterable yield 't.col_decimal' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_decimal FROM %s t', - 'mysql' => self::numericString(), - 'sqlite' => self::numericString(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), - 'mssql' => self::numericString(), + 'mysql' => self::numericString(false, true), + 'sqlite' => self::numericString(false, true), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), + 'mssql' => self::numericString(false, true), 'mysqlResult' => '0.1', 'sqliteResult' => '0.1', 'pdoPgsqlResult' => '0.1', @@ -2029,11 +2031,11 @@ public static function provideCases(): iterable yield 't.col_bigint' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_bigint FROM %s t', - 'mysql' => self::hasDbal4() ? self::int() : self::numericString(), - 'sqlite' => self::hasDbal4() ? self::int() : self::numericString(), - 'pdo_pgsql' => self::hasDbal4() ? self::int() : self::numericString(), - 'pgsql' => self::hasDbal4() ? self::int() : self::numericString(), - 'mssql' => self::hasDbal4() ? self::int() : self::numericString(), + 'mysql' => self::hasDbal4() ? self::int() : self::numericString(true, true), + 'sqlite' => self::hasDbal4() ? self::int() : self::numericString(true, true), + 'pdo_pgsql' => self::hasDbal4() ? self::int() : self::numericString(true, true), + 'pgsql' => self::hasDbal4() ? self::int() : self::numericString(true, true), + 'mssql' => self::hasDbal4() ? self::int() : self::numericString(true, true), 'mysqlResult' => self::hasDbal4() ? 2147483648 : '2147483648', 'sqliteResult' => self::hasDbal4() ? 2147483648 : '2147483648', 'pdoPgsqlResult' => self::hasDbal4() ? 2147483648 : '2147483648', @@ -2125,10 +2127,10 @@ public static function provideCases(): iterable yield 'AVG(t.col_decimal)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(t.col_decimal) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(false, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(false, true), + 'pgsql' => self::numericStringOrNull(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '0.10000', 'sqliteResult' => 0.1, @@ -2141,10 +2143,10 @@ public static function provideCases(): iterable yield 'AVG(t.col_decimal) + int data' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT AVG(t.col_decimal) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(false, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(false, true), + 'pgsql' => self::numericStringOrNull(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.00000', 'sqliteResult' => 1.0, @@ -2173,10 +2175,10 @@ public static function provideCases(): iterable yield 'AVG(t.col_int)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(t.col_int) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '9.0000', 'sqliteResult' => 9.0, @@ -2189,7 +2191,7 @@ public static function provideCases(): iterable yield 'AVG(t.col_bool)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(t.col_bool) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), 'pdo_pgsql' => null, // Undefined function 'pgsql' => null, // Undefined function @@ -2221,10 +2223,10 @@ public static function provideCases(): iterable yield 'AVG(1)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(1) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0000', 'sqliteResult' => 1.0, @@ -2301,10 +2303,10 @@ public static function provideCases(): iterable yield 'AVG(1) + GROUP BY' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(1) FROM %s t GROUP BY t.col_int', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0000', 'sqliteResult' => 1.0, @@ -2317,10 +2319,10 @@ public static function provideCases(): iterable yield 'AVG(1.0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(1.0) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.00000', 'sqliteResult' => 1.0, @@ -2333,10 +2335,10 @@ public static function provideCases(): iterable yield 'AVG(1e0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(1.0) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.00000', 'sqliteResult' => 1.0, @@ -2349,10 +2351,10 @@ public static function provideCases(): iterable yield 'AVG(t.col_bigint)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(t.col_bigint) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '2147483648.0000', 'sqliteResult' => 2147483648.0, @@ -2429,10 +2431,10 @@ public static function provideCases(): iterable yield 'SUM(t.col_decimal)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT SUM(t.col_decimal) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(false, true), 'sqlite' => self::floatOrIntOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(false, true), + 'pgsql' => self::numericStringOrNull(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '0.1', 'sqliteResult' => 0.1, @@ -2445,10 +2447,10 @@ public static function provideCases(): iterable yield 'SUM(t.col_decimal) + int data' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT SUM(t.col_decimal) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(false, true), 'sqlite' => self::floatOrIntOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(false, true), + 'pgsql' => self::numericStringOrNull(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1, @@ -2461,10 +2463,10 @@ public static function provideCases(): iterable yield 'SUM(t.col_int)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT SUM(t.col_int) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::intOrNull(), - 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), - 'pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), + 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), + 'pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), 'mssql' => self::mixed(), 'mysqlResult' => '9', 'sqliteResult' => 9, @@ -2477,10 +2479,10 @@ public static function provideCases(): iterable yield '-SUM(t.col_int)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT -SUM(t.col_int) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::intOrNull(), - 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), - 'pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), + 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), + 'pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), 'mssql' => self::mixed(), 'mysqlResult' => '-9', 'sqliteResult' => -9, @@ -2493,10 +2495,10 @@ public static function provideCases(): iterable yield '-SUM(t.col_int) + no data' => [ 'data' => self::dataNone(), 'select' => 'SELECT -SUM(t.col_int) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::intOrNull(), - 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), - 'pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), + 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), + 'pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), 'mssql' => self::mixed(), 'mysqlResult' => null, 'sqliteResult' => null, @@ -2525,7 +2527,7 @@ public static function provideCases(): iterable yield 'SUM(t.col_bool)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT SUM(t.col_bool) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::intOrNull(), 'pdo_pgsql' => null, // Undefined function 'pgsql' => null, // Undefined function @@ -2621,10 +2623,10 @@ public static function provideCases(): iterable yield 'SUM(1)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT SUM(1) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::intOrNull(), - 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), - 'pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), + 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), + 'pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), 'mssql' => self::mixed(), 'mysqlResult' => '1', 'sqliteResult' => 1, @@ -2637,10 +2639,10 @@ public static function provideCases(): iterable yield 'SUM(1) + GROUP BY' => [ 'data' => self::dataDefault(), 'select' => 'SELECT SUM(1) FROM %s t GROUP BY t.col_int', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int()), - 'pgsql' => TypeCombinator::union(self::numericString(), self::int()), + 'pdo_pgsql' => TypeCombinator::union(self::numericString(true, true), self::int()), + 'pgsql' => TypeCombinator::union(self::numericString(true, true), self::int()), 'mssql' => self::mixed(), 'mysqlResult' => '1', 'sqliteResult' => 1, @@ -2653,10 +2655,10 @@ public static function provideCases(): iterable yield 'SUM(1.0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT SUM(1.0) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1.0, @@ -2671,8 +2673,8 @@ public static function provideCases(): iterable 'select' => 'SELECT SUM(1e0) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, @@ -2685,10 +2687,10 @@ public static function provideCases(): iterable yield 'SUM(t.col_bigint)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT SUM(t.col_bigint) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::intOrNull(), - 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), - 'pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), + 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), + 'pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), 'mssql' => self::mixed(), 'mysqlResult' => '2147483648', 'sqliteResult' => 2147483648, @@ -2749,10 +2751,10 @@ public static function provideCases(): iterable yield 'MAX(t.col_decimal)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT MAX(t.col_decimal) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(false, true), 'sqlite' => self::floatOrIntOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(false, true), + 'pgsql' => self::numericStringOrNull(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '0.1', 'sqliteResult' => 0.1, @@ -2765,10 +2767,10 @@ public static function provideCases(): iterable yield 'MAX(t.col_decimal) + int data' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT MAX(t.col_decimal) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(false, true), 'sqlite' => self::floatOrIntOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(false, true), + 'pgsql' => self::numericStringOrNull(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1, @@ -2861,10 +2863,10 @@ public static function provideCases(): iterable yield "MAX('1')" => [ 'data' => self::dataDefault(), 'select' => "SELECT MAX('1') FROM %s t", - 'mysql' => self::numericStringOrNull(), - 'sqlite' => self::numericStringOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), + 'sqlite' => self::numericStringOrNull(true, true), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1', 'sqliteResult' => '1', @@ -2877,10 +2879,10 @@ public static function provideCases(): iterable yield "MAX('1.0')" => [ 'data' => self::dataDefault(), 'select' => "SELECT MAX('1.0') FROM %s t", - 'mysql' => self::numericStringOrNull(), - 'sqlite' => self::numericStringOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), + 'sqlite' => self::numericStringOrNull(true, true), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => '1.0', @@ -2925,10 +2927,10 @@ public static function provideCases(): iterable yield 'MAX(1.0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT MAX(1.0) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1.0, @@ -2943,8 +2945,8 @@ public static function provideCases(): iterable 'select' => 'SELECT MAX(1e0) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, @@ -2989,10 +2991,10 @@ public static function provideCases(): iterable yield 'ABS(t.col_decimal)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT ABS(t.col_decimal) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '0.1', 'sqliteResult' => 0.1, @@ -3005,10 +3007,10 @@ public static function provideCases(): iterable yield 'ABS(t.col_decimal) + int data' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT ABS(t.col_decimal) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1, @@ -3149,10 +3151,10 @@ public static function provideCases(): iterable yield 'ABS(1.0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT ABS(1.0) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1.0, @@ -3167,8 +3169,8 @@ public static function provideCases(): iterable 'select' => 'SELECT ABS(1e0) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, @@ -3647,8 +3649,8 @@ public static function provideCases(): iterable 'select' => 'SELECT SQRT(t.col_decimal) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, @@ -3823,8 +3825,8 @@ public static function provideCases(): iterable 'select' => 'SELECT SQRT(1.0) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, @@ -4045,10 +4047,10 @@ public static function provideCases(): iterable yield 'COALESCE(SUM(t.col_int_nullable), 0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT COALESCE(SUM(t.col_int_nullable), 0) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int()), - 'pgsql' => TypeCombinator::union(self::numericString(), self::int()), + 'pdo_pgsql' => TypeCombinator::union(self::numericString(true, true), self::int()), + 'pgsql' => TypeCombinator::union(self::numericString(true, true), self::int()), 'mssql' => self::mixed(), 'mysqlResult' => '0', 'sqliteResult' => 0, @@ -4061,10 +4063,10 @@ public static function provideCases(): iterable yield 'COALESCE(SUM(ABS(t.col_int)), 0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT COALESCE(SUM(ABS(t.col_int)), 0) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), - 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString()), - 'pgsql' => TypeCombinator::union(self::int(), self::numericString()), + 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), + 'pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), 'mssql' => self::mixed(), 'mysqlResult' => '9', 'sqliteResult' => 9, @@ -4141,10 +4143,10 @@ public static function provideCases(): iterable yield "COALESCE(1, '1')" => [ 'data' => self::dataDefault(), 'select' => "SELECT COALESCE(1, '1') FROM %s t", - 'mysql' => self::numericString(), - 'sqlite' => TypeCombinator::union(self::int(), self::numericString()), - 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString()), - 'pgsql' => TypeCombinator::union(self::int(), self::numericString()), + 'mysql' => self::numericString(true, true), + 'sqlite' => TypeCombinator::union(self::int(), self::numericString(true, true)), + 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), + 'pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), 'mssql' => self::mixed(), 'mysqlResult' => '1', 'sqliteResult' => 1, @@ -4159,8 +4161,8 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(1, 1.0) FROM %s t', 'mysql' => self::numericString(), 'sqlite' => TypeCombinator::union(self::int(), self::float()), - 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString()), - 'pgsql' => TypeCombinator::union(self::int(), self::numericString()), + 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), + 'pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1, @@ -4189,10 +4191,10 @@ public static function provideCases(): iterable yield 'COALESCE(1.0, 1.0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT COALESCE(1.0, 1.0) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1.0, @@ -4207,8 +4209,8 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(1e0, 1.0) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, @@ -4223,8 +4225,8 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(1, 1.0, 1e0) FROM %s t', 'mysql' => self::float(), 'sqlite' => TypeCombinator::union(self::float(), self::int()), - 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString()), - 'pgsql' => TypeCombinator::union(self::int(), self::numericString()), + 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), + 'pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1, @@ -4238,9 +4240,9 @@ public static function provideCases(): iterable 'data' => self::dataDefault(), 'select' => "SELECT COALESCE(1, 1.0, 1e0, '1') FROM %s t", 'mysql' => self::numericString(), - 'sqlite' => TypeCombinator::union(self::float(), self::int(), self::numericString()), - 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString()), - 'pgsql' => TypeCombinator::union(self::int(), self::numericString()), + 'sqlite' => TypeCombinator::union(self::float(), self::int(), self::numericString(true, true)), + 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), + 'pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), 'mssql' => self::mixed(), 'mysqlResult' => '1', 'sqliteResult' => 1, @@ -4305,8 +4307,8 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_float_nullable, 0.0) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => TypeCombinator::union(self::float(), self::numericString()), - 'pgsql' => TypeCombinator::union(self::float(), self::numericString()), + 'pdo_pgsql' => TypeCombinator::union(self::float(), self::numericString(false, true)), + 'pgsql' => TypeCombinator::union(self::float(), self::numericString(false, true)), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0.0, @@ -4755,7 +4757,7 @@ private function assertInferredResultMatchesExpected( $inferredFirstItemType = $inferredType->getFirstIterableValueType(); self::assertTrue( - $inferredFirstItemType->equals($expectedFirstItemType), + $expectedFirstItemType->accepts($inferredFirstItemType, true)->yes(), sprintf( "Mismatch between inferred result and expected type\n\nDriver: %s\nConfig: %s\nDataset: %s\nDQL: %s\nSQL: %s\nPHP: %s\nReal first result: %s\nFirst item inferred as: %s\nFirst item expected type: %s\n", $driver, @@ -4842,12 +4844,20 @@ private static function boolOrNull(): Type return TypeCombinator::addNull(new BooleanType()); } - private static function numericString(): Type + private static function numericString(bool $lowercase = false, bool $uppercase = false): Type { - return new IntersectionType([ + $types = [ new StringType(), new AccessoryNumericStringType(), - ]); + ]; + if ($lowercase) { + $types[] = new AccessoryLowercaseStringType(); + } + if ($uppercase) { + $types[] = new AccessoryUppercaseStringType(); + } + + return new IntersectionType($types); } private static function string(): Type @@ -4855,12 +4865,9 @@ private static function string(): Type return new StringType(); } - private static function numericStringOrNull(): Type + private static function numericStringOrNull(bool $lowercase = false, bool $uppercase = false): Type { - return TypeCombinator::addNull(new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ])); + return TypeCombinator::addNull(self::numericString($lowercase, $uppercase)); } private static function int(): Type diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php index be915cad..d29f86ec 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php @@ -13,6 +13,7 @@ use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantIntegerType; @@ -141,7 +142,7 @@ public static function getTestData(): iterable yield 'getResult(object), fields' => [ self::list(self::constantArray([ - [new ConstantStringType('decimalColumn'), self::numericString()], + [new ConstantStringType('decimalColumn'), self::numericString(false, true)], [new ConstantStringType('floatColumn'), new FloatType()], ])), ' @@ -177,7 +178,7 @@ public static function getTestData(): iterable yield 'toIterable(object), fields' => [ new IterableType(new IntegerType(), self::constantArray([ - [new ConstantStringType('decimalColumn'), self::numericString()], + [new ConstantStringType('decimalColumn'), self::numericString(false, true)], [new ConstantStringType('floatColumn'), new FloatType()], ])), ' @@ -309,7 +310,7 @@ private static function list(Type $values): Type return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $values)); } - private static function numericString(bool $lowercase = false): Type + private static function numericString(bool $lowercase = false, bool $uppercase = false): Type { $types = [ new StringType(), @@ -318,6 +319,9 @@ private static function numericString(bool $lowercase = false): Type if ($lowercase) { $types[] = new AccessoryLowercaseStringType(); } + if ($uppercase) { + $types[] = new AccessoryUppercaseStringType(); + } return new IntersectionType($types); } @@ -406,14 +410,14 @@ private static function stringifies(): bool private static function floatOrStringified(): Type { return self::stringifies() - ? self::numericString() + ? self::numericString(false, true) : new FloatType(); } private static function floatOrIntOrStringified(): Type { return self::stringifies() - ? self::numericString() + ? self::numericString(false, true) : TypeCombinator::union(new FloatType(), new IntegerType()); } diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php index 905c5221..aa0ce591 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php @@ -17,6 +17,7 @@ use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantFloatType; @@ -377,7 +378,7 @@ public function getTestData(): iterable ]), $this->constantArray([ [new ConstantIntegerType(0), new ObjectType(One::class)], - [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString()], + [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString(true, true)], [new ConstantStringType('intColumn'), new IntegerType()], ]) ), @@ -399,7 +400,7 @@ public function getTestData(): iterable ]), $this->constantArray([ [new ConstantIntegerType(0), new ObjectType(Many::class)], - [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString()], + [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString(true, true)], [new ConstantStringType('intColumn'), new IntegerType()], ]) ), @@ -420,7 +421,7 @@ public function getTestData(): iterable ]), $this->constantArray([ [new ConstantStringType('one'), new ObjectType(One::class)], - [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString()], + [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString(true, true)], [new ConstantStringType('intColumn'), new IntegerType()], ]) ), @@ -530,7 +531,7 @@ public function getTestData(): iterable yield 'just root entity and scalars' => [ $this->constantArray([ [new ConstantIntegerType(0), new ObjectType(One::class)], - [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString()], + [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString(true, true)], ]), ' SELECT o, o.id @@ -788,7 +789,7 @@ public function getTestData(): iterable [ new ConstantIntegerType(4), $this->stringifies() - ? $this->numericString() + ? $this->numericString(false, true) : TypeCombinator::union( new IntegerType(), new FloatType() @@ -1474,8 +1475,8 @@ public function getTestData(): iterable yield 'unary minus' => [ $this->constantArray([ [new ConstantStringType('minusInt'), $this->stringifies() ? new ConstantStringType('-1') : new ConstantIntegerType(-1)], - [new ConstantStringType('minusFloat'), $this->stringifies() ? $this->numericString() : new ConstantFloatType(-0.1)], - [new ConstantStringType('minusIntRange'), $this->stringifies() ? $this->numericString(true) : IntegerRangeType::fromInterval(null, 0)], + [new ConstantStringType('minusFloat'), $this->stringifies() ? $this->numericString(false, true) : new ConstantFloatType(-0.1)], + [new ConstantStringType('minusIntRange'), $this->stringifies() ? $this->numericString(true, true) : IntegerRangeType::fromInterval(null, 0)], ]), ' SELECT -1 as minusInt, @@ -1516,7 +1517,7 @@ private function yieldConditionalDataset(): iterable $this->constantArray([ [ new ConstantIntegerType(1), - new StringType(), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), ], [ new ConstantIntegerType(2), @@ -1524,7 +1525,7 @@ private function yieldConditionalDataset(): iterable ], [ new ConstantIntegerType(3), - $this->numericString(), + $this->numericString(true, true), ], ]), ' @@ -1623,7 +1624,7 @@ private function constantArray(array $elements): Type return $builder->getArray(); } - private function numericString(bool $lowercase = false): Type + private function numericString(bool $lowercase = false, bool $uppercase = false): Type { $types = [ new StringType(), @@ -1632,6 +1633,9 @@ private function numericString(bool $lowercase = false): Type if ($lowercase) { $types[] = new AccessoryLowercaseStringType(); } + if ($uppercase) { + $types[] = new AccessoryUppercaseStringType(); + } return new IntersectionType($types); } @@ -1679,21 +1683,21 @@ private function stringifies(): bool private function intOrStringified(): Type { return $this->stringifies() - ? $this->numericString(true) + ? $this->numericString(true, true) : new IntegerType(); } private function uintOrStringified(): Type { return $this->stringifies() - ? $this->numericString(true) + ? $this->numericString(true, true) : $this->uint(); } private function floatOrStringified(): Type { return $this->stringifies() - ? $this->numericString() + ? $this->numericString(false, true) : new FloatType(); } From 25ab1ec770c3a9227da37964da60f5217079de4d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 20 Nov 2024 16:58:28 +0100 Subject: [PATCH 56/71] Test projects only in 2.0.x-dev --- .github/workflows/test-projects.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-projects.yml b/.github/workflows/test-projects.yml index d33dcf6f..2b85f70e 100644 --- a/.github/workflows/test-projects.yml +++ b/.github/workflows/test-projects.yml @@ -5,7 +5,7 @@ name: "Test projects" on: push: branches: - - "1.5.x" + - "2.0.x" jobs: test-projects: From 231d3f795ed5ef54c98961fd3958868cbe091207 Mon Sep 17 00:00:00 2001 From: Vlasta Neubauer Date: Mon, 2 Dec 2024 10:38:49 +0100 Subject: [PATCH 57/71] Mark all result methods in AbstractQuery as impure --- stubs/ORM/AbstractQuery.stub | 71 ++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/stubs/ORM/AbstractQuery.stub b/stubs/ORM/AbstractQuery.stub index 1b970ad0..25f7e320 100644 --- a/stubs/ORM/AbstractQuery.stub +++ b/stubs/ORM/AbstractQuery.stub @@ -4,6 +4,7 @@ namespace Doctrine\ORM; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; /** * @template-covariant TKey The type of column used in indexBy @@ -12,21 +13,75 @@ use Doctrine\ORM\NonUniqueResultException; abstract class AbstractQuery { + public const HYDRATE_OBJECT = 1; + /** * @param ArrayCollection|array $parameters * @return static */ public function setParameters($parameters) { - } - /** - * @return bool|float|int|string|null - * - * @throws NoResultException - * @throws NonUniqueResultException - */ - public function getSingleScalarResult(); + /** + * @phpstan-impure + * @param string|AbstractQuery::HYDRATE_* $hydrationMode + */ + public function getResult($hydrationMode = self::HYDRATE_OBJECT): mixed + { + } + + /** + * @phpstan-impure + * @return mixed[] + */ + public function getArrayResult(): array + { + } + + /** + * @phpstan-impure + * @return mixed[] + */ + public function getSingleColumnResult(): array + { + } + + /** + * @phpstan-impure + * @return mixed[] + */ + public function getScalarResult(): array + { + } + + /** + * @phpstan-impure + * @param string|AbstractQuery::HYDRATE_*|null $hydrationMode + * @throws NonUniqueResultException + */ + public function getOneOrNullResult($hydrationMode = null): mixed + { + } + + /** + * @phpstan-impure + * @param string|AbstractQuery::HYDRATE_*|null $hydrationMode + * @throws NonUniqueResultException + * @throws NoResultException + */ + public function getSingleResult($hydrationMode = null): mixed + { + } + + /** + * @phpstan-impure + * @return bool|float|int|string|null + * @throws NoResultException + * @throws NonUniqueResultException + */ + public function getSingleScalarResult() + { + } } From 815d5ae40ffe3a7788df1cd557e30e474f3aba1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mirtes?= Date: Tue, 28 Jan 2025 10:26:47 +0100 Subject: [PATCH 58/71] Update LICENSE --- LICENSE | 1 + 1 file changed, 1 insertion(+) diff --git a/LICENSE b/LICENSE index 7c0f2b7b..e5f34e60 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2016 Ondřej Mirtes +Copyright (c) 2025 PHPStan s.r.o. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From a61a04a361b60014ec04881ccb87252d3bf02e94 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Mon, 3 Mar 2025 10:22:39 +0100 Subject: [PATCH 59/71] EntityRepository.stub fix nullability in findOneBy --- stubs/EntityRepository.stub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubs/EntityRepository.stub b/stubs/EntityRepository.stub index 4dcde8bf..784b14c8 100644 --- a/stubs/EntityRepository.stub +++ b/stubs/EntityRepository.stub @@ -42,7 +42,7 @@ class EntityRepository implements ObjectRepository * @phpstan-param array|null $orderBy * @phpstan-return TEntityClass|null */ - public function findOneBy(array $criteria, array $orderBy = null); + public function findOneBy(array $criteria, ?array $orderBy = null); /** * @phpstan-return class-string From baf73e368f2cbdc13ced4c42df3269ba577621a0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 18 Mar 2025 11:17:26 +0100 Subject: [PATCH 60/71] Introduce phpstan-deprecation-rules --- composer.json | 1 + phpstan-baseline-deprecations.neon | 127 ++++++++++++++++++ phpstan-baseline-orm-3.neon | 99 ++++++++++++++ phpstan.neon | 2 + .../IsEmptyTypeSpecifyingExtension.php | 7 +- 5 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 phpstan-baseline-deprecations.neon diff --git a/composer.json b/composer.json index daedadb0..4ce656ed 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "gedmo/doctrine-extensions": "^3.8", "nesbot/carbon": "^2.49", "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6.20", diff --git a/phpstan-baseline-deprecations.neon b/phpstan-baseline-deprecations.neon new file mode 100644 index 00000000..bd96685e --- /dev/null +++ b/phpstan-baseline-deprecations.neon @@ -0,0 +1,127 @@ +parameters: + ignoreErrors: + - + message: ''' + #^Fetching class constant class of deprecated class Doctrine\\DBAL\\Types\\ArrayType\: + Use \{@link JsonType\} instead\.$# + ''' + identifier: classConstant.deprecatedClass + count: 1 + path: src/Type/Doctrine/Descriptors/ArrayType.php + + - + message: ''' + #^Fetching class constant class of deprecated class Doctrine\\DBAL\\Types\\ObjectType\: + Use \{@link JsonType\} instead\.$# + ''' + identifier: classConstant.deprecatedClass + count: 1 + path: src/Type/Doctrine/Descriptors/ObjectType.php + + - + message: ''' + #^Call to deprecated method getSchemaManager\(\) of class Doctrine\\DBAL\\Connection\: + Use \{@see createSchemaManager\(\)\} instead\.$# + ''' + identifier: method.deprecated + count: 1 + path: tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php + + - + message: ''' + #^Fetching class constant class of deprecated class Doctrine\\DBAL\\Types\\ArrayType\: + Use \{@link JsonType\} instead\.$# + ''' + identifier: classConstant.deprecatedClass + count: 1 + path: tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php + + - + message: ''' + #^Call to deprecated method create\(\) of class Doctrine\\ORM\\EntityManager\: + Use \{@see DriverManager\:\:getConnection\(\)\} to bootstrap the connection and call the constructor\.$# + ''' + identifier: staticMethod.deprecated + count: 1 + path: src/Doctrine/Mapping/ClassMetadataFactory.php + + - + message: ''' + #^Fetching class constant class of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: classConstant.deprecatedClass + count: 1 + path: src/Doctrine/Mapping/ClassMetadataFactory.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecated + count: 1 + path: src/Doctrine/Mapping/ClassMetadataFactory.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecated + count: 1 + path: tests/Classes/entity-manager.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecated + count: 1 + path: tests/DoctrineIntegration/ORM/entity-manager.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecated + count: 1 + path: tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecated + count: 1 + path: tests/Rules/DeadCode/entity-manager.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecated + count: 1 + path: tests/Rules/Properties/entity-manager.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecated + count: 1 + path: tests/Type/Doctrine/DBAL/mysqli.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecated + count: 1 + path: tests/Type/Doctrine/DBAL/pdo.php diff --git a/phpstan-baseline-orm-3.neon b/phpstan-baseline-orm-3.neon index 92e5f87c..ae5e8dfd 100644 --- a/phpstan-baseline-orm-3.neon +++ b/phpstan-baseline-orm-3.neon @@ -54,3 +54,102 @@ parameters: message: "#^Parameter \\#2 \\$className of static method Doctrine\\\\DBAL\\\\Types\\\\Type\\:\\:addType\\(\\) expects class\\-string\\, string given\\.$#" count: 1 path: tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php + + - + message: ''' + #^Fetching class constant class of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\. + Copyright \(c\) Doctrine Project + From https\://github\.com/doctrine/orm/blob/40fbbf4429b0d66517244051237a2bd0616a7a13/src/Mapping/Driver/AnnotationDriver\.php$# + ''' + identifier: classConstant.deprecatedClass + count: 1 + path: src/Doctrine/Mapping/ClassMetadataFactory.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\. + Copyright \(c\) Doctrine Project + From https\://github\.com/doctrine/orm/blob/40fbbf4429b0d66517244051237a2bd0616a7a13/src/Mapping/Driver/AnnotationDriver\.php$# + ''' + identifier: new.deprecated + count: 1 + path: src/Doctrine/Mapping/ClassMetadataFactory.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\. + Copyright \(c\) Doctrine Project + From https\://github\.com/doctrine/orm/blob/40fbbf4429b0d66517244051237a2bd0616a7a13/src/Mapping/Driver/AnnotationDriver\.php$# + ''' + identifier: new.deprecated + count: 1 + path: tests/Classes/entity-manager.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\. + Copyright \(c\) Doctrine Project + From https\://github\.com/doctrine/orm/blob/40fbbf4429b0d66517244051237a2bd0616a7a13/src/Mapping/Driver/AnnotationDriver\.php$# + ''' + identifier: new.deprecated + count: 1 + path: tests/DoctrineIntegration/ORM/entity-manager.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\. + Copyright \(c\) Doctrine Project + From https\://github\.com/doctrine/orm/blob/40fbbf4429b0d66517244051237a2bd0616a7a13/src/Mapping/Driver/AnnotationDriver\.php$# + ''' + identifier: new.deprecated + count: 1 + path: tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\. + Copyright \(c\) Doctrine Project + From https\://github\.com/doctrine/orm/blob/40fbbf4429b0d66517244051237a2bd0616a7a13/src/Mapping/Driver/AnnotationDriver\.php$# + ''' + identifier: new.deprecated + count: 1 + path: tests/Rules/DeadCode/entity-manager.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\. + Copyright \(c\) Doctrine Project + From https\://github\.com/doctrine/orm/blob/40fbbf4429b0d66517244051237a2bd0616a7a13/src/Mapping/Driver/AnnotationDriver\.php$# + ''' + identifier: new.deprecated + count: 1 + path: tests/Rules/Properties/entity-manager.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\. + Copyright \(c\) Doctrine Project + From https\://github\.com/doctrine/orm/blob/40fbbf4429b0d66517244051237a2bd0616a7a13/src/Mapping/Driver/AnnotationDriver\.php$# + ''' + identifier: new.deprecated + count: 1 + path: tests/Type/Doctrine/DBAL/mysqli.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\. + Copyright \(c\) Doctrine Project + From https\://github\.com/doctrine/orm/blob/40fbbf4429b0d66517244051237a2bd0616a7a13/src/Mapping/Driver/AnnotationDriver\.php$# + ''' + identifier: new.deprecated + count: 1 + path: tests/Type/Doctrine/DBAL/pdo.php diff --git a/phpstan.neon b/phpstan.neon index 8009fd82..efbf455d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,9 +2,11 @@ includes: - extension.neon - rules.neon - phpstan-baseline.neon + - phpstan-baseline-deprecations.neon - phpstan-baseline-dbal-3.neon - compatibility/orm-3-baseline.php - vendor/phpstan/phpstan-strict-rules/rules.neon + - vendor/phpstan/phpstan-deprecation-rules/rules.neon - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon - phar://phpstan.phar/conf/bleedingEdge.neon diff --git a/src/Type/Doctrine/Collection/IsEmptyTypeSpecifyingExtension.php b/src/Type/Doctrine/Collection/IsEmptyTypeSpecifyingExtension.php index 32765e79..9a177304 100644 --- a/src/Type/Doctrine/Collection/IsEmptyTypeSpecifyingExtension.php +++ b/src/Type/Doctrine/Collection/IsEmptyTypeSpecifyingExtension.php @@ -43,11 +43,8 @@ public function isMethodSupported( TypeSpecifierContext $context ): bool { - return ( - $methodReflection->getDeclaringClass()->getName() === $this->collectionClass - || $methodReflection->getDeclaringClass()->isSubclassOf($this->collectionClass) - ) - && $methodReflection->getName() === self::IS_EMPTY_METHOD_NAME; + return $methodReflection->getDeclaringClass()->is($this->collectionClass) + && $methodReflection->getName() === self::IS_EMPTY_METHOD_NAME; } public function specifyTypes( From 4a0d9cb9f9216d040e8d10f00fda2d25a995235e Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Mon, 24 Mar 2025 10:19:24 +0100 Subject: [PATCH 61/71] Adapt for recent PHPStan --- composer.json | 2 +- .../DBAL/RowCountMethodDynamicReturnTypeExtension.php | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 8bd0d8f0..5c9b02bb 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.12.12" + "phpstan/phpstan": "^1.12.23" }, "conflict": { "doctrine/collections": "<1.0", diff --git a/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php b/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php index 8fe17348..c754a41b 100644 --- a/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/DBAL/RowCountMethodDynamicReturnTypeExtension.php @@ -66,9 +66,6 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method } $resultClass = $this->getResultClass($driver); - if ($resultClass === null) { - return null; - } if (!$this->reflectionProvider->hasClass($resultClass)) { return null; @@ -87,9 +84,9 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method /** * @param DriverDetector::* $driver - * @return class-string|null + * @return class-string */ - private function getResultClass(string $driver): ?string + private function getResultClass(string $driver): string { switch ($driver) { case DriverDetector::IBM_DB2: @@ -111,8 +108,6 @@ private function getResultClass(string $driver): ?string case DriverDetector::SQLSRV: return 'Doctrine\DBAL\Driver\SQLSrv\Result'; } - - return null; } } From 6d00a6c08c934dd02b9d77b1ec32518378922e63 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 02:32:35 +0000 Subject: [PATCH 62/71] Update metcalfc/changelog-generator action to v4.5.0 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b1a669a9..be6cad08 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: - name: Generate changelog id: changelog - uses: metcalfc/changelog-generator@v4.3.1 + uses: metcalfc/changelog-generator@v4.5.0 with: myToken: ${{ secrets.PHPSTAN_BOT_TOKEN }} From ce89f09ef294aae03e08133313014d8d542d835a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 02:34:18 +0000 Subject: [PATCH 63/71] Update metcalfc/changelog-generator action to v4.6.2 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index be6cad08..b8c96d48 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: - name: Generate changelog id: changelog - uses: metcalfc/changelog-generator@v4.5.0 + uses: metcalfc/changelog-generator@v4.6.2 with: myToken: ${{ secrets.PHPSTAN_BOT_TOKEN }} From 6317ec1d1ba6cd4ad6ee1e78f17ece1d1eb7f3de Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 19 Apr 2025 22:03:22 +0200 Subject: [PATCH 64/71] Fix build --- composer.json | 4 ++-- phpstan-baseline.neon | 54 +++++++++++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/composer.json b/composer.json index 1fd25ef6..24ea4782 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.1.10" + "phpstan/phpstan": "^2.1.13" }, "conflict": { "doctrine/collections": "<1.0", @@ -31,7 +31,7 @@ "gedmo/doctrine-extensions": "^3.8", "nesbot/carbon": "^2.49", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.2", "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6.20", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index cc1e2048..73d72b45 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,71 +1,85 @@ parameters: ignoreErrors: - - message: "#^Calling PHPStan\\\\Type\\\\ParserNodeTypeToPHPStanType\\:\\:resolve\\(\\) is not covered by backward compatibility promise\\. The method might change in a minor PHPStan version\\.$#" - count: 1 - path: src/Rules/Doctrine/ORM/EntityColumnRule.php + message: '#^Call to internal method Doctrine\\DBAL\\Connection\:\:getParams\(\) from outside its root namespace Doctrine\.$#' + identifier: method.internal + count: 2 + path: src/Doctrine/Driver/DriverDetector.php - - message: "#^Calling PHPStan\\\\Type\\\\TypehintHelper\\:\\:decideType\\(\\) is not covered by backward compatibility promise\\. The method might change in a minor PHPStan version\\.$#" + message: '#^Calling PHPStan\\Type\\TypehintHelper\:\:decideType\(\) is not covered by backward compatibility promise\. The method might change in a minor PHPStan version\.$#' + identifier: phpstanApi.method count: 1 path: src/Rules/Doctrine/ORM/EntityColumnRule.php - - message: "#^Calling PHPStan\\\\Type\\\\ParserNodeTypeToPHPStanType\\:\\:resolve\\(\\) is not covered by backward compatibility promise\\. The method might change in a minor PHPStan version\\.$#" - count: 1 - path: src/Rules/Doctrine/ORM/EntityRelationRule.php - - - - message: "#^Calling PHPStan\\\\Type\\\\TypehintHelper\\:\\:decideType\\(\\) is not covered by backward compatibility promise\\. The method might change in a minor PHPStan version\\.$#" + message: '#^Calling PHPStan\\Type\\TypehintHelper\:\:decideType\(\) is not covered by backward compatibility promise\. The method might change in a minor PHPStan version\.$#' + identifier: phpstanApi.method count: 1 path: src/Rules/Doctrine/ORM/EntityRelationRule.php - - message: "#^PHPDoc tag @var with type class\\-string is not subtype of native type 'Doctrine\\\\\\\\Bundle…'\\.$#" + message: '#^PHPDoc tag @var with type class\-string is not subtype of native type ''Doctrine\\\\Bundle…''\.$#' + identifier: varTag.nativeType count: 1 path: src/Stubs/Doctrine/StubFilesExtensionLoader.php - - message: "#^Accessing PHPStan\\\\Rules\\\\Classes\\\\InstantiationRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Accessing PHPStan\\Rules\\Classes\\InstantiationRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' + identifier: phpstanApi.classConstant count: 1 path: tests/Classes/DoctrineProxyForbiddenClassNamesExtensionTest.php - - message: "#^Accessing PHPStan\\\\Rules\\\\DeadCode\\\\UnusedPrivatePropertyRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Call to internal method PHPUnit\\Framework\\TestCase\:\:dataName\(\) from outside its root namespace PHPUnit\.$#' + identifier: method.internal + count: 14 + path: tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php + + - + message: '#^Accessing PHPStan\\Rules\\DeadCode\\UnusedPrivatePropertyRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' + identifier: phpstanApi.classConstant count: 1 path: tests/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php - - message: "#^Accessing PHPStan\\\\Rules\\\\Methods\\\\CallMethodsRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Accessing PHPStan\\Rules\\Methods\\CallMethodsRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' + identifier: phpstanApi.classConstant count: 1 path: tests/Rules/Doctrine/ORM/MagicRepositoryMethodCallRuleTest.php - - message: "#^Accessing PHPStan\\\\Rules\\\\Exceptions\\\\CatchWithUnthrownExceptionRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Accessing PHPStan\\Rules\\Exceptions\\CatchWithUnthrownExceptionRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' + identifier: phpstanApi.classConstant count: 1 path: tests/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php - - message: "#^Accessing PHPStan\\\\Rules\\\\Exceptions\\\\TooWideMethodThrowTypeRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Accessing PHPStan\\Rules\\Exceptions\\TooWideMethodThrowTypeRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' + identifier: phpstanApi.classConstant count: 1 path: tests/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php - - message: "#^Accessing PHPStan\\\\Rules\\\\DeadCode\\\\UnusedPrivatePropertyRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Accessing PHPStan\\Rules\\DeadCode\\UnusedPrivatePropertyRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' + identifier: phpstanApi.classConstant count: 1 path: tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php - - message: "#^Accessing PHPStan\\\\Rules\\\\DeadCode\\\\UnusedPrivatePropertyRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Accessing PHPStan\\Rules\\DeadCode\\UnusedPrivatePropertyRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' + identifier: phpstanApi.classConstant count: 1 path: tests/Rules/Properties/MissingGedmoPropertyAssignRuleTest.php - - message: "#^Accessing PHPStan\\\\Rules\\\\Properties\\\\MissingReadOnlyByPhpDocPropertyAssignRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Accessing PHPStan\\Rules\\Properties\\MissingReadOnlyByPhpDocPropertyAssignRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' + identifier: phpstanApi.classConstant count: 1 path: tests/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php - - message: "#^Accessing PHPStan\\\\Rules\\\\Properties\\\\MissingReadOnlyPropertyAssignRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Accessing PHPStan\\Rules\\Properties\\MissingReadOnlyPropertyAssignRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' + identifier: phpstanApi.classConstant count: 1 path: tests/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php From 93d1307c50079c9d8447d72f73ca0b888cb75a07 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 24 Apr 2025 17:49:40 +0200 Subject: [PATCH 65/71] Fix build --- phpstan-baseline.neon | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 73d72b45..15848bab 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -24,6 +24,12 @@ parameters: count: 1 path: src/Stubs/Doctrine/StubFilesExtensionLoader.php + - + message: '#^Parameter references internal interface Doctrine\\ORM\\Query\\AST\\Phase2OptimizableConditional in its type\.$#' + identifier: parameter.internalInterface + count: 2 + path: src/Type/Doctrine/Query/QueryResultTypeWalker.php + - message: '#^Accessing PHPStan\\Rules\\Classes\\InstantiationRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' identifier: phpstanApi.classConstant From 8e76755816cece825b91c96034f969f673864593 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Thu, 24 Apr 2025 17:47:35 +0200 Subject: [PATCH 66/71] Use stable PHP 8.4 with xdebug in docker image --- tests/Platform/docker/Dockerfile84 | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/Platform/docker/Dockerfile84 b/tests/Platform/docker/Dockerfile84 index 81ac9834..30b47ed2 100644 --- a/tests/Platform/docker/Dockerfile84 +++ b/tests/Platform/docker/Dockerfile84 @@ -1,4 +1,4 @@ -FROM php:8.4.0beta4-cli +FROM php:8.4-cli # MSSQL RUN apt update \ @@ -12,13 +12,8 @@ RUN apt update \ && pecl install pdo_sqlsrv \ && docker-php-ext-enable sqlsrv pdo_sqlsrv -RUN set -ex \ - && apt update \ - && apt install -y bash zip libpq-dev libsqlite3-dev \ - && pecl install xdebug-3.4 mongodb \ - && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \ - && docker-php-ext-install pdo mysqli pgsql pdo_mysql pdo_pgsql pdo_sqlite \ - && docker-php-ext-enable mongodb # TODO xdebug not yet supported here +COPY ./docker-setup.sh /opt/src/scripts/setup.sh +RUN /opt/src/scripts/setup.sh COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer From b22695147e835fdcd117624a7f9ad2c943c80af7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 25 Apr 2025 22:36:15 +0200 Subject: [PATCH 67/71] Fix build --- phpstan-baseline.neon | 50 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 15848bab..01f874ed 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -6,6 +6,12 @@ parameters: count: 2 path: src/Doctrine/Driver/DriverDetector.php + - + message: '#^Access to constant on deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\.$#' + identifier: classConstant.deprecatedClass + count: 1 + path: src/Doctrine/Mapping/ClassMetadataFactory.php + - message: '#^Calling PHPStan\\Type\\TypehintHelper\:\:decideType\(\) is not covered by backward compatibility promise\. The method might change in a minor PHPStan version\.$#' identifier: phpstanApi.method @@ -25,11 +31,47 @@ parameters: path: src/Stubs/Doctrine/StubFilesExtensionLoader.php - - message: '#^Parameter references internal interface Doctrine\\ORM\\Query\\AST\\Phase2OptimizableConditional in its type\.$#' + message: '#^Catching deprecated class Doctrine\\Common\\CommonException\.$#' + identifier: catch.deprecatedClass + count: 1 + path: src/Type/Doctrine/CreateQueryDynamicReturnTypeExtension.php + + - + message: '#^Catching deprecated class Doctrine\\ORM\\ORMException\.$#' + identifier: catch.deprecatedClass + count: 1 + path: src/Type/Doctrine/CreateQueryDynamicReturnTypeExtension.php + + - + message: '#^Access to constant on deprecated class Doctrine\\DBAL\\Types\\ArrayType\.$#' + identifier: classConstant.deprecatedClass + count: 1 + path: src/Type/Doctrine/Descriptors/ArrayType.php + + - + message: '#^Access to constant on deprecated class Doctrine\\DBAL\\Types\\ObjectType\.$#' + identifier: classConstant.deprecatedClass + count: 1 + path: src/Type/Doctrine/Descriptors/ObjectType.php + + - + message: '#^Parameter \$condExpr references internal interface Doctrine\\ORM\\Query\\AST\\Phase2OptimizableConditional in its type\.$#' identifier: parameter.internalInterface count: 2 path: src/Type/Doctrine/Query/QueryResultTypeWalker.php + - + message: '#^Catching deprecated class Doctrine\\Common\\CommonException\.$#' + identifier: catch.deprecatedClass + count: 1 + path: src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php + + - + message: '#^Catching deprecated class Doctrine\\ORM\\ORMException\.$#' + identifier: catch.deprecatedClass + count: 1 + path: src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php + - message: '#^Accessing PHPStan\\Rules\\Classes\\InstantiationRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' identifier: phpstanApi.classConstant @@ -48,6 +90,12 @@ parameters: count: 1 path: tests/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php + - + message: '#^Access to constant on deprecated class Doctrine\\DBAL\\Types\\ArrayType\.$#' + identifier: classConstant.deprecatedClass + count: 1 + path: tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php + - message: '#^Accessing PHPStan\\Rules\\Methods\\CallMethodsRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' identifier: phpstanApi.classConstant From 1a479e5d212bb5f9191a164d28df7e89c1bb5cc2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Apr 2025 11:30:51 +0200 Subject: [PATCH 68/71] Fix build --- phpstan-baseline.neon | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 01f874ed..38a63aef 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -55,9 +55,15 @@ parameters: path: src/Type/Doctrine/Descriptors/ObjectType.php - - message: '#^Parameter \$condExpr references internal interface Doctrine\\ORM\\Query\\AST\\Phase2OptimizableConditional in its type\.$#' + message: '#^Parameter \$condExpr of method PHPStan\\Type\\Doctrine\\Query\\QueryResultTypeWalker\:\:walkConditionalExpression\(\) has typehint with internal interface Doctrine\\ORM\\Query\\AST\\Phase2OptimizableConditional\.$#' identifier: parameter.internalInterface - count: 2 + count: 1 + path: src/Type/Doctrine/Query/QueryResultTypeWalker.php + + - + message: '#^Parameter \$condExpr of method PHPStan\\Type\\Doctrine\\Query\\QueryResultTypeWalker\:\:walkJoinAssociationDeclaration\(\) has typehint with internal interface Doctrine\\ORM\\Query\\AST\\Phase2OptimizableConditional\.$#' + identifier: parameter.internalInterface + count: 1 path: src/Type/Doctrine/Query/QueryResultTypeWalker.php - From 913dac215f5f5ba8ba800780243461f4a9859e31 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Apr 2025 11:36:04 +0200 Subject: [PATCH 69/71] Fix test-projects.yml --- .github/workflows/test-projects.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-projects.yml b/.github/workflows/test-projects.yml index 2b85f70e..6f3a7fb7 100644 --- a/.github/workflows/test-projects.yml +++ b/.github/workflows/test-projects.yml @@ -26,4 +26,4 @@ jobs: token: ${{ secrets.REPO_ACCESS_TOKEN }} repository: "${{ matrix.repository }}" event-type: test_phpstan - client-payload: '{"ref": "1.11.x"}' + client-payload: '{"ref": "${{ github.ref_name }}"}' From 3bf5dc8cf97dd1fd8b2d8ba160b400fc7afeee2f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Apr 2025 22:06:31 +0200 Subject: [PATCH 70/71] Fix build --- phpstan-baseline.neon | 112 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 8 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 38a63aef..64b1a032 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -7,11 +7,23 @@ parameters: path: src/Doctrine/Driver/DriverDetector.php - - message: '#^Access to constant on deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\.$#' + message: ''' + #^Access to constant on deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' identifier: classConstant.deprecatedClass count: 1 path: src/Doctrine/Mapping/ClassMetadataFactory.php + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecatedClass + count: 1 + path: src/Doctrine/Mapping/ClassMetadataFactory.php + - message: '#^Calling PHPStan\\Type\\TypehintHelper\:\:decideType\(\) is not covered by backward compatibility promise\. The method might change in a minor PHPStan version\.$#' identifier: phpstanApi.method @@ -31,25 +43,37 @@ parameters: path: src/Stubs/Doctrine/StubFilesExtensionLoader.php - - message: '#^Catching deprecated class Doctrine\\Common\\CommonException\.$#' + message: ''' + #^Catching deprecated class Doctrine\\Common\\CommonException\: + The doctrine/common package is deprecated, please use specific packages and their exceptions instead\.$# + ''' identifier: catch.deprecatedClass count: 1 path: src/Type/Doctrine/CreateQueryDynamicReturnTypeExtension.php - - message: '#^Catching deprecated class Doctrine\\ORM\\ORMException\.$#' + message: ''' + #^Catching deprecated class Doctrine\\ORM\\ORMException\: + Use Doctrine\\ORM\\Exception\\ORMException for catch and instanceof$# + ''' identifier: catch.deprecatedClass count: 1 path: src/Type/Doctrine/CreateQueryDynamicReturnTypeExtension.php - - message: '#^Access to constant on deprecated class Doctrine\\DBAL\\Types\\ArrayType\.$#' + message: ''' + #^Access to constant on deprecated class Doctrine\\DBAL\\Types\\ArrayType\: + Use \{@link JsonType\} instead\.$# + ''' identifier: classConstant.deprecatedClass count: 1 path: src/Type/Doctrine/Descriptors/ArrayType.php - - message: '#^Access to constant on deprecated class Doctrine\\DBAL\\Types\\ObjectType\.$#' + message: ''' + #^Access to constant on deprecated class Doctrine\\DBAL\\Types\\ObjectType\: + Use \{@link JsonType\} instead\.$# + ''' identifier: classConstant.deprecatedClass count: 1 path: src/Type/Doctrine/Descriptors/ObjectType.php @@ -67,13 +91,19 @@ parameters: path: src/Type/Doctrine/Query/QueryResultTypeWalker.php - - message: '#^Catching deprecated class Doctrine\\Common\\CommonException\.$#' + message: ''' + #^Catching deprecated class Doctrine\\Common\\CommonException\: + The doctrine/common package is deprecated, please use specific packages and their exceptions instead\.$# + ''' identifier: catch.deprecatedClass count: 1 path: src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php - - message: '#^Catching deprecated class Doctrine\\ORM\\ORMException\.$#' + message: ''' + #^Catching deprecated class Doctrine\\ORM\\ORMException\: + Use Doctrine\\ORM\\Exception\\ORMException for catch and instanceof$# + ''' identifier: catch.deprecatedClass count: 1 path: src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php @@ -84,12 +114,39 @@ parameters: count: 1 path: tests/Classes/DoctrineProxyForbiddenClassNamesExtensionTest.php + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecatedClass + count: 1 + path: tests/Classes/entity-manager.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecatedClass + count: 1 + path: tests/DoctrineIntegration/ORM/entity-manager.php + - message: '#^Call to internal method PHPUnit\\Framework\\TestCase\:\:dataName\(\) from outside its root namespace PHPUnit\.$#' identifier: method.internal count: 14 path: tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecatedClass + count: 1 + path: tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php + - message: '#^Accessing PHPStan\\Rules\\DeadCode\\UnusedPrivatePropertyRule\:\:class is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' identifier: phpstanApi.classConstant @@ -97,7 +154,19 @@ parameters: path: tests/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php - - message: '#^Access to constant on deprecated class Doctrine\\DBAL\\Types\\ArrayType\.$#' + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecatedClass + count: 1 + path: tests/Rules/DeadCode/entity-manager.php + + - + message: ''' + #^Access to constant on deprecated class Doctrine\\DBAL\\Types\\ArrayType\: + Use \{@link JsonType\} instead\.$# + ''' identifier: classConstant.deprecatedClass count: 1 path: tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php @@ -143,3 +212,30 @@ parameters: identifier: phpstanApi.classConstant count: 1 path: tests/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecatedClass + count: 1 + path: tests/Rules/Properties/entity-manager.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecatedClass + count: 1 + path: tests/Type/Doctrine/DBAL/mysqli.php + + - + message: ''' + #^Instantiation of deprecated class Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver\: + This class will be removed in 3\.0 without replacement\.$# + ''' + identifier: new.deprecatedClass + count: 1 + path: tests/Type/Doctrine/DBAL/pdo.php From b1209a31a7ffdfc31cd37142a03e172a97827265 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Apr 2025 22:11:07 +0200 Subject: [PATCH 71/71] Fix test-projects.yml --- .github/workflows/test-projects.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-projects.yml b/.github/workflows/test-projects.yml index 6f3a7fb7..cebc3431 100644 --- a/.github/workflows/test-projects.yml +++ b/.github/workflows/test-projects.yml @@ -26,4 +26,4 @@ jobs: token: ${{ secrets.REPO_ACCESS_TOKEN }} repository: "${{ matrix.repository }}" event-type: test_phpstan - client-payload: '{"ref": "${{ github.ref_name }}"}' + client-payload: '{"ref": "2.1.x"}'