Doctrine 2.6 : Inheritance (instance of) does not work #5844

Closed
opened 2026-01-22 15:19:42 +01:00 by admin · 13 comments
Owner

Originally created by @christophe-mailfert on GitHub (Jan 11, 2018).

Originally assigned to: @lcobucci on GitHub.

Hi guys,

Doctrine ORM version : 2.6.0

I'm currently migrating my project from symfony3 to symfony4 flex and my unit tests are failing. The reason:

Instance of does not generate correct sql request

Generated sql

WHERE (p0_.type IN ('stop', 'interest') AND p0_.client_id = 1)

Instead of

WHERE (p0_.type IN ('interest') AND p0_.client_id = 1)

Code in repository:

switch ($filters['type']) {
                case 'interest':
                    $query->from(Point::class, 'p')
                        ->andWhere($builder->expr()->isInstanceOf('p', Point::class));
                    break;
                case 'bus_stop':
                    $query->from(Point::class, 'p')
                        ->andWhere($builder->expr()->isInstanceOf('p', Point\BusStop::class));
                    break;
                default:
                    throw new InvalidArgumentException(sprintf('filters "type" %s is not a valid value.', $filters['type']));
            }

See you :)

Originally created by @christophe-mailfert on GitHub (Jan 11, 2018). Originally assigned to: @lcobucci on GitHub. Hi guys, Doctrine ORM version : 2.6.0 I'm currently migrating my project from symfony3 to symfony4 flex and my unit tests are failing. The reason: Instance of does not generate correct sql request Generated sql ``` WHERE (p0_.type IN ('stop', 'interest') AND p0_.client_id = 1) ``` Instead of ``` WHERE (p0_.type IN ('interest') AND p0_.client_id = 1) ``` Code in repository: ``` switch ($filters['type']) { case 'interest': $query->from(Point::class, 'p') ->andWhere($builder->expr()->isInstanceOf('p', Point::class)); break; case 'bus_stop': $query->from(Point::class, 'p') ->andWhere($builder->expr()->isInstanceOf('p', Point\BusStop::class)); break; default: throw new InvalidArgumentException(sprintf('filters "type" %s is not a valid value.', $filters['type'])); } ``` See you :)
admin added the BugInvalidMissing Tests labels 2026-01-22 15:19:42 +01:00
admin closed this issue 2026-01-22 15:19:43 +01:00
Author
Owner

@Majkl578 commented on GitHub (Jan 11, 2018):

Can you please also share your entities/mapping to illustrate the inheritance tree? ideally as a failing test case that reproduces the bug? It'd be really helpful in order to identify the issue.
You can find test examples on https://github.com/doctrine/doctrine2/tree/v2.6.0/tests/Doctrine/Tests/ORM/Functional/Ticket.

Thanks.

@Majkl578 commented on GitHub (Jan 11, 2018): Can you please also share your entities/mapping to illustrate the inheritance tree? ideally as a failing test case that reproduces the bug? It'd be really helpful in order to identify the issue. You can find test examples on https://github.com/doctrine/doctrine2/tree/v2.6.0/tests/Doctrine/Tests/ORM/Functional/Ticket. Thanks.
Author
Owner

@christophe-mailfert commented on GitHub (Jan 11, 2018):

Sure here is my entities:

/**
 * @ORM\Entity(repositoryClass="App\Repository\PointRepository")
 * @ORM\Table(name="point", options={"collate"="utf8_general_ci", "charset"="utf8", "engine"="InnoDB"})
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="type", type="string")
 * @ORM\DiscriminatorMap({"interest" = "Point", "stop" = "App\Entity\Point\BusStop"})
 */
class Point
{
    /**
     * @var int
     *
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer", name="id")
     */
    protected $id;

...
}
/**
 * @ORM\Entity()
 * @ORM\Table(name="stop_point", options={"collate"="utf8_general_ci", "charset"="utf8", "engine"="InnoDB"})
 */
class BusStop extends Point
{
...
}

I will add a test as soon as possible :).

@christophe-mailfert commented on GitHub (Jan 11, 2018): Sure here is my entities: ``` /** * @ORM\Entity(repositoryClass="App\Repository\PointRepository") * @ORM\Table(name="point", options={"collate"="utf8_general_ci", "charset"="utf8", "engine"="InnoDB"}) * @ORM\InheritanceType("SINGLE_TABLE") * @ORM\DiscriminatorColumn(name="type", type="string") * @ORM\DiscriminatorMap({"interest" = "Point", "stop" = "App\Entity\Point\BusStop"}) */ class Point { /** * @var int * * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") * @ORM\Column(type="integer", name="id") */ protected $id; ... } ``` ``` /** * @ORM\Entity() * @ORM\Table(name="stop_point", options={"collate"="utf8_general_ci", "charset"="utf8", "engine"="InnoDB"}) */ class BusStop extends Point { ... } ``` I will add a test as soon as possible :).
Author
Owner

