Laravel delete by BelongsToMany relationship - php

I have a Task class. And each record of this table can have one or many childs and can be one or many parents of another task.
Table tasks
id | name
Table tasks_links
parent_id | child_id
Task model
<?php
namespace App;
class Task extends Model
{
public function childs()
{
return $this->belongsToMany(Task::class, 'tasks_links', 'parent_id','child_id');
}
public function parents()
{
return $this->belongsToMany(Task::class, 'tasks_links' , 'child_id' , 'parent_id');
}
}
And in my controller and views
I can use the relationship like
Task::find($id)->parents(); //Return an array
When I delete a task, I would also to delete the links with other related tasks
So the following is working :
\DB::table('tasks_links')->where('parent_id' , $task->id)->delete();
\DB::table('tasks_links')->where('child_id' , $task->id)->delete();
But I the following is not working
foreach ($task->parents() as $parent) {
$parent->delete();
}
Is there any way to delete the links by using the parents and childs functions instead of searching directly in the database ?

$task =Task::find($id);
$task->childs()->detach();
$task->parents()->detach();
$task->delete();

The fastest way of doing this.. if every time you delete a task you will want to delete all relations you should just include this in your task migrations
$table->foreign('the_foreign_key')->references('related_table_primary_key')->on('table_name_related')->onDelete('cascade');
Every time you delete a task on relationships will be deleted.

What I usually do on these situations is, when designing the database, I write a migration and set what columns I want to be affected by cascade.
Example
php artisan make:migration cascade_implementation
and on the Up() I set whatever I wish to be affected, on Down() I set the inverse, in case of a rollback needed. In your case, something like (or the other way around, depending on your needs):
public function up()
{
Schema::table('tasks', function (Blueprint $table) {
$table->foreign('parent_id')
->references('id')->on('tasks_links')
->onDelete('cascade');
$table->foreign('child_id')
->references('id')->on('tasks_links')
->onDelete('cascade');
});
}
Read more at https://laravel.com/docs/5.5/migrations

Related

Laravel Eloquent where statement returns no attributes

So from my previous post, I was advised to start using Eloquent models, which I did.
My end goal, is to print out specific gifts, that belongs to that specific box.
Migrations:
gift_items:
public function up()
{
Schema::create('gift_items', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->float('unit_price');
$table->integer('units_owned');
});
}
gift_campaigns:
public function up()
{
Schema::create('gift_campaigns', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->foreignId('user_foreignK')->constrained('users');
$table->integer('gift_item_count')->nullable();
$table->string('status');
$table->date('dispatch_date');
$table->date('delivery_date');
});
}
Pivot table:
public function up()
{
Schema::create('campaigns_gifts', function (Blueprint $table) {
$table->foreignId('gift_id')->constrained('gift_items');
$table->foreignId('campaign_id')->constrained('gift_campaigns');
});
}
Controller:
function box($id){
$data = Campaign::with('gifts')->where('id', $id)->get();
return view('DBqueries.boxView', ['data'=>$data]);
}
Error that I receive using this way:
Seems like the updated version is trying to call the gift_campaigns table id, instead of the pivots table campaign_id.
Once again, I need that Request $id would match the pivots table campaign_id, and print out all of the gifts that this specific id holds
First of all as I sense the campaigns_gifts is a pivot table for campaigns and gifts having a Many-to-Many relation. You are doing it completely against the conventions of Laravel.
You generally do not use a Model for pivot table.
You generally do not fetch data from the pivot table directly. Instead use the relation on one of the related Models.
Note: Laravel does allow a Model for a Pivot, and you can query the pivot table directly, just check the documentation.
The correct way:
Pivot
Make a pivot table (that you already have) with column gift_id and campaign_id. i.e., the convention for naming keys as [table_name_singular]_[primary_key_on_table]
Model
One each model, define relationship for the other data as:
Gift.php Model:
public function campaign() {
return $this->belongsToMany(Campaign::class, 'campaign_gift');
}
Campaign.php Model:
public function gifts() {
return $this->belongsToMany(Gift::class,'campaign_gift');
}
since gift have a hasMany relation, the gifts table must contain a foreign key to campaigns table named campaign_id (same as the one on pivot).
Controller
Now in your controller:
function box($id){
$data = Campaign::where('id',$id)->with('gifts')->get();
return view('DBqueries.boxView', ['data'=>$data]);
}
You don't need to tell Laravel which columns, tables etc are you referring to, as long as you follow the conventions, Laravel will magically do things that otherwise would have been much more painful.

