diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..c704d7d05b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + labels: + - "CI" + target-branch: "2.19.x" diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index ac2788b39a..659da17bac 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -24,4 +24,4 @@ on: jobs: coding-standards: - uses: "doctrine/.github/.github/workflows/coding-standards.yml@3.0.0" + uses: "doctrine/.github/.github/workflows/coding-standards.yml@5.0.1" diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index e9facd697d..b84a942213 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -75,7 +75,7 @@ jobs: if: "${{ matrix.dbal-version != 'default' }}" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v2" + uses: "ramsey/composer-install@v3" with: composer-options: "--ignore-platform-req=php+" dependency-versions: "${{ matrix.deps }}" @@ -156,7 +156,7 @@ jobs: if: "${{ matrix.dbal-version != 'default' }}" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v2" + uses: "ramsey/composer-install@v3" with: composer-options: "--ignore-platform-req=php+" @@ -222,7 +222,7 @@ jobs: extensions: "${{ matrix.extension }}" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v2" + uses: "ramsey/composer-install@v3" with: composer-options: "--ignore-platform-req=php+" @@ -296,7 +296,7 @@ jobs: if: "${{ matrix.dbal-version != 'default' }}" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v2" + uses: "ramsey/composer-install@v3" with: composer-options: "--ignore-platform-req=php+" @@ -337,6 +337,8 @@ jobs: path: "reports" - name: "Upload to Codecov" - uses: "codecov/codecov-action@v3" + uses: "codecov/codecov-action@v4" with: directory: reports + env: + CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}" diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index ef8053a211..65cbad613b 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -36,7 +36,7 @@ jobs: run: "composer require --dev phpdocumentor/guides-cli --no-update" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v2" + uses: "ramsey/composer-install@v3" with: dependency-versions: "highest" diff --git a/.github/workflows/phpbench.yml b/.github/workflows/phpbench.yml index d98e7fa215..2a09ec3b18 100644 --- a/.github/workflows/phpbench.yml +++ b/.github/workflows/phpbench.yml @@ -47,15 +47,8 @@ jobs: coverage: "pcov" ini-values: "zend.assertions=1, apc.enable_cli=1" - - name: "Cache dependencies installed with composer" - uses: "actions/cache@v3" - with: - path: "~/.composer/cache" - key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" - restore-keys: "php-${{ matrix.php-version }}-composer-locked-" - - - name: "Install dependencies with composer" - run: "composer update --no-interaction --no-progress" + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@v3" - name: "Run PHPBench" run: "vendor/bin/phpbench run --report=default" diff --git a/.github/workflows/release-on-milestone-closed.yml b/.github/workflows/release-on-milestone-closed.yml index d46dc4c36b..89d4fe8bf1 100644 --- a/.github/workflows/release-on-milestone-closed.yml +++ b/.github/workflows/release-on-milestone-closed.yml @@ -7,7 +7,7 @@ on: jobs: release: - uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@4.0.0" + uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@5.0.1" secrets: GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }} GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }} diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 5082f75051..52f30872a9 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -83,7 +83,7 @@ jobs: if: "${{ matrix.dbal-version != 'default' }}" - name: Install dependencies with Composer - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@v3 - name: Run static analysis with Vimeo Psalm run: vendor/bin/psalm --shepherd diff --git a/src/Query/SqlWalker.php b/src/Query/SqlWalker.php index 018c2455e4..004d29e773 100644 --- a/src/Query/SqlWalker.php +++ b/src/Query/SqlWalker.php @@ -30,6 +30,7 @@ use function implode; use function is_array; use function is_float; +use function is_int; use function is_numeric; use function is_string; use function preg_match; @@ -384,7 +385,9 @@ private function generateDiscriminatorColumnConditionSQL(array $dqlAliases): str $values = []; if ($class->discriminatorValue !== null) { // discriminators can be 0 - $values[] = $conn->quote($class->discriminatorValue); + $values[] = $class->getDiscriminatorColumn()->type === 'integer' && is_int($class->discriminatorValue) + ? $class->discriminatorValue + : $conn->quote((string) $class->discriminatorValue); } foreach ($class->subClasses as $subclassName) { @@ -396,7 +399,9 @@ private function generateDiscriminatorColumnConditionSQL(array $dqlAliases): str continue; } - $values[] = $conn->quote((string) $subclassMetadata->discriminatorValue); + $values[] = $subclassMetadata->getDiscriminatorColumn()->type === 'integer' && is_int($subclassMetadata->discriminatorValue) + ? $subclassMetadata->discriminatorValue + : $conn->quote((string) $subclassMetadata->discriminatorValue); } if ($values !== []) { @@ -2246,8 +2251,10 @@ private function getChildDiscriminatorsFromClassMetadata( $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($metadata, $this->em); } - foreach (array_keys($discriminators) as $dis) { - $sqlParameterList[] = $this->conn->quote($dis); + foreach (array_keys($discriminators) as $discriminatorValue) { + $sqlParameterList[] = $rootClass->getDiscriminatorColumn()->type === 'integer' && is_int($discriminatorValue) + ? $discriminatorValue + : $this->conn->quote((string) $discriminatorValue); } return '(' . implode(', ', $sqlParameterList) . ')'; diff --git a/tests/Tests/ORM/Functional/Ticket/GH11341Test.php b/tests/Tests/ORM/Functional/Ticket/GH11341Test.php new file mode 100644 index 0000000000..5c35dfe86c --- /dev/null +++ b/tests/Tests/ORM/Functional/Ticket/GH11341Test.php @@ -0,0 +1,157 @@ +setUpEntitySchema([ + IntegerBaseClass::class, + IntegerFooEntity::class, + IntegerBarEntity::class, + StringAsIntBaseClass::class, + StringAsIntFooEntity::class, + StringAsIntBarEntity::class, + StringBaseClass::class, + StringFooEntity::class, + StringBarEntity::class, + ]); + } + + public static function dqlStatements(): Generator + { + yield ['SELECT e FROM ' . IntegerBaseClass::class . ' e', '/WHERE [a-z]0_.type IN \(1, 2\)$/']; + yield ['SELECT e FROM ' . IntegerFooEntity::class . ' e', '/WHERE [a-z]0_.type IN \(1\)$/']; + yield ['SELECT e FROM ' . IntegerBarEntity::class . ' e', '/WHERE [a-z]0_.type IN \(2\)$/']; + yield ['SELECT e FROM ' . StringAsIntBaseClass::class . ' e', '/WHERE [a-z]0_.type IN \(\'1\', \'2\'\)$/']; + yield ['SELECT e FROM ' . StringAsIntFooEntity::class . ' e', '/WHERE [a-z]0_.type IN \(\'1\'\)$/']; + yield ['SELECT e FROM ' . StringAsIntBarEntity::class . ' e', '/WHERE [a-z]0_.type IN \(\'2\'\)$/']; + yield ['SELECT e FROM ' . StringBaseClass::class . ' e', '/WHERE [a-z]0_.type IN \(\'1\', \'2\'\)$/']; + yield ['SELECT e FROM ' . StringFooEntity::class . ' e', '/WHERE [a-z]0_.type IN \(\'1\'\)$/']; + yield ['SELECT e FROM ' . StringBarEntity::class . ' e', '/WHERE [a-z]0_.type IN \(\'2\'\)$/']; + } + + #[DataProvider('dqlStatements')] + public function testDiscriminatorValue(string $dql, string $expectedDiscriminatorValues): void + { + $query = $this->_em->createQuery($dql); + $sql = $query->getSQL(); + + self::assertMatchesRegularExpression($expectedDiscriminatorValues, $sql); + } + + public static function dqlStatementsForInstanceOf(): Generator + { + yield [IntegerBaseClass::class, IntegerFooEntity::class]; + yield [StringBaseClass::class, StringFooEntity::class]; + yield [StringAsIntBaseClass::class, StringAsIntFooEntity::class]; + } + + /** + * @psalm-param class-string $baseClass + * @psalm-param class-string $inheritedClass + */ + #[DataProvider('dqlStatementsForInstanceOf')] + public function testInstanceOf(string $baseClass, string $inheritedClass): void + { + $this->_em->persist(new $inheritedClass()); + $this->_em->flush(); + + $dql = 'SELECT p FROM ' . $baseClass . ' p WHERE p INSTANCE OF ' . $baseClass; + + $query = $this->_em->createQuery($dql); + $result = $query->getResult(); + + self::assertCount(1, $result); + self::assertContainsOnlyInstancesOf($baseClass, $result); + } +} + +#[ORM\Entity] +#[ORM\Table(name: 'integer_discriminator')] +#[ORM\InheritanceType('SINGLE_TABLE')] +#[ORM\DiscriminatorColumn(name: 'type', type: 'integer')] +#[ORM\DiscriminatorMap([ + 1 => IntegerFooEntity::class, + 2 => IntegerBarEntity::class, +])] +class IntegerBaseClass +{ + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'IDENTITY')] + #[ORM\Column(type: 'integer')] + private int|null $id = null; +} + +#[ORM\Entity] +class IntegerFooEntity extends IntegerBaseClass +{ +} + +#[ORM\Entity] +class IntegerBarEntity extends IntegerBaseClass +{ +} + +#[ORM\Entity] +#[ORM\Table(name: 'string_as_int_discriminator')] +#[ORM\InheritanceType('SINGLE_TABLE')] +#[ORM\DiscriminatorColumn(name: 'type', type: 'string')] +#[ORM\DiscriminatorMap([ + 1 => StringAsIntFooEntity::class, + 2 => StringAsIntBarEntity::class, +])] +class StringAsIntBaseClass +{ + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'IDENTITY')] + #[ORM\Column(type: 'integer')] + private int|null $id = null; +} + +#[ORM\Entity] +class StringAsIntFooEntity extends StringAsIntBaseClass +{ +} + +#[ORM\Entity] +class StringAsIntBarEntity extends StringAsIntBaseClass +{ +} + + +#[ORM\Entity] +#[ORM\Table(name: 'string_discriminator')] +#[ORM\InheritanceType('SINGLE_TABLE')] +#[ORM\DiscriminatorColumn(name: 'type', type: 'string')] +#[ORM\DiscriminatorMap([ + '1' => StringFooEntity::class, + '2' => StringBarEntity::class, +])] +class StringBaseClass +{ + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'IDENTITY')] + #[ORM\Column(type: 'integer')] + private int|null $id = null; +} + +#[ORM\Entity] +class StringFooEntity extends StringBaseClass +{ +} + +#[ORM\Entity] +class StringBarEntity extends StringBaseClass +{ +}