Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

doctrine mapping not found case

I have a table A which references table B.

edit: Database engine used is MyISAM.

Doctrine mapping works like a charm, except when I have non valid case in the DB where referenced ID in table A does not really exist in table B.

So when you execute this code:

$objectB = $objectA->getObjectB();//with lazy load

you actually get $objectB proxy object which is not null. So !empty($objectB) will pass.

And when you try to access any property of $objectB, like:

$objectB->getName();

you get Entity not found exception. There is not way you can predict in your code that $objectB actually does not exist and that there's no Name property for $objectB.

$objectB should actually be set to null but that is not happening.

Hibernate has actually mapping property not-found=ignore which sets missing object to NULL instead of setting it to Proxy object. Does Doctrine has anything similar?

PS. Of course you can always catch the Entity not found exception, and play around with that. Or you can map the actual objectB_ID field at the table A, but those are not 100% clean solutions.

I hope someone has an answer.

Thank You

like image 291
Goran Avatar asked Dec 27 '22 23:12

Goran


2 Answers

except when I have non valid case in the DB where referenced ID in table A does not really exist in table B

IMO this is a case of garbage in, garbage out. If you have schema where TableA may or may not have a row in TableB, you should implement a FK constraint on TableB, so that if a row is deleted from TableB, any rows in TableA referencing deleted rows will have their values changed to null.

If you really wanted to move forward on your proposed schema implementation, you could try doing:

$rowExists = ($objectA->getObjectB()->getId() > 0) ? true : false;

This of course assumes you have an id column on tableB and that it is unsigned.

-- Update --

try {
    $objectB = $objectA->getObjectB();
} catch (Exception $e) {
    $objectB = null;
}

if ($objectB instanceof ClassB) {
    // Do work
}
like image 107
Mike Purcell Avatar answered Jan 11 '23 09:01

Mike Purcell


If you take a look at one of your generated proxy classes, you'll see that the __load() and __clone() functions both throw the EntityNotFoundException.

The __load() function is called when you "lazily" call the getName() function.

class ObjectB extends \Foo\Entity\ObjectB implements \Doctrine\ORM\Proxy\Proxy
{
    private $_entityPersister;
    private $_identifier;
    public $__isInitialized__ = false;
    public function __construct($entityPersister, $identifier)
    {
        $this->_entityPersister = $entityPersister;
        $this->_identifier = $identifier;
    }
    /** @private */
    public function __load()
    {
        if (!$this->__isInitialized__ && $this->_entityPersister) {
            $this->__isInitialized__ = true;

            if (method_exists($this, "__wakeup")) {
                // call this after __isInitialized__to avoid infinite recursion
                // but before loading to emulate what ClassMetadata::newInstance()
                // provides.
                $this->__wakeup();
            }

            if ($this->_entityPersister->load($this->_identifier, $this) === null) {
                throw new \Doctrine\ORM\EntityNotFoundException();
            }
            unset($this->_entityPersister, $this->_identifier);
        }
    }
...
    public function getName()
    {
        $this->__load();
        return parent::getName();
    }
...
}

You basically have a couple of options, the first being use a try/catch block.

try {
    $name = $objectB->getName();
} catch (\Doctrine\ORM\EntityNotFoundException $e) {
    $name = null;
}

Or you can take a look at implementing the __wakeup() function in ObjectB and possibly handling this yourself (though you'll more than likely need to throw an exception anyways).

Lastly, if you are feeling ambitious, you can change the Proxy template. \Doctrine\ORM\Proxy\ProxyFactory contains the template.

    /** Proxy class code template */
    private static $_proxyClassTemplate =
'<?php

namespace <namespace>;

/**
 * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
 */
class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy
{
    private $_entityPersister;
    private $_identifier;
    public $__isInitialized__ = false;
    public function __construct($entityPersister, $identifier)
    {
        $this->_entityPersister = $entityPersister;
        $this->_identifier = $identifier;
    }
    /** @private */
    public function __load()
    {
        if (!$this->__isInitialized__ && $this->_entityPersister) {
            $this->__isInitialized__ = true;

            if (method_exists($this, "__wakeup")) {
                // call this after __isInitialized__to avoid infinite recursion
                // but before loading to emulate what ClassMetadata::newInstance()
            // provides.
                $this->__wakeup();
            }

            if ($this->_entityPersister->load($this->_identifier, $this) === null) {
                throw new \Doctrine\ORM\EntityNotFoundException();
            }
            unset($this->_entityPersister, $this->_identifier);
        }
    }

You should be able to get away with just removing the throwing of the EntityNotFoundException in the __load() and __clone() functions, though there might be unintended side effects. You'll also probably want to look at doing this change as a patch if you plan on upgrading Doctrine periodically.

like image 29
hafichuk Avatar answered Jan 11 '23 09:01

hafichuk