Laravel Many to Many pivot attach error: "no such table: lists"

I'm fairly now to using Pivot tables, and Many to Many relations overall, so hopefully I can learn what the issue.
I have a many to many relation between beers and beer lists, and I have a custom pivot table:
Schema::create('beer_list_pivot', function (Blueprint $table) {
$table->id();
$table->foreignId('beer_id')->constrained();
$table->foreignId('list_id')->constrained();
});
And the relations are looking like this:
BeerList Model:
public function beer()
{
return $this->belongsToMany(Beer::class, 'beer_list_pivot', 'beer_id', 'list_id');
}
Beer Model:
public function list()
{
return $this->belongsToMany(BeerList::class, 'beer_list_pivot', 'list_id', 'beer_id');
}
The attach method is used like this:
public function addItem(Request $request, $id)
{
$beerId = $request->beer;
$list = BeerList::findOrFail($id);
$list->beer()->attach($beerId);
return redirect("/list/" . $list->id);
}
This is returning the error:
SQLSTATE[HY000]: General error: 1 no such table: main.lists (SQL: insert into "beer_list_pivot" ("beer_id", "list_id") values (1, 1))
Any idea what is causing this?
it says lists table not found
try this
Schema::create('beer_list_pivot', function (Blueprint $table) {
$table->id();
$table->foreignId('beer_id')->constrained();
$table->foreignId('list_id')->constrained('beer_lists'); // <= here put your table name
});
and refresh migration
As you mentioned you are new to using pivot tables, so let me share some super small tips so you have less errors next time:
Try to stick to the Laravel standard table naming convention as much as possible, so you reduce code declaration and possible misses in declaring stuff.
You should be safe to remove all definitions for each relation (remove any parameter after table definition).
Try to add $table->timestamps(); to your tables as much as possible, because you will be able to debug anything if you have a created_at and updated_at column.
You can take advantage of implicit model binding in your controllers.
So, only for tip 1, your final code could be like this (if you follow standards):
Migration
Schema::create('beer_beer_list', function (Blueprint $table) {
$table->id();
$table->foreignId('beer_id')->constrained();
$table->foreignId('list_id')->constrained();
$table->timestamps();
});
BeerList Model
public function beer()
{
return $this->belongsToMany(Beer::class);
}
Beer Model
public function list()
{
return $this->belongsToMany(BeerList::class);
}
So, for tip 4:
public function addItem(Request $request, BeerList $list)
{
$beerId = $request->beer;
$list->beer()->attach($beerId);
return redirect("/list/" . $list->id);
}
And your route should be something like:
Route::post('/beer/{list}', [BeerListController::class, 'addItem']);
The most important thing for this to work is that {list} or whatever {slug} you write, must match BeerList $list or BeerList $slug (variable name) and also you have to type hint it. The documentation explains it very well.

How to make composite/multiple primary key in Laravel >= 5.6?

I have a many-to-many relationship created between USERS and COURSES. The models and migrations are made as follows.
User Model:
public function courses()
{
return $this->belongsToMany(Course::class)->withTimestamps();
}
Course Model:
public function users()
{
return $this->belongsToMany(User::class, 'course_user', 'course_id', 'user_id')->withTimeStamps();
}
The problem is I cannot find a way to implement a composite primary key into my migration file or pivot table in Laravel like this,
Schema::create('course_user', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
$table->integer('course_id')->unsigned()->index()->primary();
$table->foreign('course_id')->references('id')->on('courses');
$table->integer('user_id')->unsigned()->index()->primary();
$table->foreign('user_id')->references('id')->on('users');
});
After coding the above, when I call php artisan migrate:refresh an error appears showing multiple primary keys performed in the linked table. So I did a research on this matter and found out that ELOQUENT does not support composite primary keys as stated in this forum: link to the forum
Is there any other way I can go around this?
I have somehow managed to write a piece of code to detect a existing entries in the pivot table or the many-to-many relationship, inside the controller and avoid new duplicate entries.
To counter attack this problem I wrote a piece code in the controller to detect an existing tuple in the pivot table as follows. NOTE: I have used Resources to retrieve data, you can avoid the Resource functions and use the normal method also.
The userId and courseId are passed as parameters in the request:
public function index(Request $request) {
$target = new AcademicPaymentResource(User::with('courses')->findOrFail($request['userId']));
if ($target != null)
{
foreach ($target as $query){
// search for existing tuples
foreach ($query->courses as $subQuery => $key){
// Check if courseId in existing tuple is equal to the courseId of request
if ( $query->courses[$subQuery]->pivot->course_id == $request['courseId']){
// Do anything here..
// display data to test if it works
return $query->courses[$subQuery];
}
}
// update the pivot table if the tuple doesnt exist
$user = User::findOrFail($request['userId']);
$user->courses()->attach($request['courseId']);
// read tuple data again to display if it works
$target = new AcademicPaymentResource(User::with('courses')->findOrFail($request['userId']));
return $target;
}
}
}
This method actually works fine, since I have used it flawlessly so far.
But if there is any other proper method please don't hesitate to answer..

