From 6e40d3f83ab4956d79e79dc1568297cac133826c Mon Sep 17 00:00:00 2001 From: Vincent Langlet <vincentlanglet@hotmail.fr> Date: Sat, 17 Feb 2024 18:02:15 +0100 Subject: [PATCH 1/4] Refacto queryResultTypeWalkerTest --- phpunit.xml | 1 + ...t.php => PDOQueryResultTypeWalkerTest.php} | 312 +--------------- .../Query/QueryResultTypeWalkerTestCase.php | 346 ++++++++++++++++++ 3 files changed, 350 insertions(+), 309 deletions(-) rename tests/Type/Doctrine/Query/{QueryResultTypeWalkerTest.php => PDOQueryResultTypeWalkerTest.php} (81%) create mode 100644 tests/Type/Doctrine/Query/QueryResultTypeWalkerTestCase.php diff --git a/phpunit.xml b/phpunit.xml index 6d69639a..8c5f02c9 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -32,6 +32,7 @@ <testsuites> <testsuite name="PHPStan for Doctrine"> + <directory suffix="TestCase.php">tests</directory> <directory suffix="Test.php">tests</directory> </testsuite> </testsuites> diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/PDOQueryResultTypeWalkerTest.php similarity index 81% rename from tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php rename to tests/Type/Doctrine/Query/PDOQueryResultTypeWalkerTest.php index ea1e0aea..9a8312b5 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/PDOQueryResultTypeWalkerTest.php @@ -6,232 +6,35 @@ use Composer\Semver\VersionParser; use DateTime; use DateTimeImmutable; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\Column; -use Doctrine\ORM\Query\AST\TypedExpression; -use Doctrine\ORM\Tools\SchemaTool; -use PHPStan\Testing\PHPStanTestCase; -use PHPStan\Type\Accessory\AccessoryNumericStringType; -use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; -use PHPStan\Type\ConstantTypeHelper; -use PHPStan\Type\Doctrine\DescriptorRegistry; use PHPStan\Type\FloatType; -use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; -use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -use PHPStan\Type\UnionType; -use PHPStan\Type\VerbosityLevel; -use QueryResult\Entities\Embedded; -use QueryResult\Entities\JoinedChild; use QueryResult\Entities\Many; use QueryResult\Entities\ManyId; -use QueryResult\Entities\NestedEmbedded; use QueryResult\Entities\One; use QueryResult\Entities\OneId; -use QueryResult\Entities\SingleTableChild; -use QueryResult\EntitiesEnum\EntityWithEnum; use QueryResult\EntitiesEnum\IntEnum; use QueryResult\EntitiesEnum\StringEnum; -use Throwable; -use function array_merge; -use function array_shift; use function assert; use function class_exists; -use function count; use function property_exists; -use function sprintf; use function strpos; -use function version_compare; use const PHP_VERSION_ID; -final class QueryResultTypeWalkerTest extends PHPStanTestCase +final class PDOQueryResultTypeWalkerTest extends QueryResultTypeWalkerTestCase { - /** @var EntityManagerInterface */ - private static $em; - - /** @var DescriptorRegistry */ - private $descriptorRegistry; - - public static function getAdditionalConfigFiles(): array - { - return [ - __DIR__ . '/../data/QueryResult/config.neon', - ]; - } - - public static function setUpBeforeClass(): void - { - $em = require __DIR__ . '/../data/QueryResult/entity-manager.php'; - self::$em = $em; - - $schemaTool = new SchemaTool($em); - $classes = $em->getMetadataFactory()->getAllMetadata(); - $schemaTool->createSchema($classes); - - $dataOne = [ - 'intColumn' => [1, 2], - 'stringColumn' => ['A', 'B'], - 'stringNullColumn' => ['A', null], - ]; - - $dataMany = [ - 'intColumn' => [1, 2], - 'stringColumn' => ['A', 'B'], - 'stringNullColumn' => ['A', null], - ]; - - $dataJoinedInheritance = [ - 'parentColumn' => [1, 2], - 'parentNullColumn' => [1, null], - 'childColumn' => [1, 2], - 'childNullColumn' => [1, null], - ]; - - $dataSingleTableInheritance = [ - 'parentColumn' => [1, 2], - 'parentNullColumn' => [1, null], - 'childNullColumn' => [1, null], - ]; - - $id = 1; - - foreach (self::combinations($dataOne) as $combination) { - [$intColumn, $stringColumn, $stringNullColumn] = $combination; - $one = new One(); - $one->id = (string) $id++; - $one->intColumn = $intColumn; - $one->stringColumn = $stringColumn; - $one->stringNullColumn = $stringNullColumn; - $embedded = new Embedded(); - $embedded->intColumn = $intColumn; - $embedded->stringColumn = $stringColumn; - $embedded->stringNullColumn = $stringNullColumn; - $nestedEmbedded = new NestedEmbedded(); - $nestedEmbedded->intColumn = $intColumn; - $nestedEmbedded->stringColumn = $stringColumn; - $nestedEmbedded->stringNullColumn = $stringNullColumn; - $embedded->nestedEmbedded = $nestedEmbedded; - $one->embedded = $embedded; - $one->manies = new ArrayCollection(); - - foreach (self::combinations($dataMany) as $combinationMany) { - [$intColumnMany, $stringColumnMany, $stringNullColumnMany] = $combinationMany; - $many = new Many(); - $many->id = (string) $id++; - $many->intColumn = $intColumnMany; - $many->stringColumn = $stringColumnMany; - $many->stringNullColumn = $stringNullColumnMany; - $many->datetimeColumn = new DateTime('2001-01-01 00:00:00'); - $many->datetimeImmutableColumn = new DateTimeImmutable('2001-01-01 00:00:00'); - $many->simpleArrayColumn = ['foo']; - $many->one = $one; - $one->manies->add($many); - $em->persist($many); - } - - $em->persist($one); - } - - foreach (self::combinations($dataJoinedInheritance) as $combination) { - [$parentColumn, $parentNullColumn, $childColumn, $childNullColumn] = $combination; - $child = new JoinedChild(); - $child->id = (string) $id++; - $child->parentColumn = $parentColumn; - $child->parentNullColumn = $parentNullColumn; - $child->childColumn = $childColumn; - $child->childNullColumn = $childNullColumn; - $em->persist($child); - } - - foreach (self::combinations($dataSingleTableInheritance) as $combination) { - [$parentColumn, $parentNullColumn, $childNullColumn] = $combination; - $child = new SingleTableChild(); - $child->id = (string) $id++; - $child->parentColumn = $parentColumn; - $child->parentNullColumn = $parentNullColumn; - $child->childNullColumn = $childNullColumn; - $em->persist($child); - } - - if (property_exists(Column::class, 'enumType') && PHP_VERSION_ID >= 80100) { - assert(class_exists(StringEnum::class)); - assert(class_exists(IntEnum::class)); - - $entityWithEnum = new EntityWithEnum(); - $entityWithEnum->id = '1'; - $entityWithEnum->stringEnumColumn = StringEnum::A; - $entityWithEnum->intEnumColumn = IntEnum::A; - $entityWithEnum->intEnumOnStringColumn = IntEnum::A; - $em->persist($entityWithEnum); - } - - $em->flush(); - } - - public static function tearDownAfterClass(): void - { - self::$em->clear(); - } - - public function setUp(): void + protected static function getEntityManagerPath(): string { - $this->descriptorRegistry = self::getContainer()->getByType(DescriptorRegistry::class); - } - - /** @dataProvider getTestData */ - public function test(Type $expectedType, string $dql, ?string $expectedExceptionMessage = null, ?string $expectedDeprecationMessage = null): void - { - $em = self::$em; - - $query = $em->createQuery($dql); - - $typeBuilder = new QueryResultTypeBuilder(); - - if ($expectedExceptionMessage !== null) { - $this->expectException(Throwable::class); - $this->expectExceptionMessage($expectedExceptionMessage); - } elseif ($expectedDeprecationMessage !== null) { - $this->expectDeprecation(); - $this->expectDeprecationMessage($expectedDeprecationMessage); - } - - QueryResultTypeWalker::walk($query, $typeBuilder, $this->descriptorRegistry); - - $type = $typeBuilder->getResultType(); - - self::assertSame( - $expectedType->describe(VerbosityLevel::precise()), - $type->describe(VerbosityLevel::precise()) - ); - - // Double-check our expectations - - $query = $em->createQuery($dql); - - $result = $query->getResult(); - self::assertGreaterThan(0, count($result)); - - foreach ($result as $row) { - $rowType = ConstantTypeHelper::getTypeFromValue($row); - self::assertTrue( - $type->accepts($rowType, true)->yes(), - sprintf( - "The inferred type\n%s\nshould accept actual type\n%s", - $type->describe(VerbosityLevel::precise()), - $rowType->describe(VerbosityLevel::precise()) - ) - ); - } + return __DIR__ . '/../data/QueryResult/entity-manager.php'; } /** @@ -1591,113 +1394,4 @@ public function getTestData(): iterable ]; } - /** - * @param array<int,array{0: ConstantIntegerType|ConstantStringType, 1: Type, 2?: bool}> $elements - */ - private function constantArray(array $elements): Type - { - $builder = ConstantArrayTypeBuilder::createEmpty(); - - foreach ($elements as $element) { - $offsetType = $element[0]; - $valueType = $element[1]; - $optional = $element[2] ?? false; - $builder->setOffsetValueType($offsetType, $valueType, $optional); - } - - return $builder->getArray(); - } - - private function numericStringOrInt(): Type - { - return new UnionType([ - new IntegerType(), - new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]), - ]); - } - - private function numericString(): Type - { - return new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]); - } - - private function uint(): Type - { - return IntegerRangeType::fromInterval(0, null); - } - - private function intStringified(): Type - { - return TypeCombinator::union( - new IntegerType(), - $this->numericString() - ); - } - private function uintStringified(): Type - { - return TypeCombinator::union( - $this->uint(), - $this->numericString() - ); - } - - private function numericStringified(): Type - { - return TypeCombinator::union( - new FloatType(), - new IntegerType(), - $this->numericString() - ); - } - - private function unumericStringified(): Type - { - return TypeCombinator::union( - new FloatType(), - IntegerRangeType::fromInterval(0, null), - $this->numericString() - ); - } - - private function hasTypedExpressions(): bool - { - return class_exists(TypedExpression::class); - } - - /** - * @param array<mixed[]> $arrays - * - * @return iterable<mixed[]> - */ - private static function combinations(array $arrays): iterable - { - if ($arrays === []) { - yield []; - return; - } - - $head = array_shift($arrays); - - foreach ($head as $elem) { - foreach (self::combinations($arrays) as $combination) { - yield array_merge([$elem], $combination); - } - } - } - - private function isDoctrine211(): bool - { - $version = InstalledVersions::getVersion('doctrine/orm'); - - return $version !== null - && version_compare($version, '2.11', '>=') - && version_compare($version, '2.12', '<'); - } - } diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTestCase.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTestCase.php new file mode 100644 index 00000000..39f79124 --- /dev/null +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTestCase.php @@ -0,0 +1,346 @@ +<?php declare(strict_types = 1); + +namespace PHPStan\Type\Doctrine\Query; + +use Composer\InstalledVersions; +use DateTime; +use DateTimeImmutable; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Query\AST\TypedExpression; +use Doctrine\ORM\Tools\SchemaTool; +use PHPStan\Testing\PHPStanTestCase; +use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Constant\ConstantArrayTypeBuilder; +use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\ConstantTypeHelper; +use PHPStan\Type\Doctrine\DescriptorRegistry; +use PHPStan\Type\FloatType; +use PHPStan\Type\IntegerRangeType; +use PHPStan\Type\IntegerType; +use PHPStan\Type\IntersectionType; +use PHPStan\Type\StringType; +use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; +use PHPStan\Type\UnionType; +use PHPStan\Type\VerbosityLevel; +use QueryResult\Entities\Embedded; +use QueryResult\Entities\JoinedChild; +use QueryResult\Entities\Many; +use QueryResult\Entities\NestedEmbedded; +use QueryResult\Entities\One; +use QueryResult\Entities\SingleTableChild; +use QueryResult\EntitiesEnum\EntityWithEnum; +use QueryResult\EntitiesEnum\IntEnum; +use QueryResult\EntitiesEnum\StringEnum; +use Throwable; +use function array_merge; +use function array_shift; +use function assert; +use function class_exists; +use function count; +use function property_exists; +use function sprintf; +use function version_compare; +use const PHP_VERSION_ID; + +abstract class QueryResultTypeWalkerTestCase extends PHPStanTestCase +{ + /** @var EntityManagerInterface */ + private static $em; + + /** @var DescriptorRegistry */ + private $descriptorRegistry; + + abstract protected static function getEntityManagerPath(): string; + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/../data/QueryResult/config.neon', + ]; + } + + public static function setUpBeforeClass(): void + { + $em = require static::getEntityManagerPath(); + self::$em = $em; + + $schemaTool = new SchemaTool($em); + $classes = $em->getMetadataFactory()->getAllMetadata(); + $schemaTool->createSchema($classes); + + $dataOne = [ + 'intColumn' => [1, 2], + 'stringColumn' => ['A', 'B'], + 'stringNullColumn' => ['A', null], + ]; + + $dataMany = [ + 'intColumn' => [1, 2], + 'stringColumn' => ['A', 'B'], + 'stringNullColumn' => ['A', null], + ]; + + $dataJoinedInheritance = [ + 'parentColumn' => [1, 2], + 'parentNullColumn' => [1, null], + 'childColumn' => [1, 2], + 'childNullColumn' => [1, null], + ]; + + $dataSingleTableInheritance = [ + 'parentColumn' => [1, 2], + 'parentNullColumn' => [1, null], + 'childNullColumn' => [1, null], + ]; + + $id = 1; + + foreach (self::combinations($dataOne) as $combination) { + [$intColumn, $stringColumn, $stringNullColumn] = $combination; + $one = new One(); + $one->id = (string) $id++; + $one->intColumn = $intColumn; + $one->stringColumn = $stringColumn; + $one->stringNullColumn = $stringNullColumn; + $embedded = new Embedded(); + $embedded->intColumn = $intColumn; + $embedded->stringColumn = $stringColumn; + $embedded->stringNullColumn = $stringNullColumn; + $nestedEmbedded = new NestedEmbedded(); + $nestedEmbedded->intColumn = $intColumn; + $nestedEmbedded->stringColumn = $stringColumn; + $nestedEmbedded->stringNullColumn = $stringNullColumn; + $embedded->nestedEmbedded = $nestedEmbedded; + $one->embedded = $embedded; + $one->manies = new ArrayCollection(); + + foreach (self::combinations($dataMany) as $combinationMany) { + [$intColumnMany, $stringColumnMany, $stringNullColumnMany] = $combinationMany; + $many = new Many(); + $many->id = (string) $id++; + $many->intColumn = $intColumnMany; + $many->stringColumn = $stringColumnMany; + $many->stringNullColumn = $stringNullColumnMany; + $many->datetimeColumn = new DateTime('2001-01-01 00:00:00'); + $many->datetimeImmutableColumn = new DateTimeImmutable('2001-01-01 00:00:00'); + $many->simpleArrayColumn = ['foo']; + $many->one = $one; + $one->manies->add($many); + $em->persist($many); + } + + $em->persist($one); + } + + foreach (self::combinations($dataJoinedInheritance) as $combination) { + [$parentColumn, $parentNullColumn, $childColumn, $childNullColumn] = $combination; + $child = new JoinedChild(); + $child->id = (string) $id++; + $child->parentColumn = $parentColumn; + $child->parentNullColumn = $parentNullColumn; + $child->childColumn = $childColumn; + $child->childNullColumn = $childNullColumn; + $em->persist($child); + } + + foreach (self::combinations($dataSingleTableInheritance) as $combination) { + [$parentColumn, $parentNullColumn, $childNullColumn] = $combination; + $child = new SingleTableChild(); + $child->id = (string) $id++; + $child->parentColumn = $parentColumn; + $child->parentNullColumn = $parentNullColumn; + $child->childNullColumn = $childNullColumn; + $em->persist($child); + } + + if (property_exists(Column::class, 'enumType') && PHP_VERSION_ID >= 80100) { + assert(class_exists(StringEnum::class)); + assert(class_exists(IntEnum::class)); + + $entityWithEnum = new EntityWithEnum(); + $entityWithEnum->id = '1'; + $entityWithEnum->stringEnumColumn = StringEnum::A; + $entityWithEnum->intEnumColumn = IntEnum::A; + $entityWithEnum->intEnumOnStringColumn = IntEnum::A; + $em->persist($entityWithEnum); + } + + $em->flush(); + } + + public static function tearDownAfterClass(): void + { + self::$em->clear(); + } + + public function setUp(): void + { + $this->descriptorRegistry = self::getContainer()->getByType(DescriptorRegistry::class); + } + + /** @dataProvider getTestData */ + public function test(Type $expectedType, string $dql, ?string $expectedExceptionMessage = null, ?string $expectedDeprecationMessage = null): void + { + $em = self::$em; + + $query = $em->createQuery($dql); + + $typeBuilder = new QueryResultTypeBuilder(); + + if ($expectedExceptionMessage !== null) { + $this->expectException(Throwable::class); + $this->expectExceptionMessage($expectedExceptionMessage); + } elseif ($expectedDeprecationMessage !== null) { + $this->expectDeprecation(); + $this->expectDeprecationMessage($expectedDeprecationMessage); + } + + QueryResultTypeWalker::walk($query, $typeBuilder, $this->descriptorRegistry); + + $type = $typeBuilder->getResultType(); + + self::assertSame( + $expectedType->describe(VerbosityLevel::precise()), + $type->describe(VerbosityLevel::precise()) + ); + + // Double-check our expectations + + $query = $em->createQuery($dql); + + $result = $query->getResult(); + self::assertGreaterThan(0, count($result)); + + foreach ($result as $row) { + $rowType = ConstantTypeHelper::getTypeFromValue($row); + self::assertTrue( + $type->accepts($rowType, true)->yes(), + sprintf( + "The inferred type\n%s\nshould accept actual type\n%s", + $type->describe(VerbosityLevel::precise()), + $rowType->describe(VerbosityLevel::precise()) + ) + ); + } + } + + /** + * @return iterable<string,array{Type,string,2?:string|null}> + */ + abstract public function getTestData(): iterable; + + /** + * @param array<int,array{0: ConstantIntegerType|ConstantStringType, 1: Type, 2?: bool}> $elements + */ + protected function constantArray(array $elements): Type + { + $builder = ConstantArrayTypeBuilder::createEmpty(); + + foreach ($elements as $element) { + $offsetType = $element[0]; + $valueType = $element[1]; + $optional = $element[2] ?? false; + $builder->setOffsetValueType($offsetType, $valueType, $optional); + } + + return $builder->getArray(); + } + + protected function numericStringOrInt(): Type + { + return new UnionType([ + new IntegerType(), + new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]), + ]); + } + + protected function numericString(): Type + { + return new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]); + } + + protected function uint(): Type + { + return IntegerRangeType::fromInterval(0, null); + } + + protected function intStringified(): Type + { + return TypeCombinator::union( + new IntegerType(), + $this->numericString() + ); + } + + protected function uintStringified(): Type + { + return TypeCombinator::union( + $this->uint(), + $this->numericString() + ); + } + + protected function numericStringified(): Type + { + return TypeCombinator::union( + new FloatType(), + new IntegerType(), + $this->numericString() + ); + } + + protected function unumericStringified(): Type + { + return TypeCombinator::union( + new FloatType(), + IntegerRangeType::fromInterval(0, null), + $this->numericString() + ); + } + + protected function hasTypedExpressions(): bool + { + return class_exists(TypedExpression::class); + } + + /** + * @param array<mixed[]> $arrays + * + * @return iterable<mixed[]> + */ + private static function combinations(array $arrays): iterable + { + if ($arrays === []) { + yield []; + return; + } + + $head = array_shift($arrays); + + foreach ($head as $elem) { + foreach (self::combinations($arrays) as $combination) { + yield array_merge([$elem], $combination); + } + } + } + + protected function isDoctrine211(): bool + { + $version = InstalledVersions::getVersion('doctrine/orm'); + + return $version !== null + && version_compare($version, '2.11', '>=') + && version_compare($version, '2.12', '<'); + } + +} From 6fa0a11d15c940497f5d1f7c9525fe2c4a0e66fc Mon Sep 17 00:00:00 2001 From: Vincent Langlet <vincentlanglet@hotmail.fr> Date: Sat, 17 Feb 2024 18:51:02 +0100 Subject: [PATCH 2/4] Poc --- .github/workflows/test-platforms.yml | 58 +++++++++++++++++++ Makefile | 2 +- .../Query/MysqlQueryResultTypeWalkerTest.php | 17 ++++++ .../Query/PDOQueryResultTypeWalkerTest.php | 6 +- .../Query/QueryResultTypeWalkerTestCase.php | 7 ++- .../data/QueryResult/entity-manager-mysql.php | 40 +++++++++++++ 6 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/test-platforms.yml create mode 100644 tests/Type/Doctrine/Query/MysqlQueryResultTypeWalkerTest.php create mode 100644 tests/Type/Doctrine/data/QueryResult/entity-manager-mysql.php diff --git a/.github/workflows/test-platforms.yml b/.github/workflows/test-platforms.yml new file mode 100644 index 00000000..aec2723d --- /dev/null +++ b/.github/workflows/test-platforms.yml @@ -0,0 +1,58 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +name: "Test on platforms" + +on: + pull_request: + push: + branches: + - "1.3.x" + +jobs: + test-mysql: + name: "Tests mysql" + runs-on: "ubuntu-latest" + + env: + DATABASE_URL: mysql://root@127.0.0.1:3306/phpstan_doctrine + + services: + mysql: + image: mysql:${{ matrix.mysql-version }} + options: >- + --health-cmd "mysqladmin ping --silent" + --name=mysqlcontainer + -e MYSQL_ALLOW_EMPTY_PASSWORD=yes + -e MYSQL_DATABASE=phpstan_doctrine + ports: + - 3306:3306 + + strategy: + fail-fast: false + matrix: + php-version: + - "8.2" + mysql-version: + - '5.7' + - '8.0' + + steps: + - name: "Checkout" + uses: actions/checkout@v3 + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "${{ matrix.php-version }}" + ini-file: development + extensions: "mongodb" + + - name: "Install dependencies" + run: "composer update --no-interaction --no-progress" + + - name: Change mysql sql_mode + run: "docker exec mysqlcontainer mysql -u root -e \"SET GLOBAL sql_mode = '';\"" + + - name: "Tests" + run: "vendor/bin/phpunit --group=mysql" diff --git a/Makefile b/Makefile index 2ec6452c..382dfa5c 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ check: lint cs tests phpstan .PHONY: tests tests: - php vendor/bin/phpunit + php vendor/bin/phpunit --exclude-group=platform .PHONY: lint lint: diff --git a/tests/Type/Doctrine/Query/MysqlQueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/MysqlQueryResultTypeWalkerTest.php new file mode 100644 index 00000000..cefeee97 --- /dev/null +++ b/tests/Type/Doctrine/Query/MysqlQueryResultTypeWalkerTest.php @@ -0,0 +1,17 @@ +<?php declare(strict_types = 1); + +namespace PHPStan\Type\Doctrine\Query; + +/** + * @group platform + * @group mysql + */ +class MysqlQueryResultTypeWalkerTest extends PDOQueryResultTypeWalkerTest +{ + + protected static function getEntityManagerPath(): string + { + return __DIR__ . '/../data/QueryResult/entity-manager-mysql.php'; + } + +} diff --git a/tests/Type/Doctrine/Query/PDOQueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/PDOQueryResultTypeWalkerTest.php index 9a8312b5..4473d152 100644 --- a/tests/Type/Doctrine/Query/PDOQueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/PDOQueryResultTypeWalkerTest.php @@ -15,7 +15,6 @@ use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; -use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use QueryResult\Entities\Many; use QueryResult\Entities\ManyId; @@ -29,7 +28,7 @@ use function strpos; use const PHP_VERSION_ID; -final class PDOQueryResultTypeWalkerTest extends QueryResultTypeWalkerTestCase +class PDOQueryResultTypeWalkerTest extends QueryResultTypeWalkerTestCase { protected static function getEntityManagerPath(): string @@ -37,9 +36,6 @@ protected static function getEntityManagerPath(): string return __DIR__ . '/../data/QueryResult/entity-manager.php'; } - /** - * @return iterable<string,array{Type,string,2?:string|null}> - */ public function getTestData(): iterable { $ormVersion = InstalledVersions::getVersion('doctrine/orm'); diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTestCase.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTestCase.php index 39f79124..319ac1a3 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTestCase.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTestCase.php @@ -48,8 +48,9 @@ abstract class QueryResultTypeWalkerTestCase extends PHPStanTestCase { + /** @var EntityManagerInterface */ - private static $em; + protected static $em; /** @var DescriptorRegistry */ private $descriptorRegistry; @@ -182,7 +183,9 @@ public function setUp(): void $this->descriptorRegistry = self::getContainer()->getByType(DescriptorRegistry::class); } - /** @dataProvider getTestData */ + /** + * @dataProvider getTestData + */ public function test(Type $expectedType, string $dql, ?string $expectedExceptionMessage = null, ?string $expectedDeprecationMessage = null): void { $em = self::$em; diff --git a/tests/Type/Doctrine/data/QueryResult/entity-manager-mysql.php b/tests/Type/Doctrine/data/QueryResult/entity-manager-mysql.php new file mode 100644 index 00000000..c7887826 --- /dev/null +++ b/tests/Type/Doctrine/data/QueryResult/entity-manager-mysql.php @@ -0,0 +1,40 @@ +<?php declare(strict_types = 1); + +use Cache\Adapter\PHPArray\ArrayCachePool; +use Doctrine\Common\Annotations\AnnotationReader; +use Doctrine\DBAL\DriverManager; +use Doctrine\ORM\Configuration; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Driver\AnnotationDriver; +use Doctrine\ORM\Mapping\Driver\AttributeDriver; +use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; + +$config = new Configuration(); +$config->setProxyDir(__DIR__); +$config->setProxyNamespace('PHPstan\Doctrine\OrmProxies'); +$config->setMetadataCache(new ArrayCachePool()); + +$metadataDriver = new MappingDriverChain(); + +$metadataDriver->addDriver(new AnnotationDriver( + new AnnotationReader(), + [__DIR__ . '/Entities'] +), 'QueryResult\Entities\\'); + +if (property_exists(Column::class, 'enumType') && PHP_VERSION_ID >= 80100) { + $metadataDriver->addDriver(new AnnotationDriver( + new AnnotationReader(), + [__DIR__ . '/EntitiesEnum'] + ), 'QueryResult\EntitiesEnum\\'); +} + +$config->setMetadataDriverImpl($metadataDriver); + +return new EntityManager( + DriverManager::getConnection([ + 'driver' => 'mysql', + 'url' => 'mysql://root@127.0.0.1:3306/phpstan_doctrine', + ]), + $config +); From 4498953c3fb14994d4d66ccdcca8f61e6037f099 Mon Sep 17 00:00:00 2001 From: Vincent Langlet <vincentlanglet@hotmail.fr> Date: Sat, 17 Feb 2024 19:33:57 +0100 Subject: [PATCH 3/4] Fix query --- tests/Type/Doctrine/Query/PDOQueryResultTypeWalkerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Type/Doctrine/Query/PDOQueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/PDOQueryResultTypeWalkerTest.php index 4473d152..f433ed09 100644 --- a/tests/Type/Doctrine/Query/PDOQueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/PDOQueryResultTypeWalkerTest.php @@ -389,7 +389,7 @@ public function getTestData(): iterable [new ConstantIntegerType(1), new MixedType()], ]), ' - SELECT (SELECT m.intColumn FROM QueryResult\Entities\Many m) + SELECT (SELECT COUNT(m.intColumn) FROM QueryResult\Entities\Many m) FROM QueryResult\Entities\Many m2 ', ]; From 7f753d465960cf8a27fa7dfc212c00cbd300bf52 Mon Sep 17 00:00:00 2001 From: Vincent Langlet <vincentlanglet@hotmail.fr> Date: Tue, 20 Feb 2024 11:23:32 +0100 Subject: [PATCH 4/4] Add missing config --- .../Type/Doctrine/Query/MysqlQueryResultTypeWalkerTest.php | 6 ++++++ tests/Type/Doctrine/data/QueryResult/config-mysql.neon | 7 +++++++ 2 files changed, 13 insertions(+) create mode 100644 tests/Type/Doctrine/data/QueryResult/config-mysql.neon diff --git a/tests/Type/Doctrine/Query/MysqlQueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/MysqlQueryResultTypeWalkerTest.php index cefeee97..af84e121 100644 --- a/tests/Type/Doctrine/Query/MysqlQueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/MysqlQueryResultTypeWalkerTest.php @@ -8,6 +8,12 @@ */ class MysqlQueryResultTypeWalkerTest extends PDOQueryResultTypeWalkerTest { + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/../data/QueryResult/config-mysql.neon', + ]; + } protected static function getEntityManagerPath(): string { diff --git a/tests/Type/Doctrine/data/QueryResult/config-mysql.neon b/tests/Type/Doctrine/data/QueryResult/config-mysql.neon new file mode 100644 index 00000000..588a9821 --- /dev/null +++ b/tests/Type/Doctrine/data/QueryResult/config-mysql.neon @@ -0,0 +1,7 @@ +includes: + - ../../../../../extension.neon +parameters: + doctrine: + objectManagerLoader: entity-manager-mysql.php + featureToggles: + listType: true