Laravel check if updateOrCreate performed an update - laravel

I have the following code in my controller:
for($i=0; $i<$number_of_tourists; $i++) {
$tourist = Tourist::updateOrCreate([
'doc_number' => $request['doc_number'][$I]
],
$tourist_to_update);
}
Each time updateOrCreate runs, it does 1 of 3 things:
Updates the model instance; OR
Creates and saves a new one; OR
Leaves everything unchanged (if model with such values already exists)
I need to check if updateOrCreate has done the first one (updated) and then execute some code.
How can I do it?

You can figure it out like this:
$tourist = Tourist::updateOrCreate([...]);
if(!$tourist->wasRecentlyCreated && $tourist->wasChanged()){
// updateOrCreate performed an update
}
if(!$tourist->wasRecentlyCreated && !$tourist->wasChanged()){
// updateOrCreate performed nothing, row did not change
}
if($tourist->wasRecentlyCreated){
// updateOrCreate performed create
}
Remarks
From Laravel 5.5 upwards you can check if updates have actually taken place with the wasChanged and isDirty method.
isDirty() is true if model attribute has been changed and not saved.
wasChanged() is true if model attribute has been changed and saved.
There is also a property (not method!) wasRecentlyCreated to check if user was created or not.
$user = factory(\App\User::class)->create();
$user->wasRecentlyCreated; // true
$user->wasChanged(); // false
$user->isDirty(); // false
$user = \App\User::find($user->id);
$user->wasRecentlyCreated; // false
$user->wasChanged(); // false
$user->isDirty(); // false
$user->firstname = 'Max';
$user->wasChanged(); // false
$user->isDirty(); // true
$user->save();
$user->wasChanged(); // true
$user->isDirty(); // false
//You can also check if a specific attribute was changed:
$user->wasChanged('firstname');
$user->isDirty('firstname');
You can checkout the link to the laravel's documentation for wasChanged and isDirty methods.
https://laravel.com/docs/8.x/eloquent#examining-attribute-changes or
https://laravel.com/docs/9.x/eloquent#examining-attribute-changes

It is pretty easy to determine if the function resulted in an update or an insert (check the wasRecentlyCreated property). However, when using that function, it is less easy to determine if the update actually happened (if the model exists but is not dirty, no update will be performed). I would suggest not using that function, and splitting out the functionality yourself.
This is the function definition:
public function updateOrCreate(array $attributes, array $values = [])
{
$instance = $this->firstOrNew($attributes);
$instance->fill($values)->save();
return $instance;
}
To integrate this into your code, I'd suggest something like:
for ($i=0; $i<$number_of_tourists; $i++) {
$tourist = Tourist::firstOrNew(['doc_number' => $request['doc_number'][$i]]);
$tourist->fill($tourist_to_update);
// if the record exists and the fill changed data, update will be performed
$updated = $tourist->exists && $tourist->isDirty();
// save the tourist (insert or update)
$tourist->save();
if ($updated) {
// extra code
}
}

Okay so I couldn't find a good answer for my scenario.
I was using: $this->created_at == $this->updated_at however I would sometimes update the record later in the request, which meant that 20% of the time the created_at and updated_at were about 1ms out.
To combat this I created something a little more relaxed which allows an extra second between creation and modification.
public function getRecentlyCreatedAttribute()
{
return $this->wasRecentlyCreated || $this->created_at == $this->updated_at || $this->created_at->diffInSeconds($this->updated_at) <= 1;
}
I can now call $this->recentlyCreated which will return true if there is a small difference in time (1 second).
Tbh this is the second time I've needed this in a project, I'm posting as I just ended up googling it and coming back to this thread looking for the same answer.
If someone has a more elegant solution, hmu.

#patricus below presented a working way to solve the problem.
though #TheFallen here gave a solution which uses Eloquent Events and seems more elegant:
Laravel Eloquent Events - implement to save model if Updated

The model attribute 'wasRecentlyCreated' would only be 'true' if it has just been created.
There is property named 'changes' in model (it is an array), that determines whether the model has been updated with new values or it has been saved as is without making any changes to its attribute.
Check the following code snippet:
// Case 1 : Model Created
if ($model->wasRecentlyCreated) {
} else { // Case 2 : Model Updated
if (count($model->changes)) { // model has been assigned new values to one of its attributes and saved successfully
} else { // model has NOT been assigned new values to one of its attributes and saved as is
}
}

Related

The Laravel $model->save() response?

