How can I update based on _id in mongodb for YII?
What I tried is
$model= new MongoUrls();
$criteria = new EMongoCriteria;
$criteria->userid('==', $userid);
$criteria->screenshot_path('!=', null);
$criteria->screenshot_uploaded('!=', 1);
$availablescreenshots=$model-> findAll($criteria);
if(count($availablescreenshots)>0){
foreach($availablescreenshots as $obj1){
$path_parts = pathinfo($obj1->screenshot_path);
if($social->upload($obj1->screenshot_path, 'test',$path_parts['basename'])) {
$model->updateAll(array('_id'=>$obj1->_id ), array('screenshot_uploaded'=>1) );
}
}
}
But it shows an error "The EMongoDocument cannot be updated because it is new." in Yii .
I want to update a document where _id matches same value
If I am correct in assuming the extension you are using you actually want $model->updateAll() since update() relates to updating the current active record not to running a general query. It is a bit confusing but it is the way Yii works.
As yii mongosuite docs states, updateAll is a bit different in use than usual update. Also, you are using updateAll in loop and as condition you pass single id which not really makes sense. With updateAll you could use criteria to update models. Here you should use partial update like that:
// _id is already set because it comes from db
$obj1->screenshot_uploaded = 1;
// First param to set fields which should be updated
// Set second param to true, to make partial update
$obj1->update(array('screenshot_uploaded'), true);
The method worked for me was
$modifier = new EMongoModifier();
$modifier->addModifier('screenshot_uploaded', 'set', '1');
$criteria = new EMongoCriteria();
$criteria->addCond('_id','==', $obj1->_id );
$model->updateAll($modifier,$criteria );
Related
I am trying to implement the "Edit Application Settings" feature. After a bit of thinking, my configuration values are stored in the DB with key -> value structure, like this:
id
key
value
1
logo_path
img/logo.png
As you can see, for each setting, there is only a key & value column. I made an App Service provider to cache them forever, and a helper function (config('setting_key')) to get the value, but now I'd like to update it in the most efficient way.
The user interface consists of the <form action="post" ...> and input with a corresponding name, like this: <input name="setting_key_name" ... />. As you can see, the name attribute here has the value of the key column value and the actual value of the input would be the value column value (a bit of confusion here).
First thing that came to my mind, was to make a foreach loop and find & update every row in DB, but IMHO it is very unoptimized way, cause if the page has a form with 10 values, it is 10 SQL queries. But till now, this is what I've done:
$keys = collect($request->except('_token'))->keys()->toArray();
// get all settings if the key name matches the request's input name
$setting = Setting::whereIn('key', $keys)->get();
$logo = self::GENERAL_APP_LOGO; // contant with a key-name (general_application_logo);
if($request->has(self::GENERAL_APP_LOGO) && $request->$logo) {
// Processing uploaded image here;
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name); // Using an upload trait
$setting->where('key', $logo)->value = self::LOGO_IMAGE_PATH . $name; // just a try to update the DB this way
}
foreach ($keys as $key) {
$setting->where('key', $key)->value = $request->$key; // putting all request's input values to corresponding key
}
$setting->save(); // saving the DB.
As you can see, this won't work and will throw an Exception, like Call to undefined method ...\Eloquent\Builder::save(). I tried the same code with an update, but the difficult part here is to update it multiple times (since the if section should have the update as well, for the logo), as well as binding the key to value.
So, a little bit of your help would be appreciated - what the logic should be here? How can I update a DB rows with corresponding column's value? I mean - like this (update where key = 'general_app_name' set value, 'some_setting_value'), but using the optimized and clear way?
Working solution
As #miken32 stated in his answer, I used hid version of code, but with slight changes:
// Changed the $request->settings->keys() to PHP native method array_keys():
$settings = Settings::whereIn('key', array_keys($request->settings))->get()->groupBy('id');
// Also, here I changed the `whereIn('id', ...)` to `whereIn('key', ...)`, since it was my primary index.
foreach ($request->settings as $k=>$v) {
if ($k === self::GENERAL_APP_LOGO_ID) {
// not sure about this one, but I think this is
// how you'd access a file input in an array
$image = $request->file('settings')[$k];
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name);
$v = self::LOGO_IMAGE_PATH . $name;
}
// take the Setting object out of the list we pulled
// Here I added the ->first() to get the first element from the retrieved collection;
$setting = $settings->get($k)->first();
$setting->value = $v;
$setting->save();
}
Since I was fetching the configuration values via helper, that only returns the value of the current key (and no id column), I changed the id to key and made the key as my PK in a model. Works like a charm!
With each setting in a separate row, there's no way to avoid multiple database queries – one to get the current values for all settings, and other to update each one. Looking up items by primary key is more efficient, so I'd recommend putting the contents of the id column in your blade view, like this:
<label for="setting_{{$setting->id}}">{{$setting->key}}</label>
<input name="settings[{{$setting->id}}]" id="setting_{{$setting->id}}" value="{{$setting->value}}"/>
Now in your controller, $request->settings will be an array you can loop through. You can continue treating your file upload separately, but now you've got the id column to look up, so change your constant to that.
$settings = Settings::whereIn('id', $request->settings->keys())->get()->groupBy('id');
foreach ($request->settings as $k=>$v) {
if ($k === self::GENERAL_APP_LOGO_ID) {
// not sure about this one, but I think this is
// how you'd access a file input in an array
$image = $request->file('settings')[$k];
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name);
$v = self::LOGO_IMAGE_PATH . $name;
}
// take the Setting object out of the list we pulled
$setting = $settings->get($k);
$setting->value = $v;
$setting->save();
}
Note that Laravel does offer methods to bulk-update multiple models at once, but they are doing separate queries to the database in the background. IIRC, the save() method doesn't do anything if the value hasn't changed, which will spare you some hits.
You could try creating a text field, or a json field if your database supports it, and storing all of your settings as a JSON string in that field.
id
settings
1
{ "logo_path" : "img/logo.png", "foo" : "bar", "thing_count" : 17 }
2
{ "logo_path" : "img/logo2.png", "foo" : "baz", "thing_count" : 4 }
In your Laravel model, you can cast it as an array
protected $casts = ["settings" => "array"];
and then use it from the model
echo $theModel->settings['logo'];
echo $theModel->settings['foo'];
or you can cast it as a fully fledged object if you need to using value object casting.
One gotcha that can be confusing for people is the setting of the values in the array to update it. This will not work:
$theModel->settings['foo'] = "boz";
The reason is due to the way the Laravel mutators work. Instead, you make a value copy of the settings, change that, and reassign it to the model:
$settings = $theModel->settings;
$settings['foo'] = "boz";
$theModel->settings = $settings;
This approach has the capacity to infinitely expandable in the future as you just add new keys to your json. Be sure to do checks on the settings array to ensure fields you are looking for are set (which is why value objects can be very handy to do validation).
It also solves your database query problem - it's only ever one.
You don't need to put
$setting->where('key', $logo)->value = ...;
Just call
$setting->where('key', $logo)->update($request->toArray());
$setting->save(); called when you instantiated setting class like :
$setting = new Setting();
Or
$setting = Setting::whereIn('key', $keys)->get()->first();
Then
$setting->val = ...;
$setting->save(); // then it work's
In some part of my app I need to update only the field is_active of some table with a lot of fields. What is the best approach to update only this field and avoid the validations and requiriments of all other fields?
And if you want to update particular row only , use this:
$users= TableRegistry::get('Users');
$user = $users->get($id); // Return article with id = $id (primary_key of row which need to get updated)
$user->is_active = true;
// $user->email= abc#gmail.com; // other fields if necessary
if($users->save($user)){
// saved
} else {
// something went wrong
}
See here (Updating data in CakePHP3).
This will work:
$users = TableRegistry::get('Users');
$query = $users->query();
$query->update()
->set(['is_active' => true])
->where(['id' => $id])
->execute();
http://book.cakephp.org/3.0/en/orm/query-builder.html#updating-data
When you don't want callbacks to be triggered, just use updateAll()
$table->updateAll(['field' => $newValue], ['id' => $entityId]);
Using the example here: http://book.cakephp.org/3.0/en/orm/database-basics.html#running-update-statements. Run the code below to update all records in table_name_here table with a new value for is_active column.
use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
$connection->update('table_name_here', ['is_active' => 'new_value_here']);
I faced this issue when upgrading my project from 2.10 to 3.x.
In 2.10 you could update a single field using:
$this->Menus->saveField('css', $menucss);
But since this method was deprecated, we do as below now, considering that callbacks will not be triggered:
$this->Menus->updateAll(['css' => $menucss], ['id' => $menu_id]);
The other answers don't use internationalization and other models props, callbacks, etc.
I think this is because of the query builder, it does not use the models and so their behaviors, therefore you should use:
$this->loadModel('Inputs');
$input = $this->Inputs->find()->where(['`key`' => $this->request->data['id']])->first();
$this->Inputs->patchEntity($input, ['prop' => $this->request->data['prop']]);
if ($this->Inputs->save($input)) {
die(json_encode(true));
} else {
die(json_encode(false));
}
I do have the invitations table set up and in the database. I use it for other purpose such as adding more...
my goal: update the first record with a new value to a field:
I tried:
$invitation = new MemberInvitation();
$invitation1 = $invitation->find(1);
$invitation1->status = 'clicked';
$invitation1->save();
And also:
$invitation1 = \App\Model\MemberInvitation::find(1);
$invitation1->status = 'clicked';
$invitation1->save();
both ended with:
Creating default object from empty value
EDIT:
This piece of code worked and updated my records correctly -- I just can't do it via Eloquent's model:
\DB::table('member_invitations')->where('id', '=', $member_invitation->id)
->update(array('status' => 'clicked', 'member_id' => $member->id));
what am I missing?
Try this.
$invitation = MemberInvitation::findOrFail(1);
$invitation->status = 'clicked';
$invitation->save();
If that doesnt work, please show your model
find(1) doesn't mean "give me the first record", it means "give me the first record where id = 1". If you don't have a record with an id of 1, find(1) is going to return null. When you try and set an attribute on null, you get a PHP warning message "Creating default object from empty value".
If you really just want to get the first record, you would use the first() method:
$invitation = \App\Model\MemberInvitation::first();
If you need to get a record with a specific id, you can use the find() method. For example, to translate your working DB code into Eloquent, it would look like:
$invitation = \App\Model\MemberInvitation::find($member_invitation->id);
$invitation->status = 'clicked';
$invitation->member_id = $member->id;
$invitation->save();
The answer is obvious based on your reply from May 15th.
Your "fillable" attribute in the model is empty. Make sure you put "status" in that array. Attributes that are not in this array cannot up modified.
I have a problem. I have this structure of db in mongodb:
id:"xxx",
is_validated: "xxx",
validation_code:"xxx",
profile:[
{
profile_pic:"xxx",
firstname:"xxx",
lastname:"xxx",
}
]
I am using cakephp. When I update the record, I use this:
$this->User->set('id', "xxx");
$this->User->set('profile', array('firstname' => 'Benedict'));
$this->User->save()
When I save the record, the whole array of profile is deleted and only saves the "firstname":
id:"xxx",
is_validated: "xxx",
validation_code:"xxx",
profile:[
{
firstname:"xxx"
}
]
I need to be able to save the firstname without deleting the other array records of mongodb using cakephp
Do you not follow the standard convention when using MongoDB? (documentation):
// where '1' is the id of your user
$this->User->read(null, 1);
// set the new value for the field
$this->User->set('profile', array('firstname' => 'Benedict'));
// commit the changes to the database
$this->User->save();
Update
If the above doesn't work, try reading the whole record and modifying accordingly:
// set the active record
$this->User->id = 1;
// read the entire record
$user = $this->User->read();
// modify the field
$user['User']['profile']['firstname'] = 'Benedict';
// save the record
$this->User->save($user);
The reason this is happening is because you are asking for the profile field to be updated with the array that you pass. This then duly replaces the current profile array with yours.
To get round this you will have to pass the complete array in i.e. with the keys you want to keep and their values as well as the keys you want to change.
I've been trying all night to update a record like this:
$r = $this->Question->read(NULL, $question['Question']['id']);
debug($r);// This is a complete Question array
$this->Question->set('status', 'version');
$s = $this->Question->save();
//$s = $this->Question->save($r['Question']);//this also doesn't work
debug($s); // = False every time!! Why??
exit;
The two comments show variations I've tried but didn't work either.
#Dipesh:
$this->data = $this->Question->read(NULL, $question['Question']['id']);
$this->Question->status = 'version';
$s = $this->Question->save($this->data);
debug($s);
exit;
#Dipesh II:
$this->request->data = $this->Question->read(NULL, $question['Question']['id']);
debug($this->data);
//$this->Question->status = 'version';
$this->request->data['Question']['status'] = 'version';
$s = $this->Question->save($this->request->data);
//$s = $this->Question->save($r['Question']);
debug($s);
exit;
#Dipesh III:
(removed)
cakePHP provide a method called set() in both Models::set() as well as in Controller::set();
About Controller::set()
This method is used to set variables for view level from any of the controller method. For example fetching records and from models and setting them for views to display it to clients, like this
$data = $this->{ModelName}->find('first');
$this->set('dataForView',$data) // now you can access $data in your view as $dataForView
About Model::set()
This method is used to set data upon a model, the format of the array that will be passed must be same as that used in Model::save() method i.e. like this
$dataFormModel = array('ModelName'=>array('col_name'=>$colValue));
$this->{ModelName}->set($dataForModel);
Model::set() will accept its parameter only in this format, once successfully set you can do following
validate this data against the validation rules specified in model directly like this
$isValid = $this->ModelName->validate();
save/update data by calling Model::save()
Use $this->data instead of $r
Example
$this->data = $this->Question->read(NULL, $question['Question']['id']);
$this->set is used to set variable value and pass it to view so view can access it where as $this->data represent the data to be stored in database.
If You're using Cake 2.0 then replace $this->data which is read only in Cake 2.0 to $this->request->data.
It's not very "automagical" but I was able to get this working like this:
$set_perm_id = 42;//who cares
$data = array(
'Question'=> array(
'id'=> $question['Question']['id'],
'perm_id'=> $set_perm_id,
'status'=>'version'
)
);
$s=$this->Question->save($data);
Basically I'm just building the data array manually. If anyone knows why this works instead of what I was doing before, I'd love to hear it.
Just try these lines..
$this->Question->id = $question['Question']['id'];
$this->Question->set('status','version');
$this->Question->save();
OR
$aUpdate["id"] = $question['Question']['id'];
$aUpdate["status"] = "version";
$this->Question->save($aUpdate);