Laravel attach a callable to onDelete to eloquent model - php

I have a model that needs to delete images when the model is deleted. The model I will be calling is Seminar.php. This "Seminar" model has got instructors in the model called SeminarInstructor.php. Suppose I called Seminar::find(1)->delete(); to delete the seminar with id = 1-- I would also need this delete off any instructors connected to the model as below:
public function seminarInstructor()
{
return $this->hasMany(SeminarInstructor::class, 'seminar_id', 'id');
}
I have enabled cascading, so when I call delete, laravel/mysql should automatically resolve it. However, I have a method called public function deleteCloudImage1() in SeminarInstructor.php which handles the deletion logic for the image existing in my table as a url. I could manually call this method before I delete a seminar model, but I want to know if there is any convenient method to handle this in eloquent/laravel. I looked up the code a bit and there seems to be a callable attribute called onDelete in the implementation. Anyone know how to use this, or any other alternative.

Related

How to get foreign key's method used in Laravel Eloquent Relationships?

I have a problem with how to get the method name that was used in other models.
There is a list of models and some of them have used different method name on its relationship to other models.
For example, I have a model name of Member Rate Detail wherein it belongs to Member Rate model. The method that connects from Member Rate Detail to Member Rate is head() method.
Here is the sample code for head() method:
public function head()
{
return $this->belongsTo(MemberRate::class, 'member_rate_head_id')->withTrashed();
}
And for Customer Detail model it belongs to Customer model. And the connector method name that was used is group()
Here is the sample code for group():
public function group()
{
return $this->belongsTo(Customer::class, 'head_id', 'id')->withTrashed();
}
So the problem is I don't know if this model is using head() or group() or another method name.
Is there a Laravel Relationship concept way which can get a list or an array type of its foreign key's method used?
I'm expected to get the method name so that I can direct it to its instance class.
For example:
$memberRateDetail->getForeignMethod()->created_by;
**OR**
$customerDetail->getForeignMethod()->created_by;
Thank you so much!!!
No, there are not unless you declare them yourself.
Let's say you declare a method in your all your models where you declare the method for the class foreign relation. You can also implement it as an interface.
You can also just use code hinting if you have a good IDE, declaring a relation in PHP docs as * #property Collection head can help too.

Laravel 5.8 syncing / attaching / detaching events