If you are thinking this question is a beginner's question, maybe you are right. But really I was confused.
In my code, I want to know if saving a model is successful or not.
$model = Model::find(1);
$model->attr = $someVale;
$saveStatus = $model->save()
So, I think $saveStatus must show me if the saving is successful or not, But, now, the model is saved in the database while the $saveStatus value is NULL.
I am using Laravel 7;
save() will return a boolean, saved or not saved. So you can either do:
$model = new Model();
$model->attr = $value;
$saved = $model->save();
if(!$saved){
//Do something
}
Or directly save in the if:
if(!$model->save()){
//Do something
}
Please read those documentation from Laravel api section.
https://laravel.com/api/5.8/Illuminate/Database/Eloquent/Model.html#method_getChanges
From here you can get many option to know current object was modified or not.
Also you can check this,
Laravel Eloquent update just if changes have been made
For Create object,
those option can helpful,
You can check the public attribute $exists on your model
if ($model->exists) {
// Model exists in the database
}
You can check for the models id (since that's only available after the record is saved and the newly created id is returned)
if(!$model->id){
App::abort(500, 'Some Error');
}

Why Laravel accessor fires on insert?

I'm not sure how they work, I was thinking that accessor fires when you access the attribute, however when I try to insert new record my accessor fires. I store in my database only image_name by removing URL and I use an accessor to include my URL route when I retrieve my image name. I found out that my accessor fires on insert and change my input:
ImageRepository.php
public function save($imageData)
{
$this->model->owner()->associate(auth()->user());
$this->model->type = $imageData->type;
$this->model->image_name = $imageData->image_url;
$this->model->save();
return $this->model;
}
Image.php
public function getImageNameAttribute($value)
{
$filePath = 'my_path';
// check if method was hit
logger()->info('getImageNameAttribute: '.$value);
// return default image if not found
if ($value == '' || $value == null || Str::contains($value, 'no-image.png') || !File::exists(public_path($filePath))) {
return asset('img/no-image.png');
} else {
return asset($filePath);
}
}
Input for $imageData->image_url:
https://some-random-image-url/image-name.jpg
Inserted data:
img/no-image.png
Excepted output (I have a function that extracts the name from URL):
image-name.jpg
When I check the log, I get the message:
getImageNameAttribute: https://some-random-image-url/image-name.jpg
Can someone explain to me if I'm doing something wrong or this is working as pretended?
I did solve the problem, I miss checked. Actually inserted record in my database is correct:
image-name.jpg
But I was checking dd() output after the insert and the value from there was not correct:
img/no-image.png
Because when dd() fires, the accessor is also fired (as we are retrieving data) and as the image was not downloaded it was actually showing correct data.

How to toggle laravel trashed filter off temporarily?

Say I've got an A, which has a B that has a C that has a D. I want to go from A to D, but any one (or all) of the objects might have been deleted. So I have to do this:
$d = $a->b()->withTrashed()->first()->c()->withTrashed()->first()->d()->withTrashed()->first()
Which is horrible. I would really rather do this:
turnOffTrashedFilter();
$d = $a->b->c->d;
Does laravel have such an ability?
Note that this is just an example - the situation that prompted this question is actually a lot more complicated, with various calls nested in other calls such that it's not practically possible to use withTrashed as above. I need to turn off the filter for the duration of the request, without having to modify huge swathes of code to incorporate two parallel paths.
No built-in, but it can be done
There is no built in way to disable the automatic soft delete filtering. However, it is possible. The soft delete filter is a global scope, added to the boot method of the class. It can be removed like so:
\Event::listen('eloquent.booted:*', function($name) {
$name = substr($name, 17); // event name is "eloquent.booted: some/class"
$class = new \ReflectionClass($name);
$prop = $class->getProperty('globalScopes');
$prop->setAccessible(true);
$scopes = $prop->getValue();
foreach ($scopes as $c => &$s) {
unset($s['Illuminate\Database\Eloquent\SoftDeletingScope']);
}
$prop->setValue($scopes);
});
This hooks into the booted event, which is fired immediately after the global scope gets added to the class. It then opens the (private static) attribute globalScopes, which is a list of the attached global scopes, and removes the soft deleting one. This will prevent the softdelete scope from being attached to any models, provided their static boot method is called after the event listener is added.
Instead of this you can use withTrashed() in your relations:
public function aTrashed()
{
return $this->hasOne(A::class)->withTrashed();
}
public function bTrashed()
{
return $this->hasMany(B::class)->withTrashed();
}
public function cTrashed()
{
return $this->belongsToMany(C:class)->withTrashed();
}
// Then use it
$d = $z->aTrashed->bTrashed->cTrashed;

Unset session in SilverStripe

I am building a pretty simple online shop in SilverStripe. I am writing a function to remove an item from the cart (order in my case).
My setup:
My endpoint is returning JSON to the view for use in ajax.
public function remove() {
// Get existing order from SESSION
$sessionOrder = Session::get('order');
// Get the product id from POST
$productId = $_POST['product'];
// Remove the product from order object
unset($sessionOrder[$productId]);
// Set the order session value to the updated order
Session::set('order', $sessionOrder);
// Save the session (don't think this is needed, but thought I would try)
Session::save();
// Return object to view
return json_encode(Session::get('order'));
}
My issue:
When I post data to this route, the product gets removed but only temporarily, then next time remove is called, the previous item is back.
Example:
Order object:
{
product-1: {
name: 'Product One'
},
product-2: {
name: 'Product Two'
}
}
When I post to remove product-1 I get the following:
{
product-2: {
name: 'Product Two'
}
}
Which appears to have worked but then I try and remove product-2 with and get this:
{
product-1: {
name: 'Product One'
}
}
The SON OF A B is back! When I retrieve the entire cart, it still contains both.
How do I get the order to stick?
Your expectation is correct, and it should work with the code you wrote. However, the way the session data is managed doesn't work well with data being deleted, because it is not seen as a change of state. Only existing data being edited is seen as such. See Session::recursivelyApply() if you want to know more.
Only way I know is to (unfortunately) emphasized textmanipulate $_SESSION directly before you set the new value for 'order'
public function remove() {
// Get existing order from SESSION
$sessionOrder = Session::get('order');
// Get the product id from POST
$productId = $_POST['product'];
// Remove the product from order object
unset($sessionOrder[$productId]);
if (isset($_SESSION['order'])){
unset($_SESSION['order']);
}
// Set the order session value to the updated order
Session::set('order', $sessionOrder);
// Return object to view
return json_encode(Session::get('order'));
}

How to increment a column using Eloquent Model in Laravel 4

I am not sure how to increment the value in a column using Eloquent Model in Laravel 4?
This is what I currently have and I am not sure how correct is this.
$visitor = Visitor::where('token','=','sometoken')->first();
if(isset($visitor)){
$visitor->increment('totalvisits');
}else{
Visitor::create(array(
'token'=>'sometoken',
'totalvisits'=>0
));
}
With Query Builder we could do it using
DB::table('visitors')->increment('totalvisits');
Looks like the code that I posted worked after all
$visitor = Visitor::where('token','=','sometoken')->first();
if(isset($visitor)){
$visitor->increment('totalvisits');
}else{
Visitor::create(array(
'token'=>'sometoken',
'totalvisits'=>0
));
}
Prior to a fix a few weeks ago the increment method actually fell through to the query builder and would be called on the entire table, which was undesirable.
Now calling increment or decrement on a model instance will perform the operation only on that model instance.
Laravel 5 now has atomic increment:
public function increment($column, $amount = 1, array $extra = [])
{
if (! is_numeric($amount)) {
throw new InvalidArgumentException('Non-numeric value passed to increment method.');
}
$wrapped = $this->grammar->wrap($column);
$columns = array_merge([$column => $this->raw("$wrapped + $amount")], $extra);
return $this->update($columns);
}
which essentially works like:
Customer::query()
->where('id', $customer_id)
->update([
'loyalty_points' => DB::raw('loyalty_points + 1')
]);
Below is old answer for Laravel 4 where the built-in increment was a seperate select and then update which of course leads to bugs with multiple users:
If you'd like to accurately count your visitors by ensuring the update is atomic then try putting this in your Visitor model:
public function incrementTotalVisits(){
// increment regardless of the current value in this model.
$this->where('id', $this->id)->update(['totalVisits' => DB::raw('last_insert_id(totalVisits + 1)')]);
//update this model incase we would like to use it.
$this->totalVisits = DB::getPdo()->lastInsertId();
//remove from dirty list to prevent any saves overwriting the newer database value.
$this->syncOriginalAttribute('totalVisits');
//return it because why not
return $this->totalVisits;
}
I'm using it for a change tag system but might work for your needs too.
Does anyone know what to replace the "$this->where('id',$this->id)" with because since dealing with $this Visitor it should be redundant.

Resources