Multiple level discriminator mapping does not work in combination with a self-referencing one-to-many relation. #5565

Closed
opened 2026-01-22 15:11:29 +01:00 by admin · 7 comments
Owner

Originally created by @jeroenvrooij on GitHub (Jun 7, 2017).

Originally assigned to: @Ocramius on GitHub.

Given the following structure of entities, we are experiencing fatal errors the moment we try to make use of the self-referencing relation.

diagram-167397485293621894

The multiple level discrimination on itself works fine. We can query for the object directly or get it from a related entity. But the moment we insert data in the parent_id column in the database and try to call the ‘getParent()’ method we get the following fatal error:

PHP Fatal error: Cannot instantiate abstract class FinancialBundle\Entity\Invoice\Line\SocialSupport\AbstractProduct in ..vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php on line 872

An important detail is that we CAN use the self-referencing relation when querying for the ‘AddonMedicine’ entity. So it looks like that when we use the combination of the self-referencing relation AND the multiple level discrimination something is not interpreted right and that Doctrine is trying to instantiate the ‘middle layer’, being the AbstractProduct.

These are the mapping files in use:

AbstractLine

<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping
        xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://raw.githubusercontent.com/doctrine/doctrine2/2.4/doctrine-mapping.xsd">
    <entity name="FinancialBundle\Entity\Invoice\Line\AbstractLine"
            table="invoiceline"
            inheritance-type="JOINED">

        <discriminator-column name="type" type="string" />

        <discriminator-map>
            <discriminator-mapping value="socialsupportproduct_effort" class="FinancialBundle\Entity\Invoice\Line\SocialSupport\AbstractProduct" />
            <discriminator-mapping value="socialsupportproduct_output" class="FinancialBundle\Entity\Invoice\Line\SocialSupport\AbstractProduct" />
            <discriminator-mapping value="addonmedicine" class="FinancialBundle\Entity\Invoice\Line\AddonMedicine" />
        </discriminator-map>

        <id name="id" column="id" type="integer">
            <generator strategy="AUTO" />
        </id>

        <field name="quantity" column="quantity" type="float" nullable="false" />
        <field name="price" column="price" type="float" nullable="false" />
        <field name="subtotal" column="subtotal" type="integer" nullable="false" />
        <field name="parentId" column="parent_id" type="integer" nullable="true" />


        <many-to-one field="invoice" target-entity="FinancialBundle\Entity\Invoice\AbstractInvoice" inversed-by="invoiceLines">
            <join-column name="invoice_id" referenced-column-name="id" nullable="false" />
        </many-to-one>

        <many-to-one field="invoiceLineVat" target-entity="FinancialBundle\Entity\Invoice\Line\Vat">
            <cascade>
                <cascade-persist/>
            </cascade>
            <join-column name="invoicelinevat_id" referenced-column-name="id" nullable="true" />
        </many-to-one>

    </entity>
</doctrine-mapping>

AbstractProduct

<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping
        xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://raw.githubusercontent.com/doctrine/doctrine2/2.4/doctrine-mapping.xsd">
    <entity name="FinancialBundle\Entity\Invoice\Line\SocialSupport\AbstractProduct"
            table="invoicelinesocialsupportproduct"
            repository-class="FinancialBundle\Repository\Invoice\Line\SocialSupport\AbstractProductRepository"
            inheritance-type="JOINED">

        <discriminator-column name="type" type="string" />

        <discriminator-map>
            <discriminator-mapping value="socialsupportproduct_effort" class="FinancialBundle\Entity\Invoice\Line\SocialSupport\EffortOrientedProduct" />
            <discriminator-mapping value="socialsupportproduct_output" class="FinancialBundle\Entity\Invoice\Line\SocialSupport\OutputOrientedProduct" />
        </discriminator-map>

        <field name="startDate" column="startdate" type="date" nullable="false" />
        <field name="endDate" column="enddate" type="date" nullable="true" />

        <many-to-one field="product" target-entity="FinancialBundle\Entity\Invoice\Line\GenericSocialSupportProduct">
            <cascade>
                <cascade-persist/>
            </cascade>
            <join-column name="invoicelinegenericsocialsupportproduct_id" referenced-column-name="id" />
        </many-to-one>

        <many-to-one field="contract" target-entity="FinancialBundle\Entity\Contract\AbstractContract">
            <join-column name="fin_contract_id" referenced-column-name="id" nullable="false" />
        </many-to-one>

    </entity>
</doctrine-mapping>

OutputOrientedProduct

