Bug: Can't use integers as discriminator values with ORM v3 #7336

Closed
opened 2026-01-22 15:50:13 +01:00 by admin · 1 comment
Owner

Originally created by @imba28 on GitHub (Mar 5, 2024).

Bug Report

Q A
BC Break yes
Version 3.0.x

Summary

A while ago, 0db36a9607 stopped implementing type-specific handling when quoting values (Statement::quote()). Moreover, it explicitly added a string type hint. When trying to upgrade to dbal v4 / orm v3 we noticed that this breaks single table inheritance when configuring integers as discriminator values as briefly outlined in https://github.com/doctrine/orm/discussions/9921.

In that case, SqlWalker passes an integer to Connection::quote() when generating the discriminator column SQL condition:

This results in a type error.

I guess since we can no longer rely on doctrine/dbal to resolve the parameters for us doctrine/orm needs to somehow consider the discriminator column's type and preprocess the SQL parameters accordingly.

Current behavior

When configuring integers as discriminator values Doctrine triggers a type error whenever the application generates queries;

[TypeError] Doctrine\DBAL\Connection::quote(): Argument #1 ($value) must be of type string, int given, called in /var/www/html/vendor/doctrine/orm/src/Query/SqlWalker.php on line 387  

How to reproduce

Here are two functional test that demonstrate the bug:

DiscriminatorColumn(type: 'integer')

<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Tests\OrmFunctionalTestCase;
use Generator;
use PHPUnit\Framework\Attributes\DataProvider;

class IntegerDiscriminatorValueTest extends OrmFunctionalTestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        $this->setUpEntitySchema([
            IntegerBaseClass::class,
            IntegerFooEntity::class,
            IntegerBarEntity::class,
        ]);
    }

    public static function dqlStatements(): Generator
    {
        yield ['SELECT e FROM ' . IntegerBaseClass::class . ' e', '/WHERE g0_.type IN \(1, 2\)$/'];
        yield ['SELECT e FROM ' . IntegerFooEntity::class . ' e', '/WHERE g0_.type IN \(1\)$/'];
        yield ['SELECT e FROM ' . IntegerBarEntity::class . ' e', '/WHERE g0_.type IN \(2\)$/'];
    }

    #[DataProvider('dqlStatements')]
    public function testIntegerDiscriminatorValue(string $dql, string $expectedDiscriminatorValues): void
    {
        $query = $this->_em->createQuery($dql);
        $sql   = $query->getSQL();

        self::assertMatchesRegularExpression($expectedDiscriminatorValues, $sql);
    }
}

#[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
{
}

DiscriminatorColumn(type: 'string')

<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Tests\OrmFunctionalTestCase;
use Generator;
use PHPUnit\Framework\Attributes\DataProvider;

class StringIntegerEnumDiscriminatorValueTest extends OrmFunctionalTestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        $this->setUpEntitySchema([
            IntegerBaseClass::class,
            IntegerFooEntity::class,
            IntegerBarEntity::class,
        ]);
    }

    public static function dqlStatements(): Generator
    {
        yield ['SELECT e FROM ' . IntegerBaseClass::class . ' e', '/WHERE g0_.type IN \(\'1\', \'2\'\)$/'];
        yield ['SELECT e FROM ' . IntegerFooEntity::class . ' e', '/WHERE g0_.type IN \(\'1\'\)$/'];
        yield ['SELECT e FROM ' . IntegerBarEntity::class . ' e', '/WHERE g0_.type IN \(\'2\'\)$/'];
    }

    #[DataProvider('dqlStatements')]
    public function testIntegerDiscriminatorValue(string $dql, string $expectedDiscriminatorValues): void
    {
        $query = $this->_em->createQuery($dql);
        $sql   = $query->getSQL();

        self::assertMatchesRegularExpression($expectedDiscriminatorValues, $sql);
    }
}

#[ORM\Entity]
#[ORM\Table(name: 'numeric_string_discriminator')]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
#[ORM\DiscriminatorMap([
    '1' => EnumFooEntity::class,
    '2' => EnumBarEntity::class,
])]
class EnumBaseClass
{
    #[ORM\Id]
    #[ORM\GeneratedValue(strategy: 'IDENTITY')]
    #[ORM\Column(type: 'integer')]
    private int|null $id = null;
}

#[ORM\Entity]
class EnumFooEntity extends IntegerBaseClass
{
}

#[ORM\Entity]
class EnumBarEntity extends IntegerBaseClass
{
}

Expected behavior

  1. When configuring a discriminator column of type integer I'd expect all queries to contain unquoted parameters: WHERE type IN (1, 2)

    Example

    #[ORM\DiscriminatorColumn(name: 'type', type: 'integer')]
    #[ORM\DiscriminatorMap([
        1 => IntegerFooEntity::class,
        2 => IntegerBarEntity::class,
    ])]
    
  2. However, If I changed the type to string I'd like to see the parameters to be quoted instead even if the discriminator values contain numeric strings: WHERE type IN ('1', '2'). This should cover use cases where people are forced to base the inheritance on enums such as ENUM('1','2','3')..

    #[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
    #[ORM\DiscriminatorMap([
        '1' => EnumFooEntity::class,
        '2' => EnumBarEntity::class,
    ])]
    
