pivot "deleted" event when parent is deleted - php

I have 2 models: User and Role.
A user can have many roles.
A role can have many users.
I have a custom pivot model between these 2 models. This custom pivot model only exists because it uses a trait that listens/logs for events such as created, updated, deleted.
Let's say I have a role called moderator. When I attach() (or detach()) that role to 5 users, it does successfully fire 5 created (or deleted) events for the pivot table.
$roleModerator->users()->attach($anArrayOfFiveUsersHere);
So far so good.
My problem is the following: when I delete the moderator role itself, it does delete all pivot rows associated to the role, but it does not fire any deleted event for each deleted pivot rows.
Expected behavior: I want Laravel to fire deleted events for each rows it deletes in the pivot table when I ask it to delete the role.
Environment: PHP 7.3 / Laravel 6
One weird thing I noticed, if I add this to my Role model :
public static function boot()
{
parent::boot();
static::deleting(function (self $model)
{
//$model->users()->detach(); // <-- this fails firing deleted events.
//MyCustomPivot::query()->where('role_id', $model->id)->get()->each->delete(); // <-- this fails firing deleted events.
$model->users()->sync([]); // <--- this works!
});
}
sync([]) will work great and fire as many deleted events as it deletes pivot rows.
but detach(), although it accomplishes the same thing, won't fire any deleted event. Why is that? They are both from InteractisWithPivotTable.php and sync() does even call detach() itself!

Not 100% sure it's applicable to your situation, but according to this issue on Github, you need to do some setup in your models.
First, make sure you have a primary key column in your table and defined on your pivot model.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class MyCustomPivot extends Pivot
{
public $primaryKey = "id";
public $incrementing = true;
}
Second, make sure you include your custom pivot model in your relationships.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
public function users()
{
return $this->belongsToMany(User::class)
->using(MyCustomPivot::class)
->withPivot('id');
}
}
class User extends Model
{
public function roles()
{
return $this->belongsToMany(Role::class)
->using(MyCustomPivot::class)
->withPivot('id');
}
}

Related

call relation in Pivot model

I try load relations like:
return $task->load('users', 'creator', 'attachments', 'messages.creator');
Relation 'users' many to many with some fields
user
id
name
...
task
id
name
...
task_user
user_id
task_id
task_status_id
my pivot model
<?php
namespace App;
use Illuminate\Database\Eloquent\Relations\Pivot;
class TaskUser extends Pivot
{
public $incrementing = true;
public function status()
{
return $this->hasOne(TaskStatus::class);
}
}
How to correctly load 'status' relation?
If Laravel provided a way to pass Closures in pivots it wouldn't be hard at all however there are 2 solutions at the moment to load pivot relationships
1. Lazy eager load everything.
# App\Task.php
public function users()
{
return $this->belongsToMany(App\User::class)->using(App\TaskUser::class)->withPivot('task_status_id');
}
$task->load('users', ...);
$task->users->each(function ($user) {
$user->pivot->load('status');
});
The problem: This makes a query for each User related to Task. Unless you paginate your results, it's the classic N+1 problem.
2. Use hasMany and belongsTo instead.
Define $user->tasks_user relationship (User hasMany TaskUser)
Define $task->tasks_user relationship (Task hasMany TaskUser)
Define the inverse (TaskUser belongsTo User, TaskUser belongsTo Task)
Define the last task_user relationship (TaskUser hasOne TaskStatus) (looks like a belongsTo to me though)
Load relationships $task->load('task_user.user', 'task_user.status')
There's no real problem with this implementation besides not using belongsToMany.

Laravel 5.6 Flexible Relationship Spanning Differing Tables

I have a table which will be addressed by a model in Laravel for stock movements. I would like to establish a relates to field which can be related to a number of different tables.
As per the attached diagram I need the relates_to_id to be one of the following...
Purchases.purchase_id
Invoices.invoice_id
And there may be more being added in the future as we find more records which may result in a stock movement occurring.
Now I have added a relates_to_type field to the database schema for the stock movements table so that I can specify which record type the particular movement record relates to. But I have been struggling to figure out how to establish the model for such a relationship to work or if it will even work that way or if I will need to have a separate field for each type of relationship as I wish to be able to read the related record through the ORM's related record scheme.
This is the exact use case for polymorphic relations, they supply your model as you wished for with an column indicating the relation type and the id of the related model.
See also this example from the laravel documentation:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Image extends Model {
/**
* Get all of the owning imageable models.
*/
public function imageable() {
return $this->morphTo();
}
}
class Post extends Model {
/**
* Get the post's image.
*/
public function image(){
return $this->morphOne('App\Image', 'imageable');
}
}
class User extends Model {
/**
* Get the user's image.
*/
public function image()
{
return $this->morphOne('App\Image', 'imageable');
}
}
Here every user and post can have an image and every image has an imageable which is either a user or a post.

