PROBLEM 1:
When I try to save() any Yii Model, it updates all fields in the row.
The problem is: When I try to save model users, even if has no PASSWORD to update, it get the database value(already hashed) and hash again.
How can I do to YII only update fields that I want?
Code:
$user = Users::model()->findByAttributes(array('username'=>$this->username));
$user->ip = $_SERVER['REMOTE_ADDR'];
$user->save();
Users.php (Model):
public function beforeSave() {
if (!empty($this->password))
$this->password=$this->hashPassword($this->password);
return true;
}
PROBLEM 2:
I have an API that can create USERS.
API Tutorial: http://www.yiiframework.com/wiki/175/how-to-create-a-rest-api/
When I have crypter_password in the database, instead password, I got the error: Parameter password is not allowed for model Users, because the API validate parameters using $model->hasAttribute().
How can I fix the API actionCreate to allow custom parameters?
According to Yii's doc: http://www.yiiframework.com/doc/api/1.1/CActiveRecord#save-detail
public boolean save(boolean $runValidation=true, array $attributes=NULL)
$attributes - array - list of attributes that need to be saved. Defaults to null, meaning all attributes that are loaded from DB will be saved.
You can pass in an array of fields that you want to save.
Eventhough the other answers listed here are not wrong, they are definitely not really developer friendly and it's extremely easy to forget to add the attributes to the save line.
Here is a developer friendly way of working.
In your model, add the following attribute:
private $_aAttributesBackup;
In this variable, we will store an exact copy of the current model. To do this, the following afterFind method needs to be added:
public function afterFind()
{
$this->_aAttributesBackup = $this->attributes;
}
Almost there. At this point, the model will store all of his attributes in the attributesBackup field which makes it easier to compare. To make it easier, we also need a method that will check if the specified attribute has a backup value. We do this by adding the following code into our model:
public function getOriginalAttribute($sAttribute)
{
if ($this->_aAttributesBackup)
{
return $this->_aAttributesBackup[$sAttribute];
}
return NULL;
}
Now, how about checking if the password has been changed? Simple, by adding the following beforeSave code:
public function beforeSave()
{
if ($this->getOriginalAttribute('password') != $this->password)
{
$this->password = sha1($this->password);
}
return parent::beforeSave();
}
Et voila. Now everytime you execute the code $Model->save(); the system will check if the password has been changed, If the password is changed, it will hash it again, if it is not changed, it won't be hashed again.
Save () inserts a row into the database table if its isNewRecord property is true. Otherwise, it will update the corresponding row in the table (usually the case if the record is obtained using one of those 'find' methods.)
What you have to do is update specific field so you can use SaveAttributes and it accepts the array of string values that have been updated for example demo code is as follow
$user = Users::model()->findByAttributes(array('username'=>$this->username));
$user->ip = $_SERVER['REMOTE_ADDR'];
$user->SaveAttributes(array('ip'));
Related
I am using laravel 8 with an existing user table. All is working as expected except the password reset link functionality. This is because my table has the email column name as "Email" instead of "email." Other applications use this table, so the column name cannot be changed. I can get the password reset link functionality working if I manually set the column name within the framework itself (example below).
File: /vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php
public function retrieveByCredentials(array $credentials)
{
// framework code that retieves the user record for email address
if ($res) {
$res->email = $res->Email;
}
// rest of frame work code
}
This seems a little "hacky." Is there a better approach to this?
Laravel would benefit greatly from more customization regarding the user's table (custom user table name, column names, etc.).
Laravel has mutators and accessors. This does that you can change behavior of ->email access or assigning it. Add this snippet to your User.php model.
class User {
public function getEmailAttribute()
{
return $this->attributes['Email'];
}
}
You can read the docs about it. The naming convention for the function is get{PropertyName}Attribute, if you define your function like so, you can easily overwrite property logic in Laravel. Making it use the column Email.
The users table, besides others, have these fields: username, first_name, last_name. Each user can decide whether to show the username or the full name (first + last). This choice is stored inside a "settings" table.
To not perform repeated queries and calls to a function any time I need to show the name, I add the name to display to the user object as it is created, like $user->display_name = ... according to the user's choice.
The problem is that when the user updates the profile, Laravel tries to save this name inside a display_name field into the users' table, which doesn't exist and returns a 500 error. That also happens when the user tries to logout.
Is it possible to avoid that Laravel tries to store that value inside the database?
As suggested in other discussions I have already tried to give a default value to the attribute inside the User model, I've tried to set the attribute as protected, but nothing did work.
This is where the get{...}Attribute() function of a Model comes in handy. Say you want to access $user->full_name without actually saving full_name to the database. Since you have first_name and last_name, you can declare on your User model:
public function getFullNameAttribute(){
return $this->first_name." ".$this->last_name;
}
Laravel will parse what's between get and Attribute into a property available on the model, in this case either $user->full_name or $user->fullName.
To translate this to your use case, you can use something like:
In your User.php model:
public function getDisplayNameAttribute(){
if($this->settings == "use_full_name"){
return $this->first_name." ".$this->last_name;
} else if($this->settings == "use_username"){
return $this->username;
}
return "Not Configured...";
}
Note: You'll have to configure your if statements to determine what to return based on your settings.
Somewhere in a controller or view, you can call $user->display_name to have one of 3 things (determined by the logic/return statements above) displayed:
public function example(){
$user = User::first();
dd($user->display_name);
// $user->first_name." "$user->last_name, $user->username or "Not Configured..."
}
By doing this, when you access your $user, it will have a display_name property available that doesn't actually exist on the model, so you won't run into issues should you call $user->save();
I'm trying to update a model, I load the model, take all the data from the POST and then save it, easy... But my record was never updating so went to the log and discovered that the update query is adding a weird condition. FYI, MD_ID is my primary key.
So, I load the model, the next line is the SQL produced by Yii:
$model = Ositems::model()->findByPk($id);
SELECT * FROM "MTODETALLADO_INV" "t" WHERE "t"."MD_ID"=249217
If echo the json_encode of the loaded model I get that dictionary in my browser:
echo json_encode($model->getAttributes());
{""MD_BODEGA":"01","MD_PRODUCTO":"0031253","MD_CANTIDAD":"1","MD_PRECIOTOTAL":"1466",,"MD_PORCENTAJEDESCUENTO":"0","MD_IDCABECERA":"97403","MD_ID":"249217","MD_OBSERVACION":null}
At this point everything looks right, now I take the values from post:
$model->attributes = $_POST;
And here if echo the values of the model I get the new values right, now here is the problem: I save the model and this is the SQL Yii runs (I replaced the :yp_ values to make it more readable)
$model->save();
UPDATE "MTODETALLADO_INV" SET
MD_BODEGA"='01'
MD_PRODUCTO"='0020514
MD_CANTIDAD"='10'
MD_PORCENTAJEDESCUENTO"='0
MD_IDCABECERA"=97403
MD_ID"=249218
MD_PRECIOTOTAL"='36210'
MD_OBSERVACION"=''
WHERE "MTODETALLADO_INV"."MD_ID"=1
And there is the problem! WHERE "MTODETALLADO_INV"."MD_ID"=1, Why would it make it 1 if all this time my model id has been 249218 ?
A few considerations:
My model only takes some columns that I need from the actual table, Yii sets the other columns as null and I omitted them in the previous code.
The table is in a foreign db, I use have a custom ActiveRecord which manages the CDbConnection to a database according to the user. (It's a webservice app)
I followed what the function save() did and could finally find the problem was when it tried to get the primary key. I had this method in my model:
public function primaryKey()
{
return array('MS_ID');
}
}
But it had to be:
public function primaryKey()
{
return 'MS_ID';
}
}
Somehow that was causing the problem.
I'm able to save data to the database and it creates a uuid in the primary key column(id). When I call $model->save(); and access $model->id afterwards, it returns:
{"expression":"UUID()","params":[]}
It should return the actual value in the database...How can I get the actual value that mysql stores?
I have a beforeSave function that adds in the uuid:
public function beforeSave() {
if ($this->isNewRecord)
$this->id = new CDbExpression('UUID()');
return parent::beforeSave();
}
I have also tried creating a trigger that executes the mysql uuid function before update on the id...The $model->id then returns null.
The problem is that you are telling Yii to run a MySQL function to insert into the DB. And it does so just fine, but Yii does not re-read the record afterwards to get the value. To re-read it would be inefficient because what you are doing is not very used and a read from the DB is not necessary usualy. You can go around this in 2 ways:
1) to read the value yourself from the db. You can do a $model->save(); $model->refresh(); right after and you should have the fresh data in the $model
2) you can make your function do a
public function beforeSave() {
if ($this->isNewRecord)
$this->id = Yii::app()->db->createCommand('select UUID()')->queryScalar();
return parent::beforeSave();
}
I have not tested this but you get the idea.
This will go to the DB, run UUID() and return to yii the result. By assigning to $this->id a string instead of a mysql command you will be able to use right away the value without doing a refresh. This will still use MySQL to get the UUID, so it will still put some stress on the server.
Usually I do something like
public function beforeSave() {
if ($this->isNewRecord)
$this->hash = hash('ripemd160',microtime());
return parent::beforeSave();
}
So I do not use the MySQL server at all, but because you use your column as the ID you might want UUID() to generate a truly unique ID.
I'm using the cacheCounter in CakePHP, which increments a counter for related fields.
Example, I have a Person table a Source table. Person.source_id maps to a row in the Source table. Each person has one Source, and each Source has none or many Person rows.
cacheCounter is working great when I change the value of a source on a person. It increments Source.Person_Count. Cool.
But when it increments, it adds it to the destination source for a person, but doesn't remove it from the old value. I tried updateCacheControl() in afterSave, but that didn't do anything.
So then I wrote some code in my model for afterSave that would subtract the source source_id, but it always did this even when I wasn't even changing the source_id. (So the count went negative).
My question: Is there a way to tell if a field was changed in the model in CakePHP?
To monitor changes in a field, you can use this logic in your model with no changes elsewhere required:
function beforeSave() {
$this->recursive = -1;
$this->old = $this->find(array($this->primaryKey => $this->id));
if ($this->old){
$changed_fields = array();
foreach ($this->data[$this->alias] as $key =>$value) {
if ($this->old[$this->alias][$key] != $value) {
$changed_fields[] = $key;
}
}
}
// $changed_fields is an array of fields that changed
return true;
}
With reference to Alexander Morland Answer.
How about this instead of looping through it in before filter.
$result = array_diff_assoc($this->old[$this->alias],$this->data[$this->alias]);
You will get key as well as value also.
You could use ->isDirty() in the entity to see if a field has been modified.
// Prior to 3.5 use dirty()
$article->isDirty('title');
check the doc: https://book.cakephp.org/3/en/orm/entities.html#checking-if-an-entity-has-been-modified
Edits happen infrequently, so another select before you do the update is no big deal, so, fetch the record before you save, save it, compare the data submitted in the edit form with the data you fetched from the db before you saved it, if its different, do something.
In the edit view, include another hidden field for the field you want to monitor but suffix the field name with something like "_prev" and set the value to the current value of the field you want to monitor. Then in your controller's edit action, do something if the two fields are not equal. e.g.
echo $form->input('field_to_monitor');
echo $form->hidden('field_to_monitor_prev', array('value'=>$form->value('field_to_monitor')));
See if the "save" uses some sort of DBAL call that returns "affected rows", usually this is how you can judge if the last query changed data, or if it didn't. Because if it didn't, the affected rows after an UPDATE-statement are 0.
You can call getAffectedRows() on any model class.
From class Model :
/**
* Returns the number of rows affected by the last query
*
* #return int Number of rows
* #access public
*/
function getAffectedRows() {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
return $db->lastAffected();
}