Eloquent model event `updating` isn't firing in laravel 7 - laravel

I'm trying to catch the model event updating in laravel 7 but it doesn't fire.
This is the place where the model get's changed:
public function update(Request $request, Model $model) {
$model->update($request->input());
return new Resource($model);
}
I also tried this to update the values:
public function update(Request $request, Model $model) {
$model->attribute1 = $request->get('value1');
$model->attribute2 = $request->get('value2');
$model->attribute3 = $request->get('value3');
$model->save();
return new Resource($model);
}
And here I'm trying to catch the event within the bill model:
protected static function boot() {
static::updating(function ($model) {
// code
});
}
What am I doing wrong?

You have to call parent::boot() at the start of your boot method:
protected static function boot() {
parent::boot();
static::updating(function ($model) {
// code
});
}
Laravel 7 added a booted method to make it easier:
Adding booting / booted methods to Model
Currently, users who extend the boot method to add event listeners on
model events must remember to call parent::boot() at the start of
their method (or after). This is often forgotten and causes confusion
for the user. By adding these simple place-holder extension points we
can point users towards these methods instead which do not require
them to call any parent methods at all.
From the docs:
Instead of using custom event classes, you may register Closures that
execute when various model events are fired. Typically, you should
register these Closures in the booted method of your model:
<?php
namespace App;
use App\Scopes\AgeScope;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::created(function ($user) {
//
});
}
}

Related

How to detect update event in model in Laravel 8

Good day to all
The situation is as follows
In the controller, in the update method, I try to update the object
There is an image in the fields of this object
Wrote a trait to process this field and load an image
In the model itself, I called the update method, which just determines the event of updating the object
The problem lies in the following image in the specified directory is loaded and the entry itself in the database does not change
Here is my code
Controller
Model
Trait
There is extra code in the model
public function update(Request $request, MainHeader $mainHeader): RedirectResponse
{
$mainHeader->update([
'language_id' => $request->language_id,
'brandLogoImage' => $request->file('brandLogoImage'),
'homeTitle' => $request->homeTitle,
'ourProjectsTitle' => $request->ourProjectsTitle,
'contactTitle' => $request->contactTitle,
'feedbackTitle' => $request->feedbackTitle,
]);
return redirect()->route('admin.header.index')->with('success', 'Данные успешно обновлены');
}
public function setBrandLogoImageAttribute($value): string
{
return $this->uploadImage('brandLogoImage', $value);
}
public function update(array $attributes = [], array $options = [])
{
$this->uploadImage('brandLogoImage', $attributes['brandLogoImage']);
$this->setBrandLogoImageAttribute($attributes['brandLogoImage']);
return parent::update($attributes, $options); // TODO: Change the autogenerated stub
}
protected function uploadImage(string $attr, $value): string
{
$uploadDir = public_path('uploads/');
$imageDir = public_path('uploads/image/');
if (!file_exists($uploadDir)){
mkdir($uploadDir);
}
if (!file_exists($imageDir)){
mkdir($imageDir);
}
if (!file_exists(public_path("uploads/image/$this->table/"))){
mkdir(public_path("uploads/image/$this->table/"));
}
$imageName = Str::random(12) . '.png';
Image::make($value)->save(public_path("uploads/image/$this->table/$imageName") , 100);
return $this->attributes[$attr] = (string) "uploads/image/$this->table/$imageName";
}
if you call the update methode in your model then you are overriding the default update() of the model class , its not listening to the event it simply runs your code before parent:: , so you need to make sure that the changes you are making does not get overwitten by the parent call .
regarding your question on how to detect update , if you want to perform anything before update than i advise you to use eloquent events or use observers , Observers listen to various events regarding your model like updating or updated .. but i think if its only for updating event than you should use event using closure
for example :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::updating(function ($user) {
// do what you want
});
}
}
If your pupose

Laravel, having a custom controller method with custom policy method?

I have a resource controller and want to add an extra custom policy method for destroyMany
In which I would check if the user is admin before deleting many.
The default methods work fine
Controller Method
Policy Method
index
viewAny
show
view
create
create
store
create
edit
update
update
update
destroy
delete
destroyMany
destroyMany
Controller destroyMany method is called, the policy isn't
Or should I stick to Gates for this extra method?
The docs say I can have any name for the methods and policies, How can both be linked?
destroyMany->destroyMany or
destroyMany->deleteMany would be a good setup.
And would be a great addition to my resource controller (where it should reside)
class ResourceController extends Controller
{
public function __construct()
{
$this->middleware('auth:api');
$this->authorizeResource(Resource::class, 'resource');
}
public function index()
{
return ResourceCollection::collection(Resource::all());
}
public function destroyMany(Request $request)
{
// gets called but needs a policy which isn't called
}
}
policy
class ResourcePolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
//
}
public function viewAny(User $user)
{
// works
return $user->hasAnyRoles(['admin', 'superAdmin']);
}
public function delete(User $user, Resource $resource)
{
// works
return $user->hasAnyRoles(['admin', 'superAdmin']);
}
public function deleteMany(User $user, Resource $resource)
{
// not called because the controller method needs to be hooked up, like the other methods
}
}
To get the addition policy method to work you will need to update the resourceAbilityMap for the controller. Adding the following to your controller should do the trick:
protected function resourceAbilityMap()
{
return array_merge(parent::resourceAbilityMap(), [
'destroyMany' => 'deleteMany'
]);
}
Also, if you don't return anything from your deleteMany policy method it will result in a 403.
If you're route/controller method isn't receiving an instance of the model then you will also need to update the array returned from the resourceMethodsWithoutModels method:
protected function resourceMethodsWithoutModels()
{
return array_merge(parent::resourceMethodsWithoutModels(), ['destroyMany']);
}

