0

I have a document that has a ReferenceMany attribute to another document. The reference is setup fine, and the data is returned from the query fine, but each document in the arraycollection is returned as a proxy. On this site, I saw it was mentioned I should add ->prime(true) in order to return the actual referenced documents.

When that ArrayCollection of documents is returned, I am running a loop of ids I have submitted to the server to remove them from the referenced collection. The removeElement method is not working b/c the returned documents are proxies, and I am comparing an actual document vs. those proxies. So basically I am trying to:

  1. Look up a single document
  2. Force all documents in the ReferenceMany attribute to be actual documents and not Proxy documents
  3. Loop through my array of id's and load each document
  4. Send the document to the removeElement method

On the first getSingleResult query method below, I am getting an error cannot modify cursor after beginning iteration. I saw a thread on this site mention you should prime the results in order to get actual documents back instead of proxies, and in his example, he used getSingleResult.

$q = $this->dm->createQueryBuilder('\FH\Document\Person')->field('target')->prime(true)->field('id')->equals($data->p_id);
$person = $q->getQuery()->getSingleResult();

foreach($data->loc_id as $loc) {
    $location = $this->dm->createQueryBuilder('\FH\Document\Location')->field('id')->equals(new \MongoId($loc))->getQuery()->getSingleResult();
    $person->removeTarget($location);
}

....
....
....
public function removeTarget($document)
{
    $this->target->removeElement($document);

    return $this;
}

If I remove ->prime(true) from the first query, it doesn't throw an error, yet it doesn't actually remove any elements even though I breakpoint on the method, compare the two documents, and the data is exactly the same, except in $this->target they are Location Proxy documents, and the loaded one is an actual Location Document.

Can I prime the single result somehow so I can use the ArrayCollection methods properly, or do I need to just do some for loop and compare ids?

UPDATE

So here is an update showing the problem I am having. While the solution below would work just using the MongoId(s), when I submit an actual Document class, it never actually removes the document. The ArrayCollection comes back from Doctrine as a PersistentCollection. Each element in $this->coll is of this Document type:

DocumentProxy\__CG__\FH\Document\Location

And the actual Document is this:

FH\Document\Location

The removeElement method does an array_search like this:

public function removeElement($element)
{
    $key = array_search($element, $this->_elements, true);

    if ($key !== false) {
        unset($this->_elements[$key]);

        return true;
    }

    return false;
}

So because the object types are not exactly the same, even though the proxy object should be inheriting from the actual Document I created, $key always returns 0 (false), so the element is not removed. Everything between the two documents are exactly the same, except the object type.

Like I said, I guess I can do it by MongoId, but why isn't it working by submitting the entire object?

Nathan
  • 2,941
  • 6
  • 49
  • 80

1 Answers1

1

Don't worry about the prime(true) stuff for just now. All that does is tell doctrine to pull the referenced data now, so it doesn't have to make multiple calls to the database when you iterate over the cursor.

What I would do is change your removeTarget method to do the following.

$this->dm->createQueryBuilder('\FH\Document\Person')->field('id')->equals($data->p_id);
$person = $q->getQuery()->getSingleResult();
$person->removeTargets($data->loc_id);

Person.php

public function removeTargets($targets)
{
    foreach ($targets as $target) {
        $this->removeTarget($target);
    }
}

public function removeTarget($target)
{
    if ($target instanceof \FH\Document\Location) {
        return $this->targets->removeElement($target);
    }

    foreach ($this->targets as $t) {
        if ($t->getId() == $target) {
            return $this->targets->removeElement($t);
        }
    }
    return $this;
}

This would mean you don't have to perform the second query manually as doctrine will know it needs to pull the data on that reference when you iterate over it. Then you can make this operation quicker by using the prime(true) call to make it pull the information it needs in one call rather than doing it dynamically when you request the object.

Jamie Sutherland
  • 2,760
  • 18
  • 19
  • I will try this tonight, but my problem is this line would never work `return $this->targets->removeElement($target);` because when it tried to match my Document object, all of the elements in `$this->targets` were `Document_Proxy\FH\Document\Location`, so in the `removeElement` method, they never deleted, because the object types were not the same. I can set it up to only remove target by the id, but it's why I originally tried to use `prime->(true)` – Nathan Nov 26 '12 at 16:23
  • Proxy classes should extend the original class so that shouldn't be an issue I'd have thought. When you said it would never work, what do you mean? It wouldn't remove the element? or it would throw an exception? – Jamie Sutherland Nov 27 '12 at 00:00
  • I have updated the question with further explanation. Hopefully that helps. It didn't throw an exception, it just failed to remove the element, even though it's there. I used the same static object for testing creating and removing. When I `prime` the results, it returns a matching document object, and I can remove successfully. However, I cannot `prime->(true)` and `getSingleResult()` at the same time, so I wanted to find out why the proxy object & document object weren't playing nice. – Nathan Nov 27 '12 at 03:02