<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping
        xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://raw.githubusercontent.com/doctrine/doctrine2/2.4/doctrine-mapping.xsd">
    <entity name="FinancialBundle\Entity\Invoice\Line\SocialSupport\OutputOrientedProduct"
            table="invoicelinesocialsupportproductoutput"
            repository-class="FinancialBundle\Repository\Invoice\Line\SocialSupport\Product\OutputOrientedRepository">

        <many-to-one field="treatmentActivity" target-entity="Medical\SocialSupport\AbstractTreatmentActivity">
            <join-column name="behandeling_socialsupportactivity_id" referenced-column-name="id" />
        </many-to-one>

        <many-to-one field="allocatedProduct" target-entity="FinancialBundle\Entity\Invoice\Line\GenericDispositionProductAllocated">
            <cascade>
                <cascade-persist/>
            </cascade>
            <join-column name="invoicelineallocatedproduct_id" referenced-column-name="id" />
        </many-to-one>

    </entity>
</doctrine-mapping>

AddonMedicine

<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping
        xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://raw.githubusercontent.com/doctrine/doctrine2/2.4/doctrine-mapping.xsd">
    <entity name="FinancialBundle\Entity\Invoice\Line\AddonMedicine"
            table="invoicelineaddonmedicine"
            repository-class="FinancialBundle\Repository\Invoice\Line\AddonMedicineRepository">

        <one-to-one field="addon" target-entity="FinancialBundle\Entity\Invoice\Line\AddonMedicine\Addon">
            <cascade>
                <cascade-persist/>
            </cascade>
            <join-column name="invoicelineaddonmedicineaddon_id" referenced-column-name="id" nullable="false" />
        </one-to-one>

        <many-to-one field="contract" target-entity="FinancialBundle\Entity\Contract\AbstractContract">
            <cascade>
                <cascade-persist/>
            </cascade>
            <join-column name="fin_contract_id" referenced-column-name="id" nullable="false" />
        </many-to-one>

    </entity>