@lcobucci commented on GitHub (Jan 15, 2018):

@christophe-mailfert the SQL is being generated correctly, what's happening is that you're not using inheritance correctly.

Objects with type BusStop are also an instance of Point, e.g.:

$elem = new BusStop();

var_dump($elem instanceof BusStop());
var_dump($elem instanceof Point());

Therefore if you're using $builder->expr()->isInstanceOf('p', Point::class), the generated SQL will use interest AND stop as filter.

This was a bug in v2.5.x, which was fixed in v2.6 (#6392).

@lcobucci commented on GitHub (Jan 15, 2018): @christophe-mailfert the SQL is being generated correctly, what's happening is that you're not using inheritance correctly. Objects with type `BusStop` are also an instance of `Point`, e.g.: ```php $elem = new BusStop(); var_dump($elem instanceof BusStop()); var_dump($elem instanceof Point()); ``` Therefore if you're using `$builder->expr()->isInstanceOf('p', Point::class)`, the generated SQL will use `interest` AND `stop` as filter. This was a bug in v2.5.x, which was fixed in v2.6 (#6392).
Author
Owner

@christophe-mailfert commented on GitHub (Jan 15, 2018):

Hey Icobucci,

Thx for your response.

So with my current schema, it's not possible to handle inheritance ?

Should i transform Point to an Abstract class and create a child InterestPoint who extends AbstractPoint ? or there is another solution with current schema ?

Thx for advance.

@christophe-mailfert commented on GitHub (Jan 15, 2018): Hey Icobucci, Thx for your response. So with my current schema, it's not possible to handle inheritance ? Should i transform Point to an Abstract class and create a child InterestPoint who extends AbstractPoint ? or there is another solution with current schema ? Thx for advance.
Author
Owner

@lcobucci commented on GitHub (Jan 15, 2018):

So with my current schema, it's not possible to handle inheritance ?

Should i transform Point to an Abstract class and create a child InterestPoint who extends AbstractPoint ? or there is another solution with current schema ?

That's indeed a viable solution, and it won't require a redesign of your models.

@lcobucci commented on GitHub (Jan 15, 2018): > So with my current schema, it's not possible to handle inheritance ? > > Should i transform Point to an Abstract class and create a child InterestPoint who extends AbstractPoint ? or there is another solution with current schema ? That's indeed a viable solution, and it won't require a redesign of your models.
Author
Owner

@koseduhemak commented on GitHub (Jan 18, 2018):

The following does also not work in 2.6.0:

/**
 * Class Project
 *
 * @Entity(repositoryClass="ProjectRepository")
 * @InheritanceType("SINGLE_TABLE")
 * @Table(options={"collate"="utf8mb4_unicode_ci", "charset"="utf8mb4"})
 */
class Project
{
    ...
}
/**
 * Class SubProject
 *
 * @Entity
 * @Table(options={"collate"="utf8mb4_unicode_ci", "charset"="utf8mb4"})
 */
class SubProject extends Project
{
    ...
}

Problem is that $array = $this->entityManager->getRepository(Project::class)->findAll(); does not return any entities of type SubProject, only of type Project, even SubProject is of type Project though. However, there are many null values (I think every SubProject gets hydrated as null value) in the resulting $array.

The same code works with 2.5.14.

@koseduhemak commented on GitHub (Jan 18, 2018): The following does also not work in `2.6.0`: ```php /** * Class Project * * @Entity(repositoryClass="ProjectRepository") * @InheritanceType("SINGLE_TABLE") * @Table(options={"collate"="utf8mb4_unicode_ci", "charset"="utf8mb4"}) */ class Project { ... } ``` ```php /** * Class SubProject * * @Entity * @Table(options={"collate"="utf8mb4_unicode_ci", "charset"="utf8mb4"}) */ class SubProject extends Project { ... } ``` Problem is that `$array = $this->entityManager->getRepository(Project::class)->findAll();` does not return any entities of type `SubProject`, only of type `Project`, even `SubProject` is of type `Project` though. However, there are many `null` values (I think every `SubProject` gets hydrated as `null` value) in the resulting `$array`. The same code works with `2.5.14`.
Author
Owner

@lcobucci commented on GitHub (Jan 18, 2018):

@koseduhemak your mapping seems invalid to me, since it doesn't follow any of the guidelines explained in http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html.

That might be the cause of not getting the correct data, however it's kind of scary that it used to work on v2.5.14.

@lcobucci commented on GitHub (Jan 18, 2018): @koseduhemak your mapping seems invalid to me, since it doesn't follow any of the guidelines explained in http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html. That might be the cause of not getting the correct data, however it's kind of scary that it used to work on `v2.5.14`.
Author
Owner

@koseduhemak commented on GitHub (Jan 18, 2018):

@lcobucci Sorry, did some mistakes by copying and simplifying my example.
Corrected it in my previous answer (it was way more complex, but I tried to reduce complexity and make a shorter, easier to understand example).
I followed the guidelines here: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#single-table-inheritance

@koseduhemak commented on GitHub (Jan 18, 2018): @lcobucci Sorry, did some mistakes by copying and simplifying my example. Corrected it in my previous answer (it was way more complex, but I tried to reduce complexity and make a shorter, easier to understand example). I followed the guidelines here: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#single-table-inheritance
Author
Owner

@lcobucci commented on GitHub (Jan 18, 2018):

@koseduhemak so you really don't have a @DiscriminatorMap? Could you try to send a PR with a failing functional test with this scenario (targeting 2.6)?

@lcobucci commented on GitHub (Jan 18, 2018): @koseduhemak so you really don't have a `@DiscriminatorMap`? Could you try to send a PR with a failing functional test with this scenario (targeting `2.6`)?
Author
Owner

@koseduhemak commented on GitHub (Jan 18, 2018):

@lcobucci nope, I do not explicitly specify a @DiscriminatorMap. I thought (and according to the docs), we do not necessarily specify one: "If no discriminator map is provided, then the map is generated automatically. The automatically generated discriminator map contains the lowercase short name of each class as key." (Source: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#single-table-inheritance, last bullet).

However, I tried to create a functional test reproducing my issue without luck. I tried 2.6 but also 2.5 and in both cases I get an exception Doctrine\ORM\Mapping\MappingException: Entity 'Doctrine\Tests\Models\Project\SubProject' has to be part of the discriminator map of 'Doctrine\Tests\Models\Project\Project' to be properly mapped in the inheritance hierarchy. Alternatively you can make 'Doctrine\Tests\Models\Project\SubProject' an abstract class to avoid this exception from occurring..

This leads to the question, why, in my own project, I am able to specify SINGLE_TABLE inheritances without @DiscrimintatorMap.

@koseduhemak commented on GitHub (Jan 18, 2018): @lcobucci nope, I do not explicitly specify a `@DiscriminatorMap`. I thought (and according to the docs), we do not necessarily specify one: "If no discriminator map is provided, then the map is generated automatically. The automatically generated discriminator map contains the lowercase short name of each class as key." (Source: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#single-table-inheritance, last bullet). However, I tried to create a functional test reproducing my issue without luck. I tried `2.6` but also `2.5` and in both cases I get an exception `Doctrine\ORM\Mapping\MappingException: Entity 'Doctrine\Tests\Models\Project\SubProject' has to be part of the discriminator map of 'Doctrine\Tests\Models\Project\Project' to be properly mapped in the inheritance hierarchy. Alternatively you can make 'Doctrine\Tests\Models\Project\SubProject' an abstract class to avoid this exception from occurring.`. This leads to the question, why, in my own project, I am able to specify `SINGLE_TABLE` inheritances without `@DiscrimintatorMap`.
Author
Owner

@insekticid commented on GitHub (Jan 3, 2019):

same problem here with doctrine/orm (v2.6.3) - using InheritanceType("JOINED")

/**
 *
 * @ORM\InheritanceType("JOINED")
 *
 * @ORM\DiscriminatorColumn(name="item", type="string")
 *
 * @ORM\DiscriminatorMap({"article"="Article", "page"="Page"})
 *
 * @ORM\Entity()
 */
class Article
{
}
/**
 *
 * @ORM\Entity()
 */
class Page extends Article
{

}

$qb = $entityManager->getRepository(Article::class)->createQueryBuilder('a');
$qb->where($qb->expr()->isInstanceOf('a', Article::class));
SELECT 
...
        FROM 
          article a0_ 
          LEFT JOIN page p2_ ON a0_.id = p2_.id 
        WHERE 
          (
            a0_.item IN ('page', 'article')
          ) 

Workaround:.

$qb->andWhere($qb->expr()->not($qb->expr()->isInstanceOf('a', Page::class)));

EDIT:
now I see, that this behavior is feature :(

There should be some option to override this behavior

@ORM\DiscriminatorColumn(name="item", type="string", disableInheritanceInstanceOf=true)
@insekticid commented on GitHub (Jan 3, 2019): same problem here with doctrine/orm (v2.6.3) - using InheritanceType("JOINED") ``` /** * * @ORM\InheritanceType("JOINED") * * @ORM\DiscriminatorColumn(name="item", type="string") * * @ORM\DiscriminatorMap({"article"="Article", "page"="Page"}) * * @ORM\Entity() */ class Article { } ``` ``` /** * * @ORM\Entity() */ class Page extends Article { } ``` ``` $qb = $entityManager->getRepository(Article::class)->createQueryBuilder('a'); $qb->where($qb->expr()->isInstanceOf('a', Article::class)); ``` ``` SELECT ... FROM article a0_ LEFT JOIN page p2_ ON a0_.id = p2_.id WHERE ( a0_.item IN ('page', 'article') ) ``` Workaround:. ``` $qb->andWhere($qb->expr()->not($qb->expr()->isInstanceOf('a', Page::class))); ``` EDIT: now I see, that this behavior is feature :( There should be some option to override this behavior ``` @ORM\DiscriminatorColumn(name="item", type="string", disableInheritanceInstanceOf=true) ```
Author
Owner

@lcobucci commented on GitHub (Jan 5, 2019):

@insekticid the ORM should behave the same way as the language, there's no point in adding a flag to modify that behaviour.

In your case, I'd dare to say that you probably have a design issue when you say that a Page is an Article (that's the relation type you have when using inheritance in object oriented programming).

I'll close this as invalid since we don't have any failing test that proves the bug.

@lcobucci commented on GitHub (Jan 5, 2019): @insekticid the ORM should behave the same way as the language, there's no point in adding a flag to modify that behaviour. In your case, I'd dare to say that you probably have a design issue when you say that a Page is an Article (that's the relation type you have when using inheritance in object oriented programming). I'll close this as invalid since we don't have any failing test that proves the bug.
Author
Owner

@insekticid commented on GitHub (Jan 17, 2019):

@lcobucci

EDIT:
found problem: ýou cannot use join WITH $expr, you have to use where...

abstract class AbstractLabel {}
class DayLabel extends AbstractLabel  {}
...
class YearLabel extends AbstractLabel  {}

Not working

$expr = $qb->expr()->isInstanceOf('labels', LabelYear::class);
$qb->join('content.labels', 'labels', Expr\Join::WITH, $expr)

SELECT
	...
FROM
	article a0_
LEFT JOIN page p2_ ON a0_.id = p2_.id
INNER JOIN labels l3_ ON a0_.id = l3_.article_id
AND l3_.type IN (
	'day',
	'week',
	'month',
	'year'
)
AND (l3_.type IN('year'))

Working

$expr = $qb->expr()->isInstanceOf('labels', LabelYear::class);
$qb->join('content.labels', 'labels')
$qb->where($expr);

SELECT
	...
FROM
	article a0_
LEFT JOIN page p2_ ON a0_.id = p2_.id
INNER JOIN labels l3_ ON a0_.id = l3_.article_id
AND l3_.type IN (
	'day',
	'week',
	'month',
	'year'
)
WHERE (l3_.type IN('year'))
@insekticid commented on GitHub (Jan 17, 2019): @lcobucci EDIT: found problem: ýou cannot use join WITH $expr, you have to use where... ``` abstract class AbstractLabel {} class DayLabel extends AbstractLabel {} ... class YearLabel extends AbstractLabel {} ``` Not working ``` $expr = $qb->expr()->isInstanceOf('labels', LabelYear::class); $qb->join('content.labels', 'labels', Expr\Join::WITH, $expr) SELECT ... FROM article a0_ LEFT JOIN page p2_ ON a0_.id = p2_.id INNER JOIN labels l3_ ON a0_.id = l3_.article_id AND l3_.type IN ( 'day', 'week', 'month', 'year' ) AND (l3_.type IN('year')) ``` Working ``` $expr = $qb->expr()->isInstanceOf('labels', LabelYear::class); $qb->join('content.labels', 'labels') $qb->where($expr); SELECT ... FROM article a0_ LEFT JOIN page p2_ ON a0_.id = p2_.id INNER JOIN labels l3_ ON a0_.id = l3_.article_id AND l3_.type IN ( 'day', 'week', 'month', 'year' ) WHERE (l3_.type IN('year')) ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5844