Updating value of a document MongoDB PHP - php

Inside a database called user_notifications there is a collection for each user with their user id.
There should a static document that would contain a counter column default set to 0 for each collection.
In a simple method call i need to increment the counter value by 1, as a newbie in MongoDB i think i am messed up. Need some idea, here is my object that would do that:
private function increment_notification()
{
// database => notification
// collection => _238
// document => unseen_counter
$unseen = $this->notification->_238->unseen_counter; // selecting a single document
// if the document didn't exists then create the document and
// set a default value 0 to the counter row.
if(!$unseen) {
// create a new document
$this->notification->insertOne([
'_id' => 'unseen_counter',
'counter' => '0'
]);
} else {
// we already have the document
// now update the counter value by 1
$unseen->update([
'$set' => [
'counter' => $unseen['counter'] + 1;
]
]);
}
}
I ended up writing this method as a pseudo code :[
As a beginner in MongoDB, how is my schema design? any suggestion to make it better?
Also, how i should run the above queries?
Thanks

Another way of doing this is to use the $inc operator. Here is documentation and example for $inc.

Related

get first element of an array

Lets assume, the return value of an search-fuction is something like this
// If only one record is found
$value = [
'records' => [
'record' => ['some', 'Important', 'Information']
]
]
// If multiple records are found
$value = [
'records' => [
'record' => [
0 => ['some', 'important', 'information'],
1 => ['some', 'information', 'I dont care']
]
]
]
what woul'd be the best way to get the important information (in case of multiple records, it is always the first one)?
Should I check something like
if (array_values($value['record']['records'])[0] == 0){//do something};
But I guess, there is a way more elegant solution.
Edit:
And btw, this is not realy a duplicate of the refered question which only covers the multiple records.
If you want the first element of an array, you should use reset. This function sets the pointer to the first element and returns it.
$firstValue = reset($value['record']['records']);
Edit.. after reading your question again, it seems, you dont want the first element.
You rather want this
if (isset($value['record']['records'][0]) && is_array($value['record']['records'][0])) {
// multiple return values
} else {
// single return value
}
Doing this is kind of error proun and i wouldn't suggest that one function returns different kinds of array structures.
check like this..
if(is_array($value['records']['record'][0])) {
// multiple records
} else {
// single record
}

Update subdocument with mongodb and php

I have this document:
places: {
'name': 'wallmart',
'categories' : [{'name':'store','accuracy':'1'}]
}
A place can have a lot of categories, each with a different value.
In certain situations, the value of a category can rise.
Im trying to update but its not working, it changes the document and becomes like this:
{...
'categories' : {'name':'store','accuracy':'2'}
...}
It removes the array and add just one subdocument.
This is my php code:
$query = array('_id'=>$place['_id']);
$acc = $place_cat['accuracy'] + 1;
$params = array('$set' => array("categories" => array('name'=>$cat['name'],'accuracy'=>$acc)));
$col->update($query,$params);
Which is the correct update sintax?
Thanks
$col->update(array('_id'=>$place['_id'], 'categories.name'=>'store'),
array('$inc' => array('categories.$.accuracy'=>1)))
Will do what you want, atomically as well.

mongo add field to document ($set doesn't)

What I want:
fetch some documents
for every document, set a field:
change it, if it exists
add it, if it doesn't
What I do:
// fresh data
print_r(iterator_to_array($collection->find()->limit(2)));
// query
$docs = $collection->find()->limit(2);
// fetch
foreach ( $docs AS $id => $doc ) {
// update
$collection->update(array('_id' => $doc['_id']), array(
'$set' => array(
'existing_field' => 'x',
'new_field' => 'y',
),
), array('multiple' => false));
}
// verify
print_r(iterator_to_array($collection->find()->limit(2)));
Why doesn't that do anything? The existing field isn't changed and the new field isn't added. Is the condition maybe wrong??
PS. The full code (with a lot of junk in between): http://pastebin.com/CNfCVxex
lines 33 - 37 -- fresh data
lines 63 - 76 -- query, fetch & update
lines 79 - 80 -- verify
Not familiar with the driver you are using here, but from your description you can achieve what you want in a single database hit, no need to fetch/loop/update..
The $set operator will insert or update a field depending on whether it exists or not.
db.Collection.update({}, { $set : { "myfield" : "x" } }, false, true)
The above would set the 'myfield' field in all documents in the collection to 'x', or if it already exists it would change the value to 'x'. Is that what you want to achieve?
I suspect you need to convert the string $doc['_id'] back to a MongoId by using new MongoId. See the example at the bottom of this page: http://www.php.net/manual/en/class.mongoid.php

Map Reduce To Get Most popular tags

I have a problem that I need some help on but I feel I'm close. It involves Lithium and MongoDB Code looks like this:
http://pastium.org/view/0403d3e4f560e3f790b32053c71d0f2b
$db = PopularTags::connection();
$map = new \MongoCode("function() {
if (!this.saved_terms) {
return;
}
for (index in this.saved_terms) {
emit(this.saved_terms[index], 1);
}
}");
$reduce = new \MongoCode("function(previous, current) {
var count = 0;
for (index in current) {
count += current[index];
}
return count;
}");
$metrics = $db->connection->command(array(
'mapreduce' => 'users',
'map' => $map,
'reduce' => $reduce,
'out' => 'terms'
));
$cursor = $db->connection->selectCollection($metrics['result'])->find()->limit(1);
print_r($cursor);
/**
User Data In Mongo
{
"_id" : ObjectId("4e789f954c734cc95b000012"),
"email" : "example#bob.com",
"saved_terms" : [
null,
[
"technology",
" apple",
" iphone"
],
[
"apple",
" water",
" beryy"
]
] }
**/
I am having a user savings terms they search on and then I am try to get the most populars terms
but I keep getting errors like :Uncaught exception 'Exception' with message 'MongoDB::__construct( invalid name '. does anyone have any idea how to do this or some direction?
First off I would not store this in the user object. MongoDb objects have an upper limit of 4/16MB (depending on version). Now this limit is normally not a problem, but when logging inline in one object you might be able to reach it. However a more real problem is that every time you need to act on these objects you need to load them into RAM and it becomes consuming. I dont think you want that on your user objects.
Secondly arrays in objects are not sortable and have other limitations that might come back to bite you later.
But, if you want to have it like this (low volume of searches should not be a problem really) you can solve this most easy by using a group query.
A group query is pretty much like a group query in sql, so its a slight trick as you need to group on something most objects share. (An active field on users maybe).
So, heres a working group example that will sum words used based on your structure.
Just put this method in your model and do MyModel::searchTermUsage() to get a Document object back.
public static function searchTermUsage() {
$reduce = 'function(obj, prev) {
obj.terms.forEach(function(terms) {
terms.forEach(function(term) {
if (!(term in prev)) prev[term] = 0;
prev[term]++;
});
});
}';
return static::all(array(
'initial' => new \stdclass,
'reduce' => $reduce,
'group' => 'common-value-key' // Change this
));
}
There is no protection against non-array types in the terms field (you had a null value in your example). I removed it for simplicity, its better to probably strip this before it ends up in the database.

does sfWidgetFormSelect provide a string or an int of the selected item?

I'm having an annoying problem. I'm trying to find out what fields of a form were changed, and then insert that into a table. I managed to var_dump in doUpdateObjectas shown in the following
public function doUpdateObject($values)
{
parent::doUpdateObject($values);
var_dump($this->getObject()->getModified(false));
var_dump($this->getObject()->getModified(true));
}
And it seems like $this->getObject()->getModified seems to work in giving me both before and after values by setting it to either true or false.
The problem that I'm facing right now is that, some how, sfWidgetFormSelect seems to be saving one of my fields as a string. before saving, that exact same field was an int. (I got this idea by var_dump both before and after).
Here is what the results on both var dumps showed:
array(1) {["annoying_field"]=> int(3)} array(1) {["annoying_field"]=>string(1)"3"}
This seems to cause doctrine to think that this is a modification and thus gives a false positive.
In my base form, I have
under $this->getWidgets()
'annoying_field' => new sfWidgetFormInputText(),
under $this->setValidators
'annoying_field' => new sfValidatorInteger(array('required' => false)),
and lastly in my configured Form.class.php I have reconfigured the file as such:
$this->widgetSchema['annoying_field'] = new sfWidgetFormSelect(array('choices' => $statuses));
statuses is an array containing values like {""a", "b", "c", "d"}
and I just want the index of the status to be stored in the database.
And also how can I insert the changes into another database table? let's say my Log table?
Any ideas and advice as to why this is happen is appreciated, I've been trying to figure it out and browsing google for various keywords with no avail.
Thanks!
Edit:
ok so I created another field, integer in my schema just for testing.
I created an entry, saved it, and edited it.
this time the same thing happened!
first if you what the status_id to be saved in the database, you should define your status array like this:
{1 => "a", 2 => "b", 3 => "c", 4 => "d"}
So that way he know that 1 should be rendered like "a" and so on. Then, when saving, only the index should be saved.
About saving in another database, my advise is to modify the doSave method defined by the Form class yo match your needs. I only know how Propel deals with it, maybe this could help:
the doSave method dose something like this:
protected function doSave($con = null)
{
if (null === $con)
{
$con = $this->getConnection();
}
$old = $this->getObject()->getModifiedValues($this);//Define this
$new_object = new Log($old);//Create a new log entry
$new_object->save($con));//save it!
$this->updateObject();
$this->getObject()->save($con);
// embedded forms
$this->saveEmbeddedForms($con);
}
Hope this helps!
Edit:
This is an example extracted from a model in one of my applications and its working ok:
Schema:
[...]
funding_source_id:
type: integer
required: true
[...]
Form:
$this->setWidget('funding_source_id', new sfWidgetFormChoice(array(
'choices' => array(1 => 'asdads', 2 => '123123123' , 3 => 'asd23qsdf'),
)));
$this->setValidator('funding_source_id', new sfValidatorChoice(array(
'choices' => array(1 => 'asdads', 2 => '123123123' , 3 => 'asd23qsdf'),
'required' => true
)));
About the log thing, that could be quite more complex, you should read the current implementation of the doSave method in the base form class, currently sfFomrObject on Symfony1.4., and when and how it delegates object dealing with modified values.
Okay,
It turns out I forgot to do a custom validator to use the array key instead.

Categories