</doctrine-mapping>
Originally created by @jeroenvrooij on GitHub (Jun 7, 2017). Originally assigned to: @Ocramius on GitHub. Given the following structure of entities, we are experiencing fatal errors the moment we try to make use of the self-referencing relation. ![diagram-167397485293621894](https://user-images.githubusercontent.com/1611240/26863475-14090754-4b53-11e7-99bd-b9087eb4409e.png) The multiple level discrimination on itself works fine. We can query for the object directly or get it from a related entity. But the moment we insert data in the parent_id column in the database and try to call the ‘getParent()’ method we get the following fatal error: _PHP Fatal error: Cannot instantiate abstract class FinancialBundle\Entity\Invoice\Line\SocialSupport\AbstractProduct in ..vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php on line 872_ An important detail is that we CAN use the self-referencing relation when querying for the ‘AddonMedicine’ entity. So it looks like that when we use the combination of the self-referencing relation AND the multiple level discrimination something is not interpreted right and that Doctrine is trying to instantiate the ‘middle layer’, being the AbstractProduct. These are the mapping files in use: **AbstractLine** ``` <?xml version="1.0" encoding="utf-8"?> <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://raw.githubusercontent.com/doctrine/doctrine2/2.4/doctrine-mapping.xsd"> <entity name="FinancialBundle\Entity\Invoice\Line\AbstractLine" table="invoiceline" inheritance-type="JOINED"> <discriminator-column name="type" type="string" /> <discriminator-map> <discriminator-mapping value="socialsupportproduct_effort" class="FinancialBundle\Entity\Invoice\Line\SocialSupport\AbstractProduct" /> <discriminator-mapping value="socialsupportproduct_output" class="FinancialBundle\Entity\Invoice\Line\SocialSupport\AbstractProduct" /> <discriminator-mapping value="addonmedicine" class="FinancialBundle\Entity\Invoice\Line\AddonMedicine" /> </discriminator-map> <id name="id" column="id" type="integer"> <generator strategy="AUTO" /> </id> <field name="quantity" column="quantity" type="float" nullable="false" /> <field name="price" column="price" type="float" nullable="false" /> <field name="subtotal" column="subtotal" type="integer" nullable="false" /> <field name="parentId" column="parent_id" type="integer" nullable="true" /> <many-to-one field="invoice" target-entity="FinancialBundle\Entity\Invoice\AbstractInvoice" inversed-by="invoiceLines"> <join-column name="invoice_id" referenced-column-name="id" nullable="false" /> </many-to-one> <many-to-one field="invoiceLineVat" target-entity="FinancialBundle\Entity\Invoice\Line\Vat"> <cascade> <cascade-persist/> </cascade> <join-column name="invoicelinevat_id" referenced-column-name="id" nullable="true" /> </many-to-one> </entity> </doctrine-mapping> ``` **AbstractProduct** ``` <?xml version="1.0" encoding="utf-8"?> <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://raw.githubusercontent.com/doctrine/doctrine2/2.4/doctrine-mapping.xsd"> <entity name="FinancialBundle\Entity\Invoice\Line\SocialSupport\AbstractProduct" table="invoicelinesocialsupportproduct" repository-class="FinancialBundle\Repository\Invoice\Line\SocialSupport\AbstractProductRepository" inheritance-type="JOINED"> <discriminator-column name="type" type="string" /> <discriminator-map> <discriminator-mapping value="socialsupportproduct_effort" class="FinancialBundle\Entity\Invoice\Line\SocialSupport\EffortOrientedProduct" /> <discriminator-mapping value="socialsupportproduct_output" class="FinancialBundle\Entity\Invoice\Line\SocialSupport\OutputOrientedProduct" /> </discriminator-map> <field name="startDate" column="startdate" type="date" nullable="false" /> <field name="endDate" column="enddate" type="date" nullable="true" /> <many-to-one field="product" target-entity="FinancialBundle\Entity\Invoice\Line\GenericSocialSupportProduct"> <cascade> <cascade-persist/> </cascade> <join-column name="invoicelinegenericsocialsupportproduct_id" referenced-column-name="id" /> </many-to-one> <many-to-one field="contract" target-entity="FinancialBundle\Entity\Contract\AbstractContract"> <join-column name="fin_contract_id" referenced-column-name="id" nullable="false" /> </many-to-one> </entity> </doctrine-mapping> ``` **OutputOrientedProduct** ``` <?xml version="1.0" encoding="utf-8"?> <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://raw.githubusercontent.com/doctrine/doctrine2/2.4/doctrine-mapping.xsd"> <entity name="FinancialBundle\Entity\Invoice\Line\SocialSupport\OutputOrientedProduct" table="invoicelinesocialsupportproductoutput" repository-class="FinancialBundle\Repository\Invoice\Line\SocialSupport\Product\OutputOrientedRepository"> <many-to-one field="treatmentActivity" target-entity="Medical\SocialSupport\AbstractTreatmentActivity"> <join-column name="behandeling_socialsupportactivity_id" referenced-column-name="id" /> </many-to-one> <many-to-one field="allocatedProduct" target-entity="FinancialBundle\Entity\Invoice\Line\GenericDispositionProductAllocated"> <cascade> <cascade-persist/> </cascade> <join-column name="invoicelineallocatedproduct_id" referenced-column-name="id" /> </many-to-one> </entity> </doctrine-mapping> ``` **AddonMedicine** ``` <?xml version="1.0" encoding="utf-8"?> <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://raw.githubusercontent.com/doctrine/doctrine2/2.4/doctrine-mapping.xsd"> <entity name="FinancialBundle\Entity\Invoice\Line\AddonMedicine" table="invoicelineaddonmedicine" repository-class="FinancialBundle\Repository\Invoice\Line\AddonMedicineRepository"> <one-to-one field="addon" target-entity="FinancialBundle\Entity\Invoice\Line\AddonMedicine\Addon"> <cascade> <cascade-persist/> </cascade> <join-column name="invoicelineaddonmedicineaddon_id" referenced-column-name="id" nullable="false" /> </one-to-one> <many-to-one field="contract" target-entity="FinancialBundle\Entity\Contract\AbstractContract"> <cascade> <cascade-persist/> </cascade> <join-column name="fin_contract_id" referenced-column-name="id" nullable="false" /> </many-to-one> </entity> </doctrine-mapping> ```
admin added the IncompleteMissing TestsQuestion labels 2026-01-22 15:11:29 +01:00
admin closed this issue 2026-01-22 15:11:30 +01:00
Author
Owner

@Ocramius commented on GitHub (Jun 7, 2017):

PHP Fatal error: Cannot instantiate abstract class Medicore\Bundle\FinancialBundle\Entity\Invoice\Line\SocialSupport\AbstractProduct in ..vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php on line 872

What's the code causing that error? Looks like a wrong discriminator value being used? What's the trace for that exception? Can this be reproduced in a test case?

@Ocramius commented on GitHub (Jun 7, 2017): > `PHP Fatal error: Cannot instantiate abstract class Medicore\Bundle\FinancialBundle\Entity\Invoice\Line\SocialSupport\AbstractProduct in ..vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php on line 872` What's the code causing that error? Looks like a wrong discriminator value being used? What's the trace for that exception? Can this be reproduced in a test case?
Author
Owner

@jeroenvrooij commented on GitHub (Jun 8, 2017):

Hi Ocramius, thanks for your quick reply.

  1. The error can be triggered by simply calling $this->repository->find($id);. The repository being used the the repository of the AbstracProduct, but we've also tried using the repo from the abstractLine (top level) and the OutputOrientedProduct (the concrete leaf) and that yields the same results.
  2. I'm pretty sure it cannot be a wrong discriminator value. The way we tested this was to have a valid record in the database (so without the parent_id). This record can be queried like said in bullet '1'. Everything works fine, we get an instance of an 'OutputOrientedProduct 'entity. After manually inserting a parent_id (and changing nothing else) executing the same code results in the 500.
  3. The reason I did not include a trace is because the trace didn't have much more info. But I'll include it at the bottom.
  4. I don't really know what you mean with test case, but I can't make a unit test for our code/logic, since all we do is calling find() in the repo. I didn't dive in the Doctrine code as well so no, I can not repro this in a test case.

Trace:

PHP Fatal error:  Uncaught exception 'Symfony\Component\Debug\Exception\FatalErrorException' with message 'Error: Cannot instantiate abstract class FinancialBundle\Entity\Invoice\Line\SocialSupport\AbstractProduct' in vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php:872
Stack trace:
#0 {main}
  thrown in vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php on line 872
@jeroenvrooij commented on GitHub (Jun 8, 2017): Hi Ocramius, thanks for your quick reply. 1. The error can be triggered by simply calling `$this->repository->find($id);`. The repository being used the the repository of the AbstracProduct, but we've also tried using the repo from the abstractLine (top level) and the OutputOrientedProduct (the concrete leaf) and that yields the same results. 2. I'm pretty sure it cannot be a wrong discriminator value. The way we tested this was to have a valid record in the database (so without the parent_id). This record can be queried like said in bullet '1'. Everything works fine, we get an instance of an 'OutputOrientedProduct 'entity. After manually inserting a parent_id (and changing nothing else) executing the same code results in the 500. 3. The reason I did not include a trace is because the trace didn't have much more info. But I'll include it at the bottom. 4. I don't really know what you mean with test case, but I can't make a unit test for our code/logic, since all we do is calling find() in the repo. I didn't dive in the Doctrine code as well so no, I can not repro this in a test case. Trace: ``` PHP Fatal error: Uncaught exception 'Symfony\Component\Debug\Exception\FatalErrorException' with message 'Error: Cannot instantiate abstract class FinancialBundle\Entity\Invoice\Line\SocialSupport\AbstractProduct' in vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php:872 Stack trace: #0 {main} thrown in vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php on line 872 ```
Author
Owner

@Ocramius commented on GitHub (Jun 8, 2017):

The way we tested this was to have a valid record in the database (so without the parent_id). This record can be queried like said in bullet '1'. Everything works fine, we get an instance of an 'OutputOrientedProduct 'entity. After manually inserting a parent_id (and changing nothing else) executing the same code results in the 500.

Is there an automated test for this?

@Ocramius commented on GitHub (Jun 8, 2017): > The way we tested this was to have a valid record in the database (so without the parent_id). This record can be queried like said in bullet '1'. Everything works fine, we get an instance of an 'OutputOrientedProduct 'entity. After manually inserting a parent_id (and changing nothing else) executing the same code results in the 500. Is there an automated test for this?
Author
Owner

@Ocramius commented on GitHub (Jun 8, 2017):

I don't really know what you mean with test case, but I can't make a unit test for our code/logic, since all we do is calling find() in the repo. I didn't dive in the Doctrine code as well so no, I can not repro this in a test case.

See 971c400025/tests/Doctrine/Tests/ORM/Functional for examples

@Ocramius commented on GitHub (Jun 8, 2017): > I don't really know what you mean with test case, but I can't make a unit test for our code/logic, since all we do is calling find() in the repo. I didn't dive in the Doctrine code as well so no, I can not repro this in a test case. See https://github.com/doctrine/doctrine2/tree/971c40002522cfebe58d80aff21eef9fe439fa60/tests/Doctrine/Tests/ORM/Functional for examples
Author
Owner

@jeroenvrooij commented on GitHub (Jun 8, 2017):

No, there is no automated test for this. For me to make one would take me some time, which I do not have at the moment. So the best I can do for now are the repro steps I provided. Will that be an issue?

@jeroenvrooij commented on GitHub (Jun 8, 2017): No, there is no automated test for this. For me to make one would take me some time, which I do not have at the moment. So the best I can do for now are the repro steps I provided. Will that be an issue?
Author
Owner

@Ocramius commented on GitHub (Jun 8, 2017):

Will that be an issue?

Well, until there's a test case nobody will really look at it :-\

@Ocramius commented on GitHub (Jun 8, 2017): > Will that be an issue? Well, until there's a test case nobody will really look at it :-\
Author
Owner

@jeroenvrooij commented on GitHub (Jul 18, 2017):

After some more investigation it turned out it had nothing to do with the self-referencing relation. Because of this, and some new insights, I have made a new issue: https://github.com/doctrine/doctrine2/issues/6558. Will hereby close this one.

@jeroenvrooij commented on GitHub (Jul 18, 2017): After some more investigation it turned out it had nothing to do with the self-referencing relation. Because of this, and some new insights, I have made a new issue: https://github.com/doctrine/doctrine2/issues/6558. Will hereby close this one.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5565