Originally created by @imba28 on GitHub (Mar 5, 2024). ### Bug Report | Q | A |------------ | ------ | BC Break | yes | Version | 3.0.x #### Summary A while ago, https://github.com/doctrine/dbal/commit/0db36a9607f70cd1e5f54b7de75569195d38eaed stopped implementing type-specific handling when quoting values (`Statement::quote()`). Moreover, it explicitly added a `string` type hint. When trying to upgrade to dbal v4 / orm v3 we noticed that this breaks single table inheritance when configuring integers as discriminator values as briefly outlined in https://github.com/doctrine/orm/discussions/9921. In that case, `SqlWalker` passes an integer to `Connection::quote()` when generating the discriminator column SQL condition: - https://github.com/doctrine/orm/blob/c3cc0fdd8c9ffb102170c930ca56188626a34719/src/Query/SqlWalker.php#L387 - https://github.com/doctrine/orm/blob/c3cc0fdd8c9ffb102170c930ca56188626a34719/src/Query/SqlWalker.php#L2250 This results in a type error. I guess since we can no longer rely on `doctrine/dbal` to resolve the parameters for us `doctrine/orm` needs to somehow consider the discriminator column's type and preprocess the SQL parameters accordingly. #### Current behavior When configuring integers as discriminator values Doctrine triggers a type error whenever the application generates queries; ``` [TypeError] Doctrine\DBAL\Connection::quote(): Argument #1 ($value) must be of type string, int given, called in /var/www/html/vendor/doctrine/orm/src/Query/SqlWalker.php on line 387 ``` #### How to reproduce Here are two functional test that demonstrate the bug: **DiscriminatorColumn(type: 'integer')** ```php <?php declare(strict_types=1); namespace Doctrine\Tests\ORM\Functional\Ticket; use Doctrine\ORM\Mapping as ORM; use Doctrine\Tests\OrmFunctionalTestCase; use Generator; use PHPUnit\Framework\Attributes\DataProvider; class IntegerDiscriminatorValueTest extends OrmFunctionalTestCase { protected function setUp(): void { parent::setUp(); $this->setUpEntitySchema([ IntegerBaseClass::class, IntegerFooEntity::class, IntegerBarEntity::class, ]); } public static function dqlStatements(): Generator { yield ['SELECT e FROM ' . IntegerBaseClass::class . ' e', '/WHERE g0_.type IN \(1, 2\)$/']; yield ['SELECT e FROM ' . IntegerFooEntity::class . ' e', '/WHERE g0_.type IN \(1\)$/']; yield ['SELECT e FROM ' . IntegerBarEntity::class . ' e', '/WHERE g0_.type IN \(2\)$/']; } #[DataProvider('dqlStatements')] public function testIntegerDiscriminatorValue(string $dql, string $expectedDiscriminatorValues): void { $query = $this->_em->createQuery($dql); $sql = $query->getSQL(); self::assertMatchesRegularExpression($expectedDiscriminatorValues, $sql); } } #[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 { } ``` **DiscriminatorColumn(type: 'string')** ```php <?php declare(strict_types=1); namespace Doctrine\Tests\ORM\Functional\Ticket; use Doctrine\ORM\Mapping as ORM; use Doctrine\Tests\OrmFunctionalTestCase; use Generator; use PHPUnit\Framework\Attributes\DataProvider; class StringIntegerEnumDiscriminatorValueTest extends OrmFunctionalTestCase { protected function setUp(): void { parent::setUp(); $this->setUpEntitySchema([ IntegerBaseClass::class, IntegerFooEntity::class, IntegerBarEntity::class, ]); } public static function dqlStatements(): Generator { yield ['SELECT e FROM ' . IntegerBaseClass::class . ' e', '/WHERE g0_.type IN \(\'1\', \'2\'\)$/']; yield ['SELECT e FROM ' . IntegerFooEntity::class . ' e', '/WHERE g0_.type IN \(\'1\'\)$/']; yield ['SELECT e FROM ' . IntegerBarEntity::class . ' e', '/WHERE g0_.type IN \(\'2\'\)$/']; } #[DataProvider('dqlStatements')] public function testIntegerDiscriminatorValue(string $dql, string $expectedDiscriminatorValues): void { $query = $this->_em->createQuery($dql); $sql = $query->getSQL(); self::assertMatchesRegularExpression($expectedDiscriminatorValues, $sql); } } #[ORM\Entity] #[ORM\Table(name: 'numeric_string_discriminator')] #[ORM\InheritanceType('SINGLE_TABLE')] #[ORM\DiscriminatorColumn(name: 'type', type: 'string')] #[ORM\DiscriminatorMap([ '1' => EnumFooEntity::class, '2' => EnumBarEntity::class, ])] class EnumBaseClass { #[ORM\Id] #[ORM\GeneratedValue(strategy: 'IDENTITY')] #[ORM\Column(type: 'integer')] private int|null $id = null; } #[ORM\Entity] class EnumFooEntity extends IntegerBaseClass { } #[ORM\Entity] class EnumBarEntity extends IntegerBaseClass { } ``` #### Expected behavior 1. When configuring a discriminator column of type `integer` I'd expect all queries to contain unquoted parameters: `WHERE type IN (1, 2)` *Example* ```php #[ORM\DiscriminatorColumn(name: 'type', type: 'integer')] #[ORM\DiscriminatorMap([ 1 => IntegerFooEntity::class, 2 => IntegerBarEntity::class, ])] ``` 2. However, If I changed the type to `string` I'd like to see the parameters to be quoted instead even if the discriminator values contain numeric strings: `WHERE type IN ('1', '2')`. This should cover use cases where people are forced to base the inheritance on enums such as `ENUM('1','2','3').`. ```php #[ORM\DiscriminatorColumn(name: 'type', type: 'string')] #[ORM\DiscriminatorMap([ '1' => EnumFooEntity::class, '2' => EnumBarEntity::class, ])] ```
admin closed this issue 2026-01-22 15:50:14 +01:00
Author
Owner

@imba28 commented on GitHub (Jul 8, 2024):

Fixed by https://github.com/doctrine/orm/pull/11425

@imba28 commented on GitHub (Jul 8, 2024): Fixed by https://github.com/doctrine/orm/pull/11425
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7336