Best Practice to update hasMany Relationships with laravel - php

Hello Actually am trying to create an app in which every project has some users i.e ProjectMembers.
here is project model with TeamMembers function.
class Project extends Model
{
use HasFactory;
function TeamMembers(){
return $this->hasMany(ProjectMember::class);
}
}
project members table schema.
Schema::create('projects_members', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('project_id')->nullable();
$table->unsignedBigInteger('user_id')->nullable();
$table->foreign('project_id')->references('id')->on('projects');
$table->foreign('user_id')->references('id')->on('users');
$table->timestamps();
});
Now for updating project members i have to first delete relationships from Project members and then saving new one. because i have added multiselect dropdown. $request->team_members have type array.
public function update(Request $request, $id)
{
// return $request;
$project = Project::findorfail($id);
$project->name = $request->project_name;
$project->details = $request->details;
$project->start_date = $request->start_date;
$project->end_date = $request->end_date;
$members = $request->team_members;
ProjectMember::where('project_id', $id)->delete();
$this->update_project_memebers($members, $project);
return redirect('/projects');
}
public function update_project_memebers($members, $project){
foreach ($members as $member_id) {
$project_member = new ProjectMember();
$project_member->project_id = $project->id;
$project_member->user_id = $member_id;
$project_member->save();
}
}
here am deleting cuz if someone created project with two members and when the he/she want to update then he/she can remove one member from multiselect then i have to delete relationship cuz he/she selected only one user.
I don't think it's a good practice, so can i achieve this same func. with another way?
thankyou.

First of all please make a proper naming convention of pivot tabel according to laravel and same for the model name too.
As of now the solution is
define relation as
Project
public function teamMembers()
{
return $this->belongsToMany(ProjectMember::class, 'projects_members', 'project_id', 'member_id');
}
ProjectMember
public function projects()
{
return $this->belongsToMany(Project::class, 'projects_members', 'member_id', 'project_id');
}
So now instead of deleting use sync() method. AS
$project->teamMembers()->sync($members);
Important docs,
https://laravel.com/docs/8.x/eloquent-relationships#syncing-associations

Related

How to call a relationship who contains another relationship - Laravel