Laravel eloquent extending model

I have default user model. Now I have another table for Administrators. Is there a way to return user fields (since admin is extending user) from admin model ? I found this example but its when admin_id is present in user model. I have one-to-one relation here.
class Admin extends User
{
protected $table = 'users';
public static function boot()
{
parent::boot();
static::addGlobalScope(function ($query) {
$query->where('is_admin', true);
});
}
}
This is the example I found. I'm not sure how can I return user fields from my admin model when its on different table.
The point is I want to be able to do something like this (call methods from users):
Admin::first()->posts()
Where posts method is not on Admin class but on user class.
Edit:
To explain it better. I have two tables, users and admins. They are connected in one-to-one relationship so I can do something like this:
$admin = Admin::first();
$posts = $admin->user()->posts();
but since Admin should have all fields from users table and one more field from admins table I'm looking for a way to do this:
$admin = Admin::first();
$posts = $admin->posts();
I don't want to add admin or something to users table, I still want to use admins table since I will need more fields there later.
If both tables have an equal id, use a trait to define your relationships:
trait UserRelationships {
public function posts() {
return $this->hasMany(Post::class, 'user_id');
}
}
class Admin {
use UserRelationships;
}
class User {
use UserRelationships;
}
You'll just have to be sure to explicitly declare the foreign key name in the relationship.
You could also extend the User model and override the $table property but this may present problems for various reasons since User properties exist on the user relationship and not on the Admin model.

What is the difference between BelongsTo And HasOne in Laravel

Can any body tell me what is the main difference between
the BelongsTo and HasOne relationship in eloquent.
The main difference is which side of the relation holds relationship's foreign key. The model that calls $this->belongsTo() is the owned model in one-to-one and many-to-one relationships and holds the key of the owning model.
Example one-to-one relationship:
class User extends Model {
public function car() {
// user has at maximum one car,
// so $user->car will return a single model
return $this->hasOne('Car');
}
}
class Car extends Model {
public function owner() {
// cars table has owner_id field that stores id of related user model
return $this->belongsTo('User');
}
}
Example one-to-many relationship:
class User extends Model {
public function phoneNumbers() {
// user can have multiple phone numbers,
// so $user->phoneNumbers will return a collection of models
return $this->hasMany('PhoneNumber');
}
}
class PhoneNumber extends Model {
public function owner() {
// phone_numbers table has owner_id field that stores id of related user model
return $this->belongsTo('User');
}
}
BelongsTo is a inverse of HasOne.
We can define the inverse of a hasOne relationship using the belongsTo method.
Take simple example with User and Phone models.
I'm giving hasOne relation from User to Phone.
class User extends Model
{
/**
* Get the phone record associated with the user.
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
Using this relation, I'm able to get Phone model data using User model.
But it is not possible with Inverse process using HasOne. Like Access User model using Phone model.
If I want to access User model using Phone, then it is necessary to add BelongsTo in Phone model.
class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
You can refer this link for more detail.
One-to-one relationship: You, as a User, can have one (hasOne) Profile. And of course the inverse also applies. Profile (belongsTo) a User. A user can't have more than one profile and a profile can't belong to multiple users.
If you want to make One TO one relationship between two table then first you have to make "hasOne" Relation and If you want to make inversely table relationship then you make " "Belongs to"... IT is a simple difference between HasOne and Belongs to the relationship if you want to know about this
One To Many (Inverse)
Now that we can access all of a post's comments, let's define a relationship to allow a comment to access its parent post. To define the inverse of a hasMany relationship, define a relationship function on the child model which calls the belongsTo method:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
Here you can see a good example and see what the difference is between BelongsTo and HasOne relationship in eloquent.
Eloquent Relationships Cheat Sheet by Mahmoud Zalt https://link.medium.com/9lj9BAG8lR

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.

Categories