I am trying to use Phalcons save() function to either create or update a record depending on whether the record is already in the database from here
I am doing the following:
$vars = $this->request->getPost();
$code = new Code();
$code->save( $vars, array("code_type", "code", "name") );
When I am sending an update to my controller, the "id" field has the primary key populated, whereas it is blank (but the array key still exists) if I am creating the record.
My understanding is that the ORM should either create or update the record depending on whether the primary key exists or not. The problem I am having is that it is always creating the record- not updating.
I've also tried something like the below, however the reverse is true, when I use find:
$code = Code::findFirst($vars["id"]);
$code->save( $vars, array("code_type", "code", "name") );
Any idea what I could be doing wrong? I want to get to a point where I have a single controller for my insert/update actions.
It's not surprising that the first example is only creating new records, because you're neither loading an existing record nor including the "id" in the white list of fields to save. Instantiating your object as follows should solve the problem (assuming the "id" is an integer):
$code = ($id = (int) $vars['id']) ? Code::findFirst($id) : new Code();
Related
Code:
$item1 = Item::find(1);
$item1->foo = 1;
$item1->save();
$another_item1 = Item::find(1);
dd($another_item1->foo);//Is this value always 1?
My question:
Is always read the newly written data after calling save() method of ORM? In my example, Is $another_item1->foo always 1?
If the answer to question 1 is not, how could I ensure I read the newly written data from the database?
Is always read the newly written data after calling save() method of ORM?
No, there is no SELECT ran after a INSERT or UPDATE statement in this case.
In my example, Is $another_item1->foo always 1?
Based on your own comment, Yes.
If the answer to question 1 is not, how could I ensure I read the newly written data from the database?
$model->save();
// Reload the current model instance with fresh attributes from the database.
$model->refresh();
// OR
// Reload a fresh model instance from the database.
$fresh = $model->fresh();
I think you may be confused about the find() function. find() is used to fetch one or many models by its / their primary key(s). The return value will either be a single model, a collection or null if the record is not found.
If you are looking to lookup multiple rows you need to run Item::get();
Uses
$Item = Item::find(1); // returns model or null
$Items = Item::find(array(1, 2, 3)); // returns selected Items in collection
$Items = Item::get(); // Returns all in collection
https://laravel.com/docs/5.5/eloquent
I assume that this should all be in one query in order to prevent duplicate data in the database. Is this correct?
How do I simplify this code into one Eloquent query?
$user = User::where( 'id', '=', $otherID )->first();
if( $user != null )
{
if( $user->requestReceived() )
accept_friend( $otherID );
else if( !$user->requestSent() )
{
$friend = new Friend;
$friend->user_1= $myID;
$friend->user_2 = $otherID;
$friend->accepted = 0;
$friend->save();
}
}
I assume that this should all be in one query in order to prevent
duplicate data in the database. Is this correct?
It's not correct. You prevent duplication by placing unique constraints on database level.
There's literally nothing you can do in php or any other language for that matter, that will prevent duplicates, if you don't have unique keys on your table(s). That's a simple fact, and if anyone tells you anything different - that person is blatantly wrong. I can explain why, but the explanation would be a lengthy one so I'll skip it.
Your code should be quite simple - just insert the data. Since it's not exactly clear how uniqueness is handled (it appears to be user_2, accepted, but there's an edge case), without a bit more data form you - it's not possible to suggest a complete solution.
You can always disregard what I wrote and try to go with suggested solutions, but they will fail miserably and you'll end up with duplicates.
I would say if there is a relationship between User and Friend you can simply employ Laravel's model relationship, such as:
$status = User::find($id)->friends()->updateOrCreate(['user_id' => $id], $attributes_to_update));
Thats what I would do to ensure that the new data is updated or a new one is created.
PS: I have used updateOrCreate() on Laravel 5.2.* only. And also it would be nice to actually do some check on user existence before updating else some errors might be thrown for null.
UPDATE
I'm not sure what to do. Could you explain a bit more what I should do? What about $attributes_to_update ?
Okay. Depending on what fields in the friends table marks the two friends, now using your example user_1 and user_2. By the example I gave, the $attributes_to_update would be (assuming otherID is the new friend's id):
$attributes_to_update = ['user_2' => otherID, 'accepted' => 0 ];
If your relationship between User and Friend is set properly, then the user_1 would already included in the insertion.
Furthermore,on this updateOrCreate function:
updateOrCreate($attributes_to_check, $attributes_to_update);
$attributes_to_check would mean those fields you want to check if they already exists before you create/update new one so if I want to ensure, the check is made when accepted is 0 then I can pass both say `['user_1' => 1, 'accepted' => 0]
Hope this is clearer now.
I'm assuming "friends" here represents a many-to-many relation between users. Apparently friend requests from one user (myID) to another (otherId).
You can represent that with Eloquent as:
class User extends Model
{
//...
public function friends()
{
return $this->belongsToMany(User::class, 'friends', 'myId', 'otherId')->withPivot('accepted');
}
}
That is, no need for Friend model.
Then, I think this is equivalent to what you want to accomplish (if not, please update with clarification):
$me = User::find($myId);
$me->friends()->syncWithoutDetaching([$otherId => ['accepted' => 0]]);
(accepted 0 or 1, according to your business logic).
This sync method prevents duplicate inserts, and updates or creates any row for the given pair of "myId - otherId". You can set any number of additional fields in the pivot table with this method.
However, I agree with #Mjh about setting unique constraints at database level as well.
For this kind of issue, First of all, you have to enjoy the code and database if you are working in laravel. For this first you create realtionship between both table friend and user in database as well as in Models . Also you have to use unique in database .
$data= array('accepted' => 0);
User::find($otherID)->friends()->updateOrCreate(['user_id', $otherID], $data));
This is query you can work with this . Also you can pass multiple condition here. Thanks
You can use firstOrCreate/ firstOrNew methods (https://laravel.com/docs/5.3/eloquent)
Example (from docs) :
// Retrieve the flight by the attributes, or create it if it doesn't exist...
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
// Retrieve the flight by the attributes, or instantiate a new instance...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
use `firstOrCreate' it will do same as you did manually.
Definition of FirstOrCreate copied from the Laravel Manual.
The firstOrCreate method will attempt to locate a database record using the given column / value pairs. If the model can not be found in the database, a record will be inserted with the given attributes.
So according to that you should try :
$user = User::where( 'id', '=', $otherID )->first();
$friend=Friend::firstOrCreate(['user_id' => $myId], ['user_2' => $otherId]);
It will check with both IDs if not exists then create record in friends table.
Let's say I make a call to my User and return the following:
$user = User::with('permissions')->find(1);
I expect to get a user with $user->permissions being the permission of the user.
Next, I create and assign some new permissions to this user by:
// Say Input::all() contains an array of new permissions I want to add
// I am using Underscore PHP here
$newPermissions = Arrays::each(Input::all(), function($permission) {
$new = new Permission($permission);
$user->associate($new);
return $new;
});
Now I want to update the $user and return them back:
// This does NOT work (it returns the original $user->permissions)
return $user->permissions = $newPermissions;
// But this DOES work
unset($user->permissions);
return $user->permissions = $newPermissions;
Is this a PHP thing or Laravel thing? And what can I do? (btw, even if I say $user->permissions ='anything, text, string, or object doesnt work!' nothing happens).
It looks like permissions is a relationship, not a simple value that you can assign values to. I'd recommend reading the documentation on relationships, eager loading, and on inserting related models.
That last one, in particular, is what you're trying to do. Instead of assigning a value to $user->permissions, you need to update the relationship with either the save, associate, or attach methods, depending on the type of relationship.
Okay so as far as i can understand using $this->id = id will set that your next request will get the row from the database with that id. (if you have followed the cake conventions).
Now i am trying to set a value and have therefore created the following function in my Product model
$this->id = $product_id;
$product = $this->find('first');
$final_amount = $product['Product']['antal'] - $amount;
$this->saveField('Product.antal',$final_amount);
However when i debug it then $product is not equal to the id i set. meaning it just took the first of the database table.
How come? and if this isnt the way to use $this->id what is?
After setting the id, you are supposed to use the read method. it will get the record you desired for.
You should call $this->ModelName->read();
Using the find have nothing to do with setting the id. it will simply find a record based on the options supplied.
Try this:
$this->findById('first', $product_id);
Creating or updating is controlled by the model’s id field. If
$Model->id is set, the record with this primary key is updated.
Otherwise a new record is created [...]
More http://book.cakephp.org/2.0/en/models/saving-your-data.html
Edit:
To update row try this;
$product = $this->findById('first', $product_id);
$final_amount = $product['Product']['antal'] - $amount;
$this->saveField('Product.antal',$final_amount);
find(), as documented, uses conditions array to retrieve records
$product = $this->Product->find('first', array('conditions' => array('id' => $id)));
See http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#find-first
Sidenote:
$this->id is currently mainly used for saving records, to be able to easily read the new created primary key value (id).
It is also used for saving via saveField().
In your case then (after using find() to get the product array):
$this->Product->id = $product['Product']['id'];
$this->Product->saveField('antal', $final_amount);
You shoud assign a value to $this->id when you want to update that row id with Model::save(), or want to read from that row with Model::read(). Setting $this->id prior to Model::find() has no effect on select queries.
if you will call Model::find('first') most probably it will find the first record by ordering records by column whose name is assigned to orderBy class attribute of model, by default perhaps its the id field and ordering must be ASC.
Ideal scenarioes for using $this->id = $intVar would be as under:
when calling
Model::save()//for updating records
Model::saveField();
Model::read();
Model::delete();//for deleting
My host object hasMany option objects associated with it. In the edit form, users can (de)select options and save that new set of associations. This is implemented using saveAll() on the posted data. The result is that
the host (main) object is updated,
option (associated) objects that are included both in the prior and the new association are updated, and
option objects that were not included in the prior association but are included in the new one are created.
But what does not happen is
that option objects that were included in the prior association but not in the new one are deleted.
Question: Can saveAll() do that as well, and how would the data structure have to look like to achieve this effect?
Related information:
My code to handle the edit form is actually more complex (hence I haven't quoted it here) but it results in the data structure as described in the book:
( [Host] => ( ... host object fields ... ),
[Option] => ( [0] => ( ... first option object fields ... ),
...
[n] => ( ... nth option object fields ... )
)
)
Now, if the original host had an associated option that is not included in the 0..n array then saveAll() won't detect this and won't delete that associated object.
Not sure if this is relevant but I am using CakePHP 1.3 .
Not really an elegant solution but works for me.
if ($this->Main->saveAll($this->data))
{
$this->Main->query(sprintf(
'DELETE '
. 'FROM extraneous '
. 'WHERE main_id = \'%s\' AND modified < (SELECT modified FROM main WHERE id = \'%1$s\')'
, mysql_real_escape_string($this->Main->id)
));
}
Note that your tables need to have a modified field.
You can ensure that everything gets executed atomically if you manually wrap everything into a transaction.
This can be done with the begin(), rollback() and commit() methods of the datasource:
$this->Main->begin();
if ( !$this->Main->save(...) ) {
$this->Main->rollback();
return false;
}
// Perform saves in related models...
if ( !$this->Main->MainRelatedModel->save(...) ) {
$this->Main->rollback();
return false;
}
// Perform deletes in extraneous records...
if ( !$this->Main->MainRelatedModel->delete(...) ) {
$this->Main->rollback();
return false;
}
// Everything went well, commit and close the transaction
$this->Main->commit();
The main disadvantage here is that transactions cannot be nested, hence you cannot use saveAll(). You have to save/delete everything step by step, instead of doing it in a single call.
saveAll() wont delete anything from your database.
I guess the best way is to delete options related to the current host before saving, and then adding them. If however, you need to update those that already exists (do you?) for some reason (like: options being related to some other models), I guess you can try to write a piece of code, that will delete unselected options.
Looking for this, I noticed there still isn't a solution built-in CakePHP. To achieve this, I added the following code to my model:
private $oldBarIds = array();
public function beforeSave($options = array() {
parent::beforeSave($options);
$this->oldBarIds = array();
if ($this->id && $this->exists() && isset($this->data['Bar'])) {
$oldBars = $this->Bar->find('all', array(
'fields' => array('id'),
'conditions' => array(
'Bar.foo_id' => $this->id
)
));
$this->oldBarIds = Hash::extract($oldBars, '{n}.id');
}
}
This checks if Bar exists in the saving data. If it does, it'll get the current id's of the current ones, setting them to $this->oldBarIds. Then when the save succeeds, it should delete the old ones:
public function afterSave($created, $options = array()) {
parent::afterSave($created, $options);
if (!$created && $this->oldBarIds) {
$this->Bar->deleteAll(array(
'Bar' => $this->oldBarIds
));
}
}
This way the deletion is handled by the model, and only occurs when the save succeeded. Should be able to add this to a behavior, might do this some day.
HABTM deletes all associated records then recreates what is needed. As PawelMysior suggests, you could achieve this with your hasMany by manually deleting the associated records immediately before the save. The danger, though, is that the save fails you lose the previous state.
I would go with a variant of GJ's solution and delete them after a successful save, but instead loop over an array of redundant IDs and use Cake's Model->del() method. This way you retain all the built-in error handling.