How to call mongodb server function inside update statement - mongodb-php

Lets say there is an array defined as sub document.
User
first_name:"blah",
last_name: "blah",
scores [{score: 12},
{score: 13},
{score: 9},
{score: 14}]
Say I want to loop through this sub array inside the update statement and check if I need to add a new score. New score could be 8 or 16 if its 8 then nothing should be added, however if its 16 it should be added to the end of the list.
Is there is anyway to define javascript server side function and use it inside update statement, or there are other way of doing something like that? The important thing that operation has to be atomic.
Using php driver
public bool|array MongoCollection::update ( array $criteria , array $new_object [, array $options = array() ] )
How do I specify criteria to match those conditions that I have listed?

You can create a server-side javascript function, however the creators of mongo do not recommend this:
We do not recommend using server-side stored functions if possible.
If you need atomicity with complex logic, one approach with is to lock the document before working with it. You can achieve this with the findAndModify function. The basic gist of this is that you try to find your document as you normally would, with the additional criteria that locked = false. The modify part of this function would set locked = true.
db.people.findAndModify({
query: { name: "Andy", locked: false },
update: { locked: true },
});
You now are free to work with the document "atomically" When you write the changes back to your database, you set locked = false. Make sure you wrap your block in a try/catch/finally and ensure the document is unlocked in the finally block.
Note that if your operation is very simple (simply need to add a field to your list), then using findAndModify alone to update the document fields may be enough for what you need.
Edit: If you really want to use server-side functions here's an example from the PHP mongo documentation:
$response = $db->execute("function(x) { return x;}", array("input param"));
Here's how you could persist a function for later use (through the mongo shell)
db.system.js.save(
{ _id: "echoFunction",
value : function(x) { return x; }
}
)
Then later in PHP:
$response = $db->execute("echoFunction( 'test' )");
var_dump($response);
Edit2:
Your update function is much easier than I first understood. You simply need the following:
$db->update(
array("first_name" => "blah"),
array(
"$push" => array(
"scores" => array("score" => 16)
)
)
);
Edit2: Access your collection from inside a function
function push_score(id, score) {
//your logic here
//...
db.your_collection.update({_id: ObjectId(id), $push: {scores: {score: score}}})
}

Related

CakePHP 3.x - Modify user data in Auth component before session creation

When using the Auth component in CakePHP 3 you can define the findAuth() finder (or configure a different finder) to have control over what data is loaded:
// AppController
$this->loadComponent('Auth', [
//...
'authenticate' => [
'Form' => [
'finder' => 'auth'
]
],
//...
]);
// UsersTable
public function findAuth($query, array $options)
{
return $query
->...;
}
I need some functionality that cannot be done with the query builder. How can I post-process the loaded auth data before session creation?
Note that I have different ways of logging in my users, so I would prefer this be kept inside the AuthComponent logic.
(This is still for CakePHP 3, but a brief comment on how this could be done in the new CakePHP 4 Authentication plugion would also be appriciated.)
EDIT: Rough outline of what I need: data needs to re-organised in the users array based on current context, i.e. users can have an active project selected.
I'm still not really sure what exactly you need to re-organize in what way exactly, but generally you can modify the queried data using mappers/reducers and result formatters, the latter usually being the easier way.
Here's a quick example that would add an additional field named additional_data to the result in case a field named active_project_id is set:
$query->formatResults(function (\Cake\Collection\CollectionInterface $results) {
return $results->map(function ($row) {
if (isset($row['active_project_id'])) {
$row['additional_data'] = 'lorem ipsum';
}
return $row;
});
});
Such a finder query would work with the new authentication plugin too.
See also
Cookbook > Database Access & ORM > Query Builder > Adding Calculated Fields

mongodb #c driver aggregate: How to implement it in vb .net?

