0

Question: How do you load an object with an associated object that uses inheritance mapping with doctrine?

For example using these objects: (ignore typos I renamed things from my real example code)

/**
 * @ORM\Table(name="int_entity")
 * @ORM\Entity
 * })
 */
class IntEntity extends BaseEntity
{
    /**
     * @ORM\Column(name="int_value", type="integer", nullable=true)
    **/
    public $intValue;
}

/**
     * @ORM\Table(name="float_entity")
     * @ORM\Entity
     * })
     */
    class floatEntity extends FakeBaseMixedEntity
    {   
        /**
         * @ORM\Column(name="float_value", type="float", nullable=true)
        **/
        public $floatValue;
    }

/** @ORM\MappedSuperclass */
class BaseEntity
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     */
    private $id;

    //could be other variables I guess but I didn't need any. The point of the
    //base class in my case is more of an interface I guess...
}

 /**
     * @ORM\Table(name="Entity_Containing_Inherited_Entity")
     * @ORM\Entity
     * })
     */
    class EntityContainingInheritedEntity
    {
        /**
         * @ORM\Column(name="id", type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="IDENTITY") 
         */
        public $id;

        /**
         * @ORM\Column(name="string_value", type="string")  
         */
        public $stringValue;

        /**
        * @ORM\OneToOne(targetEntity="BaseEntity", cascade={"persist"})
        * @ORM\JoinColumn(name="id", referencedColumnName="id")
        **/
        public $mixedEntity;
    }

loading doesn't work

$em = $this->getDoctrine()->getManager();
$loadedValue = $em->getRepository('BundleName:EntityContainingInheritedEntity')->findAll();

however saving does

public function indexAction(Request $request)
{
    $floatE = new floatEntity();
    $floatE->floatValue = 1.01;
    $floatE->setId(rand());

    $floatEContainer = new EntityContainingInheritedEntity();
    $floatEContainer->stringValue = 'hello';
    $floatEContainer->mixedEntity = $floatE;

    $intE = new intEntity();
    $intE->intValue = 100;
    $intE->setId(rand());

    $intEContainer = new EntityContainingInheritedEntity();
    $intEContainer->stringValue = 'hello';
    $intEContainer->mixedEntity = $intE;

    $em = $this->getDoctrine()->getManager();
    $em->persist($floatEContainer);
    $em->persist($intEContainer );
    $em->flush();
}