Why is the Eloquent model "booted" method not firing on Laravel 7.x?

I have this simple code on an Eloquent model to fire during the model creating event to auto-create a slug for the model.
use Illuminate\Support\Str;
...
protected static function booted()
{
static::creating(function (self $model) {
if (!$model->slug) {
$model->slug = Str::slug($model->title);
}
});
}
It's not firing when it's up on our staging site, and I have literally no idea why. It works fine locally. I'm just getting General error: 1364 Field 'slug' doesn't have a default value... when trying to create a new model.
When I change it to this, it works as expected:
use Illuminate\Support\Str;
...
protected static function boot()
{
parent::boot();
static::creating(function (self $model) {
if (!$model->slug) {
$model->slug = Str::slug($model->title);
}
});
}
However, this is a Laravel 7.x install so surely the booted method should work?
Does anyone know of a reason why booted would not be firing while boot works?

laravel nova observer trying to get property on non object

I set a database connection by the auth()->user()-dbname
This works as desired using this in the model
public function __construct() {
$this->connection = auth()->user()->dbname;
}
Now I want to observe the model on creation, update, etc.
I tried to use
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$itemIds = $model->item_ids;
... update another model based on the $itemIds
});
But Nova is not recognizing the static::creating function
So I created an Observer (I think a better choice) however when the observer is called it does not recognize the
auth()->user()->dbname property
Why doesn't the observer recognize auth?
This may be caused because there is no authenticated user. Try dumping and see what it throws you.
public function __construct() {
// Should throw an User model OR null.
dd(auth()->user());
// Alternatively, you could use the Logger
\Log::info(json_encode(auth()->user()));
$this->connection = auth()->user()->dbname;
}
If auth()->user() is null, then no user is logged in and as you might have guessed, null is a non-object.
Thanks for the suggestions but none would work for me. I gave up on Observers in Nova. I used the boot() function. This is how I setup milti tenant.
In the _constructor I added this
public function __construct() {
parent::__construct(); // needed before boot would fire
$this->connection = auth()->user()->dbname;
}
Then my boot() function became the observer
protected static function boot()
{
parent::boot();
static::creating(function($item) {
$item->event_id = Event::currentEventID();
});
}

How to always use withTrashed() for model Binding

In my app, I use soft delete on a lot of object, but I still want to access them in my app, just showing a special message that this item has been deleted and give the opportunity to restore it.
Currently I have to do this for all my route parametters in my RouteServiceProvider:
/**
* Define your route model bindings, pattern filters, etc.
*
* #return void
*/
public function boot()
{
parent::boot();
Route::bind('user', function ($value) {
return User::withTrashed()->find($value);
});
Route::bind('post', function ($value) {
return Post::withTrashed()->find($value);
});
[...]
}
Is there a quicker and better way to add the trashed Object to the model binding ?
Jerodev's answer didn't work for me. The SoftDeletingScope continued to filter out the deleted items. So I just overrode that scope and the SoftDeletes trait:
SoftDeletingWithDeletesScope.php:
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class SoftDeletingWithDeletesScope extends SoftDeletingScope
{
public function apply(Builder $builder, Model $model)
{
}
}
SoftDeletesWithDeleted.php:
namespace App\Models\Traits;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Scopes\SoftDeletingWithDeletesScope;
trait SoftDeletesWithDeleted
{
use SoftDeletes;
public static function bootSoftDeletes()
{
static::addGlobalScope(new SoftDeletingWithDeletesScope);
}
}
This effectively just removes the filter while still allowing me to use all the rest of the SoftDeletingScope extensions.
Then in my model I replaced the SoftDeletes trait with my new SoftDeletesWithDeleted trait:
use App\Models\Traits\SoftDeletesWithDeleted;
class MyModel extends Model
{
use SoftDeletesWithDeleted;
For Laravel 5.6 to 7
You can follow this doc https://laravel.com/docs/5.6/scout#soft-deleting. And set the soft_delete option of the config/scout.php configuration file to true.
'soft_delete' => true,
For Laravel 8+
You can follow this doc https://laravel.com/docs/8.x/routing#implicit-soft-deleted-models. And append ->withTrashed() to the route that should accept trashed models:
Ex:
Route::get('/users/{user}', function (User $user) {
return $user->email;
})->withTrashed();
You can add a Global Scope to the models that have to be visible even when trashed.
For example:
class WithTrashedScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$builder->withTrashed();
}
}
class User extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new WithTrashedScope);
}
}
Update:
If you don't want to show the deleted objects you can still manually add ->whereNull('deleted_at') to your query.

Resources