Good morning,
I have a collection with a huge number of documents, and what I want to do is group by a certain field and put the results into a new collection.
I know how to do this in MongoDB, and it works perfect:
db.collection_1.aggregate([
{ $group : {_id : "$field_1" } },
{ $out : "collection_group" } ])
I've found how to do this in C++ in the MongoDB documentation. In this case it executes a sum after grouping, I want to insert the result in a new collection, but I supose I would only have to change "$sum" for "$out" :
var collection = _database.GetCollection<BsonDocument>("collection_1");
var aggregate = collection.Aggregate().Group(new BsonDocument { { "_id", "$field_1" },
{ "count", new BsonDocument("$sum", 1) } });
The thing is that I can't find examples in vb .net. In vb .net, I've tried for example this to group:
Dim collection_act = db.GetCollection(Of BsonDocument)("collection_1")
collection_act.Group(New GroupArgs("_id", "$field_1"))
But it causes an error because the GroupArgs cannot be defined like this.
I tried to use the Aggregate method too, but I have the same problem defining the AggregateArgs, and I can't find how to define it.
The most interesting thing would be to create a pipeline and then add commands to it, because after doing this I have to remove documents based on these group.
Any help will be very appreciate.
Thanks.

vanilla php ldap query works. Symfony 3 ldap query fails. Why?

I'm trying to figure out how to use the Ldap class in Symfony3. I've successfully created and bound a connection but I can't get any results on a query. To make sure that the query actually works, I ran a bare php version:
if($lconn = ldap_connect('ds.mydomain.ca')){
ldap_set_option($lconn, LDAP_OPT_REFERRALS, 0);
ldap_set_option($lconn, LDAP_OPT_PROTOCOL_VERSION, 3);
if($lbind = ldap_bind($lconn,'webuser','password')){
$filter ="(&(sn=Smith)(givenname=J*))";
if(!$result = ldap_search($lconn, "dc=ds, dc=mydomain, dc=ca", $filter)) throw \Exception("Error in search query: ".ldap_error($lconn));
$output = ldap_get_entries($lconn, $result);
}else{
$output='bind failed';
}
} else {
$output= 'connection failed';
}
It returns the expected number of results.
On the other hand, this query done with Symfony 3's Ldap component returns 0 results:
//use Symfony\Component\Ldap\Ldap
$ldap = Ldap::create('ext_ldap', array(
'host' => 'ds.mydomain.ca',
'version' => 3,
'debug' => true,
'referrals' => false,
));
$ldap->bind('webuser', 'password');
$q = $ldap->query("dc=ds, dc=nrc, dc=ca", "(&(sn=Smith)(givenname=J*))");
$output = $q->execute();
Any idea why the Symfony ldap query fails when all its options should be identical to those I used for the bare php query?
I reposted this question on the Symfony github. #ChadSikorra was there too. And he made it clear what my issue was. Here's his explanation:
If you look at the collection class, nothing is done with the result
resource until initialize() is called in the class. If you do
return array('output' => array('bare' => $bare, 'symfony' =>
$symf->toArray())); it will call initialize and you'll see the
entries populated in the class. Unless there's something else going
on.
Do you still experience this issue with the latest 3.1+ versions?
Sorry but I don't go very often on Stack Overflow and spend most of my time on Github so I didn't see your question before.
As #ChadSikorra said, you should be using the toArray() method of the resulting Collection class, or you should iterate on the results directly.
The implementation is made so that the results are traversed in a memory-efficient manner, without storing all the results in an array by default, but the toArray() method can do this for you. Behind the scenes,it actually converts the resulting itératif to an array using the appropriate PHP function (iterator_to_array).
By the way, there used to be some inconsistency between the iterator and the toArray() function call, but that has been fixed in recent versions.
Cheers!

How to get Phalcon to not reload the relation each time I want to access it