I'm trying to make a site where users have projects, and I'm trying to make a kind of "activity wall" to improve the communication of each project, so this "activity wall" messages belong to the project and the Messages inserted in it belong to the user too. So, I created the migration below:
Schema::create('messages', function (Blueprint $table) {
$table->id();
$table->string('content'); //the message
$table->string('file'); //if has a file
$table->integer('user_id')->constrained(); //to make the relationship with the Users table
$table->integer('projeto_id')->constrained();//to make the relationship with the Projetos (Projectc) table
$table->timestamps();
});
And in Projeto Model, I create the relation.
Projeto.php
public function message()
{
return $this->hasMany(Message::class);
}
The same to
User.php
public function messages()
{
return $this->hasMany(Message::class);
}
And this to
Message.php
public function projetos()
{
return $this->belongsTo(Projeto::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
But the problem is: When I try to get the Messages table with the Project relationship.
public function index($id)
{
$projeto = Projeto::findOrFail($id);
$messages = $projeto->message;
}
I can't get the User relationship to get the owner username of the message to return to my view. What is the better way to do this?
you should use with, to prevent n query:
public function index($id)
{
$projeto = Projeto::with('message', 'message.user') // this will get the relation `message`, and `user` from the message
->findOrFail($id);
$messages = $projeto->message;
}

Associating multiple single instances of the same model in Laravel

I am working on a project in which there are events, which each relate to two single forms on two separate relations – booking and survey. These forms are identically constructed, making it seem unnecessary to use two entirely distinct form models – I instead wanted to use a polymorphic relation, but it appears that isn't possible.
What is the appropriate way to structure this relationship?
Events have one or no booking form
Events have one or no survey form
Forms are a separate, single table
What I have tried:
Polymorphic relationship: Not compatible with two relations to the same model.
Has one relationship: This used a booking_id and survey_id but refused to set either of these fields.
Has many relationship with a type field: Made it difficult to easily save the forms, as it wasn't possible to save to the single relationship. There was also no restriction on the number of forms.
class Event extends Model
{
public function booking()
{
return $this->hasOne(Form::class, 'id', 'booking_form_id');
}
public function survey()
{
return $this->hasOne(Form::class, 'id', 'survey_form_id');
}
}
...
class Form extends Model
{
public function event()
{
return $this->belongsTo(Event::class);
}
}
...
$event = new Event;
$event->name = 'Event';
$event->save();
$booking = new Form;
$booking->name = 'booking';
$event->booking()->save($booking);
$survey = new Form;
$survey->name = 'survey';
$event->survey()->save($survey);
...
Schema::create('events', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->unsignedInteger('booking_form_id')->nullable()->index();
$table->unsignedInteger('survey_form_id')->nullable()->index();
$table->timestamps();
});
Schema::create('forms', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
What would be preferable:
Using a polymorphic relationship which would allow forms to be used in other parts of the application.
Using multiple hasOne relationships to limit the number of forms to one for each type.
I think you got your param order wrong. It's hasOne($related, $foreignKey, $localKey)
class Event extends Model
{
/* if you haven't changed the default primary keys, $localKey should be equal to 'id' */
public function booking()
{
return $this->belongsTo(Form::class, 'booking_form_id');
}
public function survey()
{
return $this->belongsTo(Form::class, 'survey_form_id');
}
}
class Form extends Model
{
public function booking_event()
{
return $this->hasOne(Event::class, 'booking_form_id');
}
public function survey_event()
{
return $this->hasOne(Event::class, 'survey_form_id');
}
}
Now there's 2 ways you can go about this.
If a Form can belong to both kind of events, you need to return a collection when accessing $form->event.
If a Form can belong to only one kind of event, you need to guess which kind and return the model when accessing $form->event.
# Form model
# 1. can be achieved using an accessor. Cannot be eager loaded but can be appended with the $appends Model property
public function getEventsAttribute()
{
return collect([$this->booking_event, $this->survey_event]);
}
# Form model
# 2. can be achieved using a relationship that guesses which relation it should return. Since it returns a relationship, it can be eager loaded.
public function event()
{
return ($this->booking_event()->count() != 0) ? $this->booking_event() : $this->survey_event();
}

Laravel 5.2 - eloquent relations

I have two tables one is templates and the other is template_images
Showing migration of the two tables.
Templates
Schema::create('templates', function (Blueprint $table) {
$table->increments('id');
$table->string('title', 155);
.....
});
Template Images
Schema::create('template_images', function (Blueprint $table) {
$table->increments('id');
$table->integer('template_id')->unsigned();
$table->boolean('master');
$table->binary('image');
$table->foreign('template_id')->references('id')->on('templates')->onDelete('cascade');
...
});
I have made relations in the both Models like this
Template-Model
class Template extends Model
{
public function tempImage()
{
return $this->hasMany('App\Models\TemplateImage', 'template_id');
}
}
Template Image-Model
class TemplateImage extends Model
{
public function tempImage ()
{
return $this->belongsTo('App\Models\Template', 'template_id');
}
}
What I want is
Select all from templates and select from template_images where template_images.template_id = templates.id and template_images.master = 1
Update
This is how I need to get the image in blade
$temp->tempImage->image
What I try in my controller
$temps = Template::query()->with('tempImage')->get();
but still cant get it the right way.
<?php
class Template extends Model
{
public function tempImages()
{
return $this->hasMany('App\Models\TemplateImage', 'template_id');
}
public function tempMasterImage(){
return $this->hasOne('App\Models\TemplateImage', 'template_id')->where('master', 1);
}
}
If every template can only have 1 master template image then it's better to create a separate relation in model for that. See the example model Class above. tempMasterImgage() is the new relation and it has hasOne relation with TemplateImage. With this in place you can simply do.
$template->tempMasterImage->image
from anywhere, You just need access to Template object.
Edit
It's better to name your hasMany relationship function as plural. That's why I used tempImages in my example
Example Usage.
$template = Template::with(['tempImages', 'tempMasterImage'])->first();
//if you want tempImages to exclude masterImage as we have now seperate relation for that then
$template = Template::with(['tempImages' => function($query){
$query->where('master', '<>', 1);
}])->with('tempMasterImage')->first();
//get master image
$template->tempMasterImage->image
//get all images
foreach($template->tempImages as $tempImage){
echo $tempImage->image //
}
If your $temp variable is an instance of model Template, you can not do $temp->tempImage->image becouse tempImage is a hasMany relationship.
So you can do two things, first:
$temps = Template::with(['tempImage' => function($query){
$query->where('master',1);
}])->get();
And then in your view
foreach ($temps as $temp)
{
foreach ($temp->tempImage as $temp_image)
{
// Here your code for $temp_image->image
}
}
Or second
$temps = TemplateImage::where('master',1)->with('tempImage')->get();
And then in your view
foreach ($temps as $temp)
{
// Here your code for $temp->image
}

Laravel 5.1 Multiple Many to Many Relationship on the Same Model

I seen to of got tangled in Laravel's ORM with the following:
Scenerio: All Users have a Watchlist, the Watchlist contains other Users.
I can't seem the get the relationships to work correctly as they are cyclical, so far I have the following:
class UserWatchlist extends Model
{
protected $table = 'UserWatchlist';
public function Owner() {
return $this->belongsTo('App\User');
}
public function WatchedUsers() {
return $this->hasMany('App\User');
}
}
Schema::create('UserWatchlist', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('Users')->onDelete('cascade');
$table->integer('watched_id')->unsigned();
$table->foreign('watched_id')->references('id')->on('Users')->onDelete('cascade');
$table->timestamps();
});
class User extends Model
{
public function Watchlist() {
return $this->hasOne('App\UserWatchlist');
}
public function WatchedBy() {
return $this->belongsToMany('App\UserWatchlist');
}
}
It is not pulling through the correct in formation i'm expecting. Am I missing something fundamental?
Since UserWatchlist is a pivot table, i suppose you are facing a many to many relationship with both the elements of the relation being the same model (User)
If that is the case, you should not build a model for the pivot table UserWatchlist but all you have to do is to set the relation between the users through the pivot table:
class User extends Model
{
//get all the Users this user is watching
public function Watchlist()
{
return $this->belongsToMany('User', 'UserWatchlist', 'user_id', 'watched_id' );
}
//get all the Users this user is watched by
public function WatchedBy()
{
return $this->belongsToMany('User', 'UserWatchlist', 'watched_id', 'user_id' );
}
}
Check here for more info on many-to-many relationship

Automatically deleting related rows in Laravel (Eloquent ORM)

When I delete a row using this syntax:
$user->delete();
Is there a way to attach a callback of sorts, so that it would e.g. do this automatically:
$this->photo()->delete();
Preferably inside the model-class.
I believe this is a perfect use-case for Eloquent events (http://laravel.com/docs/eloquent#model-events). You can use the "deleting" event to do the cleanup:
class User extends Eloquent
{
public function photos()
{
return $this->has_many('Photo');
}
// this is a recommended way to declare event handlers
public static function boot() {
parent::boot();
static::deleting(function($user) { // before delete() method call this
$user->photos()->delete();
// do the rest of the cleanup...
});
}
}
You should probably also put the whole thing inside a transaction, to ensure the referential integrity..
You can actually set this up in your migrations:
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
Source: http://laravel.com/docs/5.1/migrations#foreign-key-constraints
You may also specify the desired action for the "on delete" and "on
update" properties of the constraint:
$table->foreign('user_id')
->references('id')->on('users')
->onDelete('cascade');
Note: This answer was written for Laravel 3. Thus might or might not works well in more recent version of Laravel.
You can delete all related photos before actually deleting the user.
<?php
class User extends Eloquent
{
public function photos()
{
return $this->has_many('Photo');
}
public function delete()
{
// delete all related photos
$this->photos()->delete();
// as suggested by Dirk in comment,
// it's an uglier alternative, but faster
// Photo::where("user_id", $this->id)->delete()
// delete the user
return parent::delete();
}
}
Hope it helps.
Relation in User model:
public function photos()
{
return $this->hasMany('Photo');
}
Delete record and related:
$user = User::find($id);
// delete related
$user->photos()->delete();
$user->delete();
There are 3 approaches to solving this:
1. Using Eloquent Events On Model Boot (ref: https://laravel.com/docs/5.7/eloquent#events)
class User extends Eloquent
{
public static function boot() {
parent::boot();
static::deleting(function($user) {
$user->photos()->delete();
});
}
}
2. Using Eloquent Event Observers (ref: https://laravel.com/docs/5.7/eloquent#observers)
In your AppServiceProvider, register the observer like so:
public function boot()
{
User::observe(UserObserver::class);
}
Next, add an Observer class like so:
class UserObserver
{
public function deleting(User $user)
{
$user->photos()->delete();
}
}
3. Using Foreign Key Constraints (ref: https://laravel.com/docs/5.7/migrations#foreign-key-constraints)
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
As of Laravel 5.2, the documentation states that these kinds of event handlers should be registered in the AppServiceProvider:
<?php
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
User::deleting(function ($user) {
$user->photos()->delete();
});
}
I even suppose to move them to separate classes instead of closures for better application structure.
It is better if you override the delete method for this. That way, you can incorporate DB transactions within the delete method itself. If you use the event way, you will have to cover your call of delete method with a DB transaction every time you call it.
In your User model.
public function delete()
{
\DB::beginTransaction();
$this
->photo()
->delete()
;
$result = parent::delete();
\DB::commit();
return $result;
}
To elaborate on the selected answer, if your relationships also have child relationships that must be deleted, you have to retrieve all child relationship records first, then call the delete() method so their delete events are fired properly as well.
You can do this easily with higher order messages.
class User extends Eloquent
{
/**
* The "booting" method of the model.
*
* #return void
*/
public static function boot() {
parent::boot();
static::deleting(function($user) {
$user->photos()->get()->each->delete();
});
}
}
You can also improve performance by querying only the relationships ID column:
class User extends Eloquent
{
/**
* The "booting" method of the model.
*
* #return void
*/
public static function boot() {
parent::boot();
static::deleting(function($user) {
$user->photos()->get(['id'])->each->delete();
});
}
}
Using Constrained()
After Laravel 7, new foreignId() and constrained() methods are available for defining relationship constraint in database. OnDelete() method can be used on these methods to automatically delete related records.
Old style
$table->unsignedBigInterer('user_id');
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade');
New style
$table->foreignId('user_id')
->constrained()
->onDelete('cascade');
I would iterate through the collection detaching everything before deleting the object itself.
here's an example:
try {
$user = User::findOrFail($id);
if ($user->has('photos')) {
foreach ($user->photos as $photo) {
$user->photos()->detach($photo);
}
}
$user->delete();
return 'User deleted';
} catch (Exception $e) {
dd($e);
}
I know it is not automatic but it is very simple.
Another simple approach would be to provide the model with a method. Like this:
public function detach(){
try {
if ($this->has('photos')) {
foreach ($this->photos as $photo) {
$this->photos()->detach($photo);
}
}
} catch (Exception $e) {
dd($e);
}
}
Then you can simply call this where you need:
$user->detach();
$user->delete();
Add delete function on model that you want to delete
Define relations of models
for example in this instance:
/**
* #return bool|null
*/
public function delete(): ?bool
{
$this->profile()->delete();
$this->userInterests()->delete();
$this->userActivities()->delete();
$this->lastLocation()->delete();
return parent::delete();
}
And relations in user model are:
public function profile()
{
return $this->hasOne(Profile::class, 'user_id', 'id');
}
public function userInterests()
{
return $this->hasMany(userInterest::class, 'user_id', 'id');
}
public function userActivities()
{
return $this->hasMany(userActivity::class, 'user_id', 'id');
}
public function lastLocation()
{
return $this->hasOne(LastLocation::class, 'user_id', 'id');
}
This way worked for me on Laravel 8:
public static function boot() {
parent::boot();
static::deleted(function($item){
$item->deleted_by = \Auth::id(); // to know who delete item, you can delete this row
$item->save(); // to know who delete item, you can delete this row
foreach ($item->photos as $photo){
$photo->delete();
}
});
}
public function photos()
{
return $this->hasMany('App\Models\Photos');
}
Note: deleting in this syntax $user->photos()->delete(); not worked for me...
In my case it was pretty simple because my database tables are InnoDB with foreign keys with Cascade on Delete.
So in this case if your photos table contains a foreign key reference for the user than all you have to do is to delete the hotel and the cleanup will be done by the Data Base, the data base will delete all the photos records from the data base.
Here are the perfect solutions.
# model
public function order_item_properties()
{
return $this->hasMany(OrderItemProperty::class, 'order_id', 'id');
}
public function order_variations()
{
return $this->hasMany(OrderItemVariation::class, 'order_id', 'id');
}
# controller
$order_item = OrderItem::find($request->order_id);
$order_item->order_item_properties()->delete();
$order_item->order_variations()->delete();
$order_item->delete();
return response()->json([
'message' => 'Deleted',
]);
Or you can do this if you wanted, just another option:
try {
DB::connection()->pdo->beginTransaction();
$photos = Photo::where('user_id', '=', $user_id)->delete(); // Delete all photos for user
$user = Geofence::where('id', '=', $user_id)->delete(); // Delete users
DB::connection()->pdo->commit();
}catch(\Laravel\Database\Exception $e) {
DB::connection()->pdo->rollBack();
Log::exception($e);
}
Note if you are not using the default laravel db connection then you need to do the following:
DB::connection('connection_name')->pdo->beginTransaction();
DB::connection('connection_name')->pdo->commit();
DB::connection('connection_name')->pdo->rollBack();
It’s better to use onDelete cascade when defining your model’s migration. This takes care of deleting the model’s relations for you:
e.g.
$table->foreign(’user_id’)
->references(’id’)->on(’users’)
->onDelete(’cascade’);
If you happen to find yourself thinking about how to delete a model and its relations to a level greater than 3 or 4 nested relations, then you should consider redefining your model's relationships.
$table->foreignId('user_id')->constrained('user')->cascadeOnDelete();
or
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
yeah, but as #supersan stated upper in a comment, if you delete() on a QueryBuilder, the model event will not be fired, because we are not loading the model itself, then calling delete() on that model.
The events are fired only if we use the delete function on a Model Instance.
So, this beeing said:
if user->hasMany(post)
and if post->hasMany(tags)
in order to delete the post tags when deleting the user, we would have to iterate over $user->posts and calling $post->delete()
foreach($user->posts as $post) { $post->delete(); } -> this will fire the deleting event on Post
VS
$user->posts()->delete() -> this will not fire the deleting event on post because we do not actually load the Post Model (we only run a SQL like: DELETE * from posts where user_id = $user->id and thus, the Post model is not even loaded)
You can use this method as an alternative.
What will happen is that we take all the tables associated with the users table and delete the related data using looping
$tables = DB::select("
SELECT
TABLE_NAME,
COLUMN_NAME,
CONSTRAINT_NAME,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_NAME = 'users'
");
foreach($tables as $table){
$table_name = $table->TABLE_NAME;
$column_name = $table->COLUMN_NAME;
DB::delete("delete from $table_name where $column_name = ?", [$id]);
}

Categories