Doctrine Mongo ODM UniqueIndex is replicated - php

I'm playing with the UniqueIndex that I picked from the doc of Doctrine ODM and it seems I have a misanderstood of what it aims to do.
Indeed I have a Keyword Document mapped by Doctrine ODM :
Namespace App\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
/**
* #ODM\Document
* #ODM\UniqueIndex(keys={"name"="asc", "lang"="asc"})
*/
class Keyword {
/** #ODM\Id(strategy="AUTO") */
protected $id;
/** #ODM\String */
protected $name;
/** #ODM\String */
protected $lang;
....
As you can see the Document has an uniqueIndex on 2 keys (Name and Lang)
I have a all simple script which persist this Document
....
....
$keyword=new \App\Document\Keyword();
$keyword->setCreateDate(new \DateTime());
$keyword->setLang("fr");
$keyword->setLastParseDate(new \DateTime());
$keyword->setName("test");
$dm->persist($keyword);
$dm->flush();
Now when i find from mongo shell, my data with the same pair Name/lang are replicated when they should be unique :
> db.Keyword.find()
{ "_id" : ObjectId("5171c72c6155795e47000000"), "name" : "test", "lang" : "fr", "createDate" : ISODate("2013-04-19T22:37:32Z"), "lastParseDate" : ISODate("2013-04-19T22:37:32Z") }
{ "_id" : ObjectId("5171c7366155796147000000"), "name" : "test", "lang" : "fr", "createDate" : ISODate("2013-04-19T22:37:42Z"), "lastParseDate" : ISODate("2013-04-19T22:37:42Z") }
{ "_id" : ObjectId("5171c7406155796447000000"), "name" : "test", "lang" : "fo", "createDate" : ISODate("2013-04-19T22:37:52Z"), "lastParseDate" : ISODate("2013-04-19T22:37:52Z") }
{ "_id" : ObjectId("5171c7fd615579a747000000"), "name" : "test", "lang" : "fo", "createDate" : ISODate("2013-04-19T22:41:01Z"), "lastParseDate" : ISODate("2013-04-19T22:41:01Z") }
{ "_id" : ObjectId("5171c7fe615579aa47000000"), "name" : "test", "lang" : "fo", "createDate" : ISODate("2013-04-19T22:41:02Z"), "lastParseDate" : ISODate("2013-04-19T22:41:02Z") }
My goal is to make the pair name/lang unique for persistence.
So I finally have two questions :
What UniqueIndex is made for ? (Because it doesnt prevent to replication)
Should I use a custom strategy that concatenates Name and Lang as an unique id ? Is it a common usage ?
EDIT :
Thanks to #gview advices I found that i didnt ensureIndexes. I fixed thanks to this link : http://www.testically.org/2011/08/25/using-a-unique-index-in-mongodb-with-doctrine-odm-and-symfony2/
But now instead of update my entry, it throws an error for duplicate entry. Should i use custom id as i said above ?

Try to use ensureIndexes like this:
$dm = $this->get('doctrine_mongodb')->getManager();
$dm->getSchemaManager()->ensureIndexes();

The index ensure only that the Documents don't get duplicated.
If you want to do the equivalent of a "REPLACE INTO", you should either:
Get the document if present, then set the values:
$keyword= $dm->findBy(array("name"=> $name, "lang"=> $lang));
if(!$keyword) {
$keyword= new Keyword();
$dm->persist($keyword);
}
$keyword->setCreateDate(new \DateTime());
$keyword->setLang("fr");
$keyword->setLastParseDate(new \DateTime());
$keyword->setName("test");
This will lead to 2 queries.
Or:
Do an upsert:
$dm->createQueryBuilder('Keyword')
->setNewObj(array(
'lang' => 'fr',
'name' => 'test',
// ... other fields
))
->field('lang')->equals('fr')
->field('name')->equals('test')
->getQuery()
->execute();
This will update the doc if present, otherwise it will create a new Document.
However, the new document is created from the raw array, actually bypassing all the Doctrine events (like the #Timestampeble annotation).
So if the extra query isn't a problem, use the first method.

Related

Laravel - Eloquent Relationship not working in Mongo Using JsonArray

i am using mongoDB with Laravel. here is the Website document.
{
"_id" : ObjectId("5b68eeb2c2600fa0d42978fd"),
"site_name" : "Agent Inc",
"slug" : "agentinc",
"wp_env" : "production",
"wp_home" : "https://agentinc.co"
}
and here is the User document and i am using websites as Json Array in my User Document. like this.
{
"_id" : ObjectId("5c9e3ca99a8920189320ca62"),
"name" : "Usman Fakhar",
"website_ids" : [
"5be9cc00cd14658a0554a05d",
"5c0ebcdbb149d84483aa305d",
"5c10f676b149d84483acaa33"
],
"slug" : "usmanfc"
}
now in my User Model i am trying to use this relationship. but its not working.
public function websites(){
return $this->hasMany('App\Website', 'website_ids','_id');
}
this is returning an empty Collection when i try this.
$user->websites;
hello guys i was watching Documentation and there was an excellent relationship called "embedsMany" for this. so this code Worked.
return $this->embedsMany('App\Website', 'website_ids', '_id');

How to project more than one value with doctrine mongodb query builder selectElemMatch

I have the following kind of data in my mongo database. The property "values" consists of an array of attributes. "values" is a property of a product, which also has some other properties like "normalizedData". But the structure of "values" is what gives me a headache.
"values" : [
{
"_id" : ObjectId("5a09d88c83218b814a8df57d"),
"attribute" : NumberLong("118"),
"entity" : DBRef("pim_catalog_product", ObjectId("59148ee283218bb8548b45a8"), "akeneo_pim"),
"locale" : "de_AT",
"varchar" : "LED PAR-56 TCL 9x3W Short sw"
},
{
"_id" : ObjectId("5a09d88c83218b814a8df57a"),
"attribute" : NumberLong("118"),
"entity" : DBRef("pim_catalog_product", ObjectId("59148ee283218bb8548b45a8"), "akeneo_pim"),
"locale" : "de_DE",
"varchar" : "LED PAR-56 TCL 9x3W Short sw"
},
{
"_id" : ObjectId("5a09d88c83218b814a8df57c"),
"attribute" : NumberLong("184"),
"entity" : DBRef("pim_catalog_product", ObjectId("59148ee283218bb8548b45a8"), "akeneo_pim"),
"locale" : "de_AT",
"boolean" : false
},
{
"_id" : ObjectId("5a09d88c83218b814a8df585"),
"attribute" : NumberLong("118"),
"entity" : DBRef("pim_catalog_product", ObjectId("59148ee283218bb8548b45a8"), "akeneo_pim"),
"locale" : "fr_FR",
"varchar" : "LED PAR-56 TCL 9x3W Short sw"
},
{
"_id" : ObjectId("5a09d88c83218b814a8df584"),
"attribute" : NumberLong("121"),
"entity" : DBRef("pim_catalog_product", ObjectId("59148ee283218bb8548b45a8"), "akeneo_pim"),
"locale" : "fr_FR",
"varchar" : "Eurolite LED PAR-56 TCL 9x3W Short sw"
},
{
"_id" : ObjectId("5a09d88c83218b814a8df574"),
"attribute" : NumberLong("207"),
"entity" : DBRef("pim_catalog_product", ObjectId("59148ee283218bb8548b45a8"), "akeneo_pim"),
"varchar" : "51913611"
},
]
A couple of things to notice about this extract from the dataset:
attributes with their ID ("attribute") can appear multiple times, like 118 for example.
attributes do not always have the same subset of properties (see 207 and 121 for example).
if an attribute is present multiple times (like 118) it should differ in the "locale" property at least.
Now I need the doctrine mongoDB query builder to project the following result:
I want only those attributes to be present in the result that contain one of the IDs specified by the query (e.g. array(118, 184)).
If the attribute exists multiple times, I want to see it multiple times.
If the attribute exists multiple times, I want to limit the number by an array of locales given.
So an example query would be: return all attributes inside "values" that have eigther 118 or 184 as the "attribute" property, and (if specified) limit the results to those attributes, where the locale is either "de_DE" or "it_IT".
Here is what I have tried so far:
$qb = $productRepository->createQueryBuilder();
$query = $qb
->hydrate(false)
->select(array('normalizedData.sku'))
->selectElemMatch(
'values',
$qb->expr()->field('attribute')->in(array(117, 110))->addAnd(
$qb->expr()->field('locale')->in(array('it_IT', 'de_DE'))
))
->field('_id')->in($entityIds)
->field('values')->elemMatch($qb->expr()->field('attribute')->in(array(117, 110)))
->limit($limit)
->skip($offset);
This query always returns only one attribute (no matter how many times it is present within the "values" array) per product. What am I doing wrong here?
EDIT: My MongoDB version is 2.4.9 and doctrine-mongo-odm is below 1.2. Currently I cannot update either.
You can try below aggregation query in 3.4 mongo version. $elemMatch by design returns first matching element.
You will need $filter to return multiple matches.
$match to limit the documents were values has atleast one value where it contains both attribute in [118,184] and locale in ["de_DE","it_IT"] followed by $filter to limit to matching documents in a $project stage. You can add $limit and $skip stage at the end of aggregation pipeliine same as what you did with regular query.
db.col.aggregate([
{"$match":{
"values":{
"$elemMatch":{
"attribute":{"$in":[118,184]},
"locale":{"$in":["de_DE","it_IT"]}
}
}
}},
{"$project":{
"values":{
"$filter":{
"input":"$values",
"as":"item",
"cond":{
"$and":[
{"$in":["$$item.attribute",[118,184]]},
{"$in":["$$item.locale",["de_DE","it_IT"]]}
]
}
}
}
}}
])
You can use AggregationBuilder to write the query in doctrine.

How to update field in Mongo DB?

I have document like this:
{
"_id" : ObjectId("591ed2f0470e6ccc143c986e"),
"name" : "Planets",
"prototype_id" : null,
"parameters" : [
"591eefe3470e6cd70c3c9872",
"591eefc3470e6c500f3c9872",
"591eedbe470e6cd70c3c9871"
],
"available" : "1"
}
I tried to set [] for field parameters if value 591eefe3470e6cd70c3c9872 exists in this array.
I tried:
$new = array('$set' => array("parameters" => []));
$this->collection->update(array("parameters" => "591eedbe470e6cd70c3c9871"), $new);
It does not work...
MongoDB's update() and save() methods are used to update document into a collection. The update() method updates the values in the existing document while the save() method replaces the existing document with the document passed in save() method.
MongoDB Update() Method
The update() method updates the values in the existing document.
Syntax
The basic syntax of update() method is as follows −
db.COLLECTION_NAME.update(SELECTION_CRITERIA, UPDATED_DATA)
Consider the mycol collection has the following data
{ "_id" : ObjectId(5983548781331adf45ec5), "title":"MongoDB Overview"}
{ "_id" : ObjectId(5983548781331adf45ec6), "title":"NoSQL Overview"}
{ "_id" : ObjectId(5983548781331adf45ec7), "title":"My Overview"}
Following example will set the new title 'New MongoDB Tutorial' of the documents whose title is 'MongoDB Overview'.
db.mycol.update({'title':'MongoDB Overview'},{$set:{'title':'New MongoDB Tutorial'}})
db.mycol.find()
{ "_id" : ObjectId(5983548781331adf45ec5), "title":"New MongoDB Tutorial"}
{ "_id" : ObjectId(5983548781331adf45ec6), "title":"NoSQL Overview"}
{ "_id" : ObjectId(5983548781331adf45ec7), "title":"My Overview"}
By default, MongoDB will update only a single document. To update multiple documents, you need to set a parameter 'multi' to true.
db.mycol.update({'title':'MongoDB Overview'},
{$set:{'title':'New MongoDB Tutorial'}},{multi:true})

Limit returned fields with PHP MongoClient find() query

I am writing a PHP MongoClient Model which accesses mongodb that stores deploy logs with gitlab information, server hosts, and zend restart instructions. I have a mongo Collection called deployAppConfigs. Its document structure looks like this:
{
"_id" : ObjectId("54de193790ded22d1cd24c36"),
"app_name" : "ai2_api",
"name" : "AI2 Admin API",
"app_directory" : "path_to_app",
"app_owner" : "www-data:deployers",
"directories" : [],
"vcs" : {
"type" : "git",
"name" : "input/ai2-api"
},
"environments" : {
"development" : {
...
},
"qa" : {
...
},
"staging" : {
...
},
"production" : {
...
},
"actions" : {
"post_checkout" : [
"composer_install"
]
}
}
Because there are many documents in this collection, I would like to query the entire collection for only the "vcs" sub document and the "app_name". I am able to execute this command in Robomongo's mongo shell with the following find() query:
db.deployAppConfigs.find({}, {"vcs": 1, "app_name": 1})
This returns exactly what I want for each document in the collection:
{
"_id" : ObjectId("54de193790ded22d1cd24c36"),
"app_name" : "ai2_api",
"vcs" : {
"type" : "git",
"name" : "input/ai2-api"
}
}
I am having a problem writing a PHP MongoClient equivalent to that mongo shell command. I basically want to make a PHP MongoClient version of this mongo docs example on Limit Fields to Return from a Query
I have tried using an empty array to replace the "{}" in the mongo shell command like this, but it hasn't worked:
$query = array (
array(),
array("vcs"=> 1, "app_name"=> 1)
);
All the fields share the vcs.type = "git" so I tried wrote a query that selects all fields in every document based on that shared value. It looks like this:
$query = array (
"vcs.type" => "git"
);
But this returns the entire document, which is what I want to avoid.
The alternative could be to do a limit projection find() for the first document in the collection and then use the MongoCursor to iterate through the whole collection, but I'd rather not have to do the extra loop if possible.
Essentially, I am asking how to limit the return fields of a find() query to only one subdocument of each document in the entire collection.
looks like I was able to find the solution... I will solve the question and leave it up in case it ends up being useful to anyone else.
What I ended up having to do was alter my MongoClient custom class find() function, which calls the $collection->find() query, to include a $fields parameter.
Now, the MongoClient->find() query looks like this:
$collection->find(
array("vcs.type" => "git"),
array("vcs" => 1, "app_name" = 1)
)
Found the answer on the MongoClient::cursor::find() : here

MongoDB-PHP: JOIN-like query

Here are the objects:
courses
{ "name" : "Biology", "_id" : ObjectId("4b0552b0f0da7d1eb6f126a1") }
students
{
"name" : "Joe",
"classes" : [
{
"$ref" : "courses",
"$id" : ObjectId("4b0552b0f0da7d1eb6f126a1")
}
],
"_id" : ObjectId("4b0552e4f0da7d1eb6f126a2")
}
Using the PHP Mongo Class, how do I get all the students that has a biology course?
Thanks
You'll need to query twice. I dont have my environment in front of me, but something similar to what's below. I may have the "nested" portion of the second query incorrect.
// First grab the ID for the course.
$course = $collection->findOne(array("name" => "Biology"));
// Next query the students collection.
$collection->find(array("classes" => array("id" => $course['_id'])));

Categories