Reputation: 11
I want to update an database entry which has a simple reference. The Reference:
/**
* @MongoDB\ReferenceOne(targetDocument="Acme\TestBundle\Document\Reference", simple=true)
*/
protected $reference;
At the moment I use this solution:
$document->setReference($dm->getRepository('TestBundle:Reference')->find(1));
It's working but I'm very unhappy with this solution since I have to update a huge amount of database entries (1000+) and I don't want to fetch this many Database entries.
I'd like to do something like this:
$document->setReference(1);
But of course, Doctrine asks for an object as reference. Any suggestions?
Upvotes: 0
Views: 1799
Reputation: 6922
There are a few ways to accomplish what you're trying to do. My answer will utilize the following models:
/** @ODM\Document */
class TestDocument
{
/** @ODM\Id */
public $id;
/** @ODM\ReferenceOne(targetDocument="TestReference", simple=true) */
public $reference;
}
/** @ODM\Document */
class TestReference
{
/** @ODM\Id */
public $id;
/** @ODM\String */
public $name;
}
To start, let's create separate TestDocument and TestReference documents, persist them with our DocumentManager ($dm
), and then flush and clear:
$document = new TestDocument();
$reference = new TestReference();
$reference->name = 'foo';
$dm->persist($document);
$dm->persist($reference);
$dm->flush();
$dm->clear();
At this point, our DocumentManager is no longer tracking the documents, but we'll have them in our database. The additional $name
property on TestReference will illustrate how ODM handles proxy objects in a bit.
At this point, I can use a non-hydrated query to view the database values for both documents:
$rs = $dm->createQueryBuilder(get_class($document))
->field('id')->equals($document->id)
->hydrate(false)
->getQuery()
->getSingleResult();
var_dump($rs);
$rs = $dm->createQueryBuilder(get_class($reference))
->field('id')->equals($reference->id)
->hydrate(false)
->getQuery()
->getSingleResult();
var_dump($rs);
This should print something similar (obviously the ObjectIds will differ) to the following:
array(1) {
["_id"]=>
object(MongoId)#191 (1) {
["$id"]=>
string(24) "52e01b86e84df17c548b4569"
}
}
array(2) {
["_id"]=>
object(MongoId)#578 (1) {
["$id"]=>
string(24) "52e01b86e84df17c548b456a"
}
["name"]=>
string(3) "foo"
}
The query builder is perhaps the simplest way to set the simple reference on TestDocument:
$dm->createQueryBuilder(get_class($document))
->update()
->field('id')->equals($document->id)
->field('reference')->set($reference->id)
->getQuery()
->execute();
ODM naturally takes care of preparing criteria and modifier values in the query builder. So, even though $document->id
may be a string (ODM converts MongoId objects to/from strings for hydration/persistence), ODM will match the document by a real ObjectId. Likewise for setting the reference
field. ODM knows that we're dealing with a simple reference, so it will not bother creating a DBRef; however, $reference->id
is likely a string so it will need to convert it to a MongoId object. After executing this update query, dumping the database value for TestDocument should report the following:
array(2) {
["_id"]=>
object(MongoId)#191 (1) {
["$id"]=>
string(24) "52e01b86e84df17c548b4569"
}
["reference"]=>
object(MongoId)#578 (1) {
["$id"]=>
string(24) "52e01b86e84df17c548b456a"
}
}
A side benefit of the query builder is that you could match more than a single document at a time (e.g. using $in
criteria on the id
) if you wanted to set the same reference on multiple TestDocuments. Also, issuing writes with the query builder bypasses all of the ODM's change detection and object management.
We can also use basic modifications to managed documents to achieve the same result. This is what you were attempting to do in your original question, but you likely ran into an exception from DocumentManager::createDBRef()
while the update was being prepared. When modifying your managed documents, ODM expects you use to use objects (either full, managed entities or proxy objects) for relationships at all times. During preparation, ODM attempts to convert referenced objects to the appropriate database value, which means either a DBRef object or a identifier value for normal and simple references, respectively.
So, what if you only have the identifier for a referenced object? You can use DocumentManager::getReference()
or DocumentManager::getPartialReference()
to instantiate a proxy or document, respectively.
Proxies are instances of generated classes that extend your actual model class. They are constructed with an identifier field and intercept any method calls other than simple getId()
implementations to hydrate your document lazily1. ODM creates these on its own for ReferenceOne and ReferenceMany relationships.
A partial reference would be an instance of your actual model class (not a Proxy), which has only the identifier set. Partial references are equally suitable for DocumentManager::createDBRef()
, although they have limited usability beyond that.
The following example would work in your case:
$document = $dm->find(get_class($document), $document->id);
// We could also use getPartialReference() here
$document->reference = $dm->getReference(get_class($reference), $reference->id);
$dm->flush();
$dm->clear();
If you were to dump the database value for TestDocument, you should find the same results as we did with the query builder solution.
Lastly, let's dump the TestDocument object itself. The Doctrine\Common\Util\Debug
class provides a method for doing so, which conveniently avoids dumping internal ODM services that may be nestled within our objects (if you've ever var_dump()
-ed a managed document, you know what I'm referring to).
$document = $dm->find(get_class($document), $document->id);
\Doctrine\Common\Util\Debug::dump($document);
This should yield the following:
object(stdClass)#610 (3) {
["__CLASS__"]=>
string(39) "Doctrine\ODM\MongoDB\Tests\TestDocument"
["id"]=>
string(24) "52e01b86e84df17c548b4569"
["reference"]=>
object(stdClass)#458 (5) {
["__CLASS__"]=>
string(40) "Doctrine\ODM\MongoDB\Tests\TestReference"
["__IS_PROXY__"]=>
bool(true)
["__PROXY_INITIALIZED__"]=>
bool(false)
["id"]=>
NULL
["name"]=>
NULL
}
}
Why are the id
and name
fields for reference
null? When we fetched our TestDocument, ODM created a proxy for the ReferenceOne relationship to TestReference. Internally, that proxy does have the appropriate TestReference identifier, but that's not printed by the Debug::dump()
method. Since we don't have any getter methods on this class to trigger lazy loading1, I'm going going to call the internal __load()
method on the proxy object, which is declared on the Doctrine\Common\Persistence\Proxy
interface.
$document = $dm->find(get_class($document), $document->id);
$document->reference->__load();
\Doctrine\Common\Util\Debug::dump($document);
Now, we should have the following output:
object(stdClass)#630 (3) {
["__CLASS__"]=>
string(39) "Doctrine\ODM\MongoDB\Tests\TestDocument"
["id"]=>
string(24) "52e01b86e84df17c548b4569"
["reference"]=>
object(stdClass)#458 (5) {
["__CLASS__"]=>
string(40) "Doctrine\ODM\MongoDB\Tests\TestReference"
["__IS_PROXY__"]=>
bool(true)
["__PROXY_INITIALIZED__"]=>
bool(true)
["id"]=>
string(24) "52e01b86e84df17c548b456a"
["name"]=>
string(3) "foo"
}
}
1: Doctrine Common 2.4 supports proxy initializing on property access as well as method invocations. This is currently the case for Doctrine ORM, but MongoDB ODM has not yet been upgraded to support it.
Upvotes: 1
Reputation: 825
I am not familiar with Doctrine and I am not sure if the following make sense for this framework, but you may be able to achieve what you want by modifying your schema.
You may want to add the 'ReferenceOne_id' field in the 'Reference' documents. The 1000 documents would have a field "ref_id" : . If you have a 1-to-N relationship, then "ref_id" would be a string, if you have an N-to-N relationship, then "ref_id" would be an array. With that schema change, you can make one query for all the documents in the 'Reference' collection that have a given "ref_id".
Upvotes: 0