Laravel 5.8 is supposed to dispatch the syncing, attaching and detaching events (https://laravel.com/docs/5.8/releases search for Intermediate Table / Pivot Model Events section).
UPDATE: the release notes have been update after posting this question (more info: https://github.com/laravel/framework/issues/28050 - https://github.com/laravel/docs/pull/5096).
I tried it out but the following code throws the exception:
Call to undefined method App\ProjectUser::syncing()
NOTE: since Laravel 5.8 is supposed to dispatch the syncing event I don't want to use an external package.
class User extends Model
{
public function projects()
{
return $this->belongsToMany(\App\Project::class)->using(\App\ProjectUser::class);
}
}
class Project extends Model
{
public function users()
{
return $this->belongsToMany(\App\User::class)->using(\App\ProjectUser::class);
}
}
class ProjectUser extends Pivot
{
public static function boot()
{
parent::boot();
static::syncing(function ($item) {
dd('syncing event has been fired!');
});
}
}
// web.php
$project = \App\Project::first();
$project->users()->sync([1,2]);
I tried to move the boot method from ProjectUser to User and Project but I get the same exception.
On Laravel 5.8, when you are using the methods sync, attach or detach is going to be fired the appropriate model events (creating, updating, saving, ...) for the called action. Note that using sync, attach or detach is not going to fire any event like syncing, attaching or detaching.
More specifically, the sequence of events fired for each element passed to the sync method are:
saving
creating
created
saved
The sequence of events fired for each element passed to the attach method are:
saving
creating
created
saved
The sequence of events fired for each element passed to the detach method are:
deleting
deleted
So if you want to observe the syncing operation you actually have to observe the saving (or saved) event from the pivot model (in this case ProjectUser):
class ProjectUser extends Pivot
{
public static function boot()
{
parent::boot();
static::saving(function ($item) {
// this will die and dump on the first element passed to ->sync()
dd($item);
});
}
}
A working example https://github.com/danielefavi/laravel-issue-example
More info on this issue https://github.com/laravel/framework/issues/28050
The release notes were misleading and they have been changed https://github.com/laravel/docs/pull/5096.
If detach method called without ids (for detach all relations), events are not firing
https://github.com/laravel/framework/pull/27571#issuecomment-493451259
i tried many different way for the solve this need, but it is impossible without use external package or override many method.
I choose chelout/laravel-relationship-events package.
It's look clean and understable. And use with trait.

Laravel 5.1 Model Delete Event

I have a load of Models. Users can comment on some of the model records. For example, a user can comment on a Notice model record, or a user can comment on a Calendar Model record.
I've created a trait called Commentable. This contains all the methods needed to retrieve comments based on a model, to add / delete comments, create the comment create form and so on.
Whenever I want a model to be commentable, all I need to do is use that trait within the model.
Because that comment is a polymorphic relationship with the model record, I can't do an onCascade = delete migration.
Whenever the parent model is deleted (such as a notice or calendar item), then I want all associated records to be deleted as well, but I'd prefer not to have to rely on the developer writing the deleteRelatedComments() method call into an overridden delete function.
I thought I would create a service provider that listened for any model deletion event, check to see if that model was commentable, and delete any associated comments, but the event doesn't fire correctly.
This is my service provider code:
Model::deleting(function (Model $record) {
if(in_array('App\Libraries\Traits\Commentable', class_uses($record))) {
$record->deleteRelatedComments();
}
return true;
});
As you can see, all it does is check the deleted model to see if it uses the Commentable trait. If it does, it calls the deleteRelatedComments() method.
My Question
Is there any way to automatically delete related polymorphic related content on the deletion of it's parent record:
If I delete a Notice record, is there any way to delete any associated polymorphic Comment records?
I have previously created a service provider to listen for any Model deletes using Model Events as specified at http://laravel.com/docs/5.1/eloquent#events
However, this doesn't work if you're trying to listen to the Model class instead of a specific class such as Notice.
Instead, when you delete each model, a eloquent.deleting event is fired. I can then manually create a listener within the EventServiceProvider to listen for any model being deleted, and perform my checks accordingly, but without having the rely on the user overloading the delete() method and manually deleting the polymorphic relations.
Answer
If you want to delete polymorphic content automatically when you delete a model, make sure you either implement an interface or use a trait in the model.
For example, I want to delete all polymorphic comments when I delete the parent model record (If I delete a Notice record delete all polymorphic comments).
I created a Commentable trait which I use in any model (but you can just as easily use an empty interface on your model).
Then, in your EventServiceProvider.php alter the boot() method accordingly:
public function boot(DispatcherContract $events)
{
parent::boot($events);
/**
* If you use a trait in your model:
*/
$events->listen('eloquent.deleting*', function ($record) {
if (in_array('App\Libraries\Traits\Commentable', class_uses($record))) {
$record->deleteRelatedComments();
}
});
/**
* Or if you use an interface:
*/
$events->listen('eloquent.deleting*', function ($record) {
if ($record instanceof SomeInterface) {
$record->deleteSomePolymorphicRelation();
{
});
}
Use case:
Commentable Trait
namespace App\Libraries\Traits;
trait Commentable
{
public function comments()
{
return $this->morphMany(Comment::class, 'content');
}
public function deleteRelatedComments()
{
$this->comments()->delete();
}
}
Commentable Model
class Notice extends Model
{
use Commentable;
/** ... **/
/**
* NOTE: You do not need to overload the delete() method
*/
}
EventServiceProvider.php
public function boot(DispatcherContract $events)
{
parent::boot($events);
$events->listen('eloquent.deleting*', function ($record) {
if (in_array('App\Libraries\Traits\Commentable', class_uses($record))) {
$record->deleteRelatedComments();
}
});
}
Now, the developer creating a new content type that wants it to be commentable doesn't need to worry about creating the relations, managing the relation methods, or even deleting the polymorphic contents by overloading delete(). To make a model entirely commentable, they just need to use the Commentable trait and the framework handles everything.
This method can be used to automatically delete any polymorphic record, as long as the parent model implements some interface or uses a related trait.

Can Eloquent model observer access model data that are being affected by the event?

I am trying to create an observer for an Eloquent model to work as a logger for the changes on that model. I am not sure what parameters are passed to the observer's methods from the model. If the parameters don't include an instance of the updated model, is there another way around it without having to redefine the model class and override the needed methods?
class UserObserver{
public static function saved($user){
Activity::create([
"activity-name" => "save",
"user" => $user->id
]);
}
}
I found out that the model is actually passed, my mistake was not adding user property to the fillable array in the Activity model.
usually, I get an exception when my application tries to update fields that are not included in the fillable array, but this time I didn't. anybody knows why?

Laravel modify Auth::user() query?

I want to add some joins onto my Auth::user() query. How do I do this without creating a brand new query? I just want to be able to make the default call of Auth::user() different than:
SELECT * FROM `users` WHERE `id` = ?
to
SELECT * FROM users INNER JOIN user_icons ON user_icons.ID = users.iconid WHERE `id` = ?
I'm using the default model User class.
Laravel provides a way for you to extend the Auth functionality. First, you need to create a class that implements the Illuminate\Auth\UserProviderInterface. Once you have your class, you call Auth::extend() to configure Auth with your new class.
For your case, the easiest thing for you to do would be to create a class that extends Illuminate\Auth\EloquentUserProvider. You'll want to update the retrieveBy* methods to add in your custom joins. For example:
class MyEloquentUserProvider extends Illuminate\Auth\EloquentUserProvider {
public function retrieveById($identifier) {
return $this->createModel()->newQuery()->join(/*join params here*/)->find($identifier);
}
public function retrieveByToken($identifier, $token) {
// your code with join added here
}
public function retrieveByCredentials(array $credentials)
// your code with join added here
}
}
Once your class is fleshed out, you need to tell Auth to use it:
Auth::extend('eloquent', function($app) {
return new MyEloquentUserProvider($app['hash'], $app['config']['auth.model']);
});
The first parameter to the Auth::extend method is the name of the auth driver being used as defined in app/config/auth.php. If you want, you can create a new driver (e.g. 'myeloquent'), but you'd need to update your Auth::extend statement and your app/config/auth.php driver.
Once all this is done, Auth::user() will end up calling your MyEloquentUserProvider::retrieveById method.
Fair warning: I have not actually done this myself, and none of this is personally tested. You will probably want to check out the documentation (L4.1 docs, L4.2 docs) and look at the Laravel code.
Other notes:
People have already chimed in that this is probably not what you want to do. However, the this information may be helpful to you and others looking to extend Auth for some other reason.
Considering your inner join, if a user does not have an associated user_icons record, Auth::user() will not return a record anymore, and the user probably won't be able to log in at all.
If you have 1:n relation:
Add a "icons" table to you database with a foreign key "user_id".
Add a "Icon" Model to your models.
<?php
class Icon extends Eloquent{
...
}
?>
In Model Class "User" add a function:
public function icons() {
return $this->hasMany('Icon');
}
Now you can do this:
$userIcons = Auth::user()->icons();

Categories