I am using Phalcon and have a model Order that has a one-to-many relationship with model OrderAddress. I access those addresses through the following function:
public function getAddresses($params = null) {
return $this->getRelated("addresses", array(
"conditions" => "[OrderAddress].active = 'Y'"
));
}
The OrderAddress model has a public property errors that I do not want persisted to the database. The problem I am having is that everytime I access the getAddresses function, it reloads the object from MySQL which completely wipes the values that I set against that property.
I really only want the OrderAddress models to be loaded once, so that each call to getAddresses doesn't make another trip to the DB- it just iterates over the collection that was already loaded.
Is this possible?
I suppose there's no such option in phalcon, so it has to be implemented in your code.
You could create an additional object property for cached addresses, and return it if it's already been initialized:
protected $cachedAddresses = null;
public function getAddresses($params = null) {
if ($this->cachedAddresses === null) {
$this->cachedAddresses = $this->getRelated("addresses", array(
"conditions" => "[OrderAddress].active = 'Y'"
));
}
return $this->cachedAddresses;
}
This could be a quick solution, but it will be painful to repeat it if you have other relations in your code. So to keep it DRY, you could redefine a 'getRelated' method in base model so it would try to return cached relations, if they already were initialized.
It may look like this:
protected $cachedRelations = [];
public function getRelated($name, $params = [], $useCache = true) {
//generate unique cache object id for current arguments,
//so different 'getRelated' calls will return different results, as expected
$cacheId = md5(serialize([$name, $params]));
if (isset($this->cachedRelations[$cacheId]) && $useCache)
return $this->cachedRelations[$cacheId];
else {
$this->cachedRelations[$cacheId] = parent::getRelated($name, $params);
return $this->cachedRelations[$cacheId];
}
}
Then, you can leave 'getAddresses' method as is, and it will perform only one database query. In case you need to update cached value, pass false as a third parameter.
And, this is completely untested, but even if there're any minor errors, the general logic should be clear.

Yii model is validating but data could not be saved

I have a yii application. Data is validated properly. the $model->validate() returns true but data is not being saved. Is there any way that I know about the error. It does nothing. neither prints error nor any warning.
if (isset($_POST['Invoice'])) {
$model->validate();
$model->attributes = $_POST['Invoice'];
if (!$model->validate()) {
die(CVarDumper::dump($model->errors,10,true));
}
if ($model->save()) {
die("Data saved");
$this->redirect(array('view', 'id' => $model->id));
} else {
CVarDumper::dump($model->attributes,10,true);
CVarDumper::dump($model->errors,10,true);
}
}
if you override beforeSave or afterFind method in your model,
public function beforeSave() {
return true; //don't forget this
}
public function afterFind() {
return true; //don't forget this
}
make sure you return true for those function
If save() is returning true and there are no errors as such in your database and queries. Only thing, thats possible is you haven't marked some of the column safe for mass assignment via "$model->attributes".
Make sure the column you are trying to save are marked safe in the "rules" function in your model. You can mark columns safe via adding the following rule in "rules" function in the model.
array ( "column_name1, column_name2 ....." , "safe" )
I've just ran into something similar to this. Everything was validating correctly, and $model->save() was returning true, but no data was saved in the database.
The problem and solution was that I was creating the $model object like so:
$model = ClassName::model();
but you need to create the object like so:
$model = new ClassName;
If you have this problem, you replace this:
$model->save(false)
This solves your problem.
If you use $model->save(); the filters is running that is not good for you.
Fire up some logging and see what going on...
I got the same error when I was using reCaptcha. I just did this and it worked:
$model->scenario = NULL;
Make sure you do this AFTER validation.
I had the same issue, my mistake was with the post name in the controller, where I used $model->save. I had given wrong - if(isset($_POST['postname']))
If I am not wrong, you are doing an AR save() in the $model->save() method. You do not get any error, but the data is not saved as well.
If this is the case you would like to do a:
die(CVarDumper::dump($arObj->errors,10,true));
after the $arObj->save(); call. Most of the time this happens because of the Database rejecting the values provided for insert or update.
Also do not override your model constructor:
function __construct() { } // don't do this
The issue for me was that I had a property for the column name in the ActiveRecord class, so it wasn't saving.
You should not declare properties for column names as I guess the magic methods __get() and __set() are used to save data, I guess by checking if there are column changes when you click the save() method to avoid useless SQL queries. In my case, because the column was a user-declared property, it wasn't in the columns list and therefore changes to it were not detected.
Hope this helps other people