Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Doctrine detaching, caching, and merging

I'm on Doctrine 2.3. I have the following query:

$em->createQuery('
    SELECT u, c, p
    FROM Entities\User u
    LEFT JOIN u.company c
    LEFT JOIN u.privilege p
    WHERE u.id = :id
')->setParameter('id', $identity)

I then take that, get the result (which is an array, I just take the first element), and run detach $em->detach($result);.

When I go to fetch from the cache (using Doctrine's APC cache driver), I do:

$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
if($cacheDriver->contains($cacheId))
{
    $entity = $cacheDriver->fetch($cacheId);
    $em->merge($entity);
    return $entity;
}

My hope was that this would re-enable the relationship loading on the entity as there are many other relationships tied to the User object other than what's shown in that query.

I'm trying to create a new entity like such:

$newEntity = new Entities\ClientType();
$newEntity['param'] = $data;
$newEntitiy['company'] = $this->user['company'];
$em->persist($newEntity);
$em->flush();

When I do this, I get an error:

A new entity was found through the relationship 'Entities\ClientType#company' that was not configured to cascade persist operations for entity:
Entities\Company@000000005d7b49b500000000dd7ad743. 
To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"}). 
If you cannot find out which entity causes the problem implement 'Entities\Company#__toString()' to get a clue.

This works just fine when I don't use the company entity under the user entity that I got from cache. Is there any way to make this work so I don't have to refetch the company entity from the database every time I want to use it in a relationship with a new entity?

Edit: This is what I have in my User entity dealing with these two relationships:

/**
    * @ManyToOne(targetEntity="Company" , inversedBy="user", cascade={"detach", "merge"})
    */
    protected $company;

    /**
    * @ManyToOne(targetEntity="Privilege" , inversedBy="user", cascade={"detach", "merge"})
    */
    protected $privilege;

I am still getting the same error.

Second edit: Trying a $em->contains($this->user); and $em->contains($this->user['company']); both return false. Which sounds... wrong.

like image 573
nwalke Avatar asked Nov 14 '12 02:11

nwalke


1 Answers

When merging a User, you want the associated Company and Privilege to be merged as well, correct?

This process is called cascading:

http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html#transitive-persistence-cascade-operations

In your User entity put cascade={"merge"} in the @ManyToOne annotation (or another type of association-definition you are using) for $company and $privilege.

And if you want the detach call to be cascaded too (which is recommended), put in cascade={"detach", "merge"}.

p.s.: Don't put such cascades on both sides of one association, you'll create an endless loop ;)

Edit:

This piece of code:

$entity = $cacheDriver->fetch($cacheId);
$em->merge($entity);                      // <-
return $entity;

should be:

$entity = $cacheDriver->fetch($cacheId);
$entity = $em->merge($entity);            // <-
return $entity;

The thing with merge() is that it leaves the entity you pass as an argument untouched, and returns a new object that represents the managed version of the entity. So you want to use the return-value, not the argument you passed.

like image 64
Jasper N. Brouwer Avatar answered Sep 18 '22 00:09

Jasper N. Brouwer