Update parent model with the HasOne child relationship

I have a League model and a Season model their respective migrations and relationships.
League migration and relations
Schema::create('leagues', function (Blueprint $table) {
$table->unsignedBigInteger("id")->primary();
$table->boolean("active");
$table->string("name");
$table->unsignedBigInteger("current_season_id")->nullable();
$table->timestamps();
});
public function current_season()
{
return $this->hasOne(Season::class);
}
Season migration and relations
Schema::create('seasons', function (Blueprint $table) {
$table->unsignedBigInteger("id")->primary();
$table->string("name");
$table->unsignedBigInteger("league_id");
$table->boolean("is_current_season");
$table->timestamps();
});
public function league()
{
return $this->belongsTo(League::class);
}
I have two vars with my models:
$league = League::find(1);
$season = Season::find(10);
With this line, I know automatically league_id in the Season model is filled with the $league->id
$season->league()->associate($league)->save();
I want do the inverse, and fill the current_season_id without doing:
$league->current_season_id = $season->id;
$league->save();
Is it possible?
Following the comments from #M Khalid Junaid, I think it´s better this way:
Remove current_season_id from League model.
Rewrite the current_season relation to this way:
public function current_season()
{
return $this->hasOne(Season::class)->where("is_current_season", true);
}
Now, in this way, I can access the current season of the league in the form: $league->current_season
Thank you.
You do not need $table->unsignedBigInteger("current_season_id")->nullable(); in leagues table, if you are using hasOne relationship, otherwise you need another type of relationship.
I'd strong recommend in seasons table, to use a foreign key declaration in your migration
$table->unsignedBigInteger("league_id");
$table->foreign( 'league_id' )->references( 'id' )->on( 'leagues' );

Laravel, Detach() method to delete parent records

Hi guys I'm working with many to many relationship and I want to know if is there any way to delete the records of the main table.
These are my tables
Schema::create('inventario_inicial', function (Blueprint $table) {
$table->increments('id');
$table->integer('producto_nombre_id')->unsigned();
$table->foreign('producto_nombre_id')->references('id')->on('producto_nombre');
$table->integer('existencias');
$table->double('promedio');
$table->timestamps();
});
Schema::create('empresa_inventario_inicial', function (Blueprint $table) {
$table->integer('empresa_id')->unsigned();
$table->foreign('empresa_id')->references('id')->on('empresas');
$table->integer('inventario_inicial_id')->unsigned();
$table->foreign('inventario_inicial_id')->references('id')->on('inventario_inicial');
});
I can get the data via pivot with this code
$empresa = Empresa::find($request->empresa_id);
$empresa->inventario_inicial();
To detach the data of that $empresa i use $empresa->inventario_inicial()->detach();
It deletes the records of the pivot table witch is correct, but also I want to delete not only what's in empresa_inventario_inicial but also inventario_inicial that were related. Something like cascade deleting but from pivot table.
You can use $table->foreign('inventario_inicial_id')->references('id')->on('inventario_inicial')->onDelete('cascade') in your migration.
If you don't want to cascade, consider using model events to automatically detach any empresa_inventario_official pivot records when deleting an inventario_official, and then use the $empresa->inventario_inicial()->delete() method in place of your detach() above.
In App\InventarioOfficial:
protected $dispatchesEvents = ['deleting' => InventarioDeleting::class];
Then you can define the event and a listener for the event:
In App\Events\InventarioDeleting
class InventarioDeleting
{
use SerializesModels;
public $inventario_official;
public function __construct(InventarioOfficial $inventario_official)
{
$this->inventario_official = $inventario_official;
}
}
In App\Providers\EventServiceProvider
public function boot()
{
parent::boot();
Event::listen(\App\Events\InventarioDeleting::class, function ($io) {
DB::('empresa_inventario_inicial')->where('inventario_inicial_id',$io->id)->delete();
//or $io->empresas()->detach();
});
}

Categories