When loading it says the proxy doesn't exist. So I searched that and first off doctrine isn't supposed to even try making a proxy when there is inheritance mapping like this (at least that is how I interpreted the docs http://docs.doctrine-project.org/en/2.0.x/reference/inheritance-mapping.html#performance-impact

I have cleared my cache a nothing changed.
I have tried to add eager loading to annotation nothing changed.
I also kind of got the gist from my search I was supposed to use DQL instead of findAll() the below has the same proxy cache error message

$query = $em->createQuery("SELECT u FROM Path\To\Entity\EntityContainingInheritedEntity u");
$users = $query->getResult();

Mentioned error message: ContextErrorException: Warning: require( /path/app/cache/dev/doctrine/orm/Proxies/__CG__BundleNameEntityBaseEntity.php): failed to open stream: No such file or directory in /path/vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php

BTW this doesn't solve my problem: Doctrine 2 Inheritance Mapping with Association

Community
  • 1
  • 1
  • Are you using real inheritance? Because in the examples I only see being used a `@MappedSuperClass` which is not the same as table inheritance. – Alberto Fernández Apr 02 '14 at 11:02
  • So here is what happened. I had a type1 and type2 and a base. I wanted to use this both in forms and saving to the database. Saving to the database didn't work so I changed delegation so I had type1 which contained commontype and type2 contained commontype. However this messed up the forms so I switched it so commontype contained either type1 or type 2. This worked for saving/form! but when I tried to load from DB it didn't. So if I did table inheritance I might as well go back to the beginning and thus have an infinite circle of not working stuff. So to answer your question right now no. – Steven Gerdes Apr 02 '14 at 14:17
  • @AlbertoFernández In my mind Mapped Superclasses where still considered inheritance. Should I change the name of the question? – Steven Gerdes Apr 02 '14 at 14:27
  • Of course, they are *class* inheritance, but no *database* inheritance, which is a very powerful feature of Doctrine. As for your problem itself, I don't quite understand what is your problem, I see that you are doing some odd things (like calling `setId(rand())` when this is normally handled internally). – Alberto Fernández Apr 02 '14 at 14:31
  • That was just lazy me. I have done without the rand as well. My problem is when I try to load from the database it doesn't work using the repository. Now this wouldn't matter except that is how forms loads entities from the the database. – Steven Gerdes Apr 02 '14 at 14:37
  • I see you are doing a `OneToOne` relationship in one of your base classes to the `MappedSuperClass` class. You cannot do that, as the `MappedSuperClass` is not an entity at all. You need to do that relationship either on the `MappedSuperClass` itself (and to another `MappedSuperClass`), or to another inherited `MappedSuperClass` entity. – Alberto Fernández Apr 02 '14 at 14:41
  • Maybe post that last comment as an answer with more detail? I am a little confused to what you mean. And thanks for looking at this. I got hired onto a "team" but it is actually an IT team that wanted some internal tools, I have no other developers to talk to... at least it is just an internship. – Steven Gerdes Apr 02 '14 at 14:48

1 Answers1

0

According to the comment by Alberto Fernandez I just wasn't supposed to do what I did. This confused me because it saved correctly. None the less without any example from him of how to do it right I continued searching. I figured out that the best solution to my problem was to not map the associated entity. Instead I hooked into the event listeners i.e. http://symfony.com/doc/current/cookbook/doctrine/event_listeners_subscribers.html and http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html This allowed me to persist and load associated entities exactly how I wanted. I will use my above example again with my solution.

EntityContainingInheritedEntity changed to

/**
* @ORM\Table(name="Entity_Containing_Inherited_Entity")
* @ORM\Entity
* })
*/
class EntityContainingInheritedEntity
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY") 
     */
    public $id;

    /**
     * @ORM\Column(name="string_value", type="string")  
     */
    public $stringValue;

    public $mixedEntity;

    public $mixedEntityType;

    public function load($em)
    {
        $this->mixedEntity= $em->getRepository("BundleName:{$this->mixedEntityType}")->find($this->id);
    }

    public function save($em)
    {
        $this->mixedEntity->SetId($this->id);
        $em->persist($this->mixedEntity);
        $em->flush();
    }

    public function delete($em)
    {       
        $em->remove($this->mixedEntity);
        $em->flush();
    }

}

I added this to my config.yml

services:
    my.listener:
        class: Path\To\EventListeners\RecordAssociationManager
        tags:
            - { name: doctrine.event_listener, event: postPersist }
    my.listener2:
        class: Path\To\EventListeners\RecordAssociationManager
        tags:
            - { name: doctrine.event_listener, event: preRemove }
    my.listener3:
        class: Path\To\EventListeners\RecordAssociationManager
        tags:
            - { name: doctrine.event_listener, event: postLoad }

And then made the event listener class

namespace Path\To\EventListeners;

use Doctrine\ORM\Event\LifecycleEventArgs;
use Path\To\Entity\EntityContainingInheritedEntity;

class RecordAssociationManager
{
    public function preRemove(LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();
        $entityManager = $args->getEntityManager();

        if ($entity instanceof EntityContainingInheritedEntity) 
        {
            $entity->delete($entityManager);
        }
    }
    public function postPersist(LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();
        $entityManager = $args->getEntityManager();

        if ($entity instanceof EntityContainingInheritedEntity) 
        {
            $entity->save($entityManager);
        }
    }
    public function postLoad(LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();
        $entityManager = $args->getEntityManager();

        if ($entity instanceof EntityContainingInheritedEntity) 
        {
            $entity->load($entityManager);
        }
    }
}
  • I almost don't want to accept my answer because it feels a little hacky though in the docs it even suggests non-mapped associate entity persistence as a use for hooking into these events so I guess it isn't. – Steven Gerdes Apr 02 '14 at 18:04