Detach doesn't trigger cascade - php

I have a many to many relationship. The pivot model hasMany children. When I call detach on the many to many, I need to also delete the pivot model's children. I wanted to use onDelete('cascade'), but that doesn't seem to work. I also tried this: http://laravel-tricks.com/tricks/using-model-events-to-delete-related-items, but that doesn't seem to work either. Neither are working probably because the destroy event is not being triggered.
Any ideas on how I can get the children to delete when I call detach?
Here's some of my code in case I made a mistake:
My pivot model:
Schema::create('beer_distributions', function(Blueprint $table)
{
$table->increments('id');
$table->integer('beer_id');
$table->integer('distributor_id');
The pivot model's children:
Schema::create('kegs', function(Blueprint $table)
{
$table->increments('id');
$table->integer('beer_distribution_id');
$table->dropForeign('kegs_beer_distribution_id_foreign');
$table->foreign('beer_distribution_id')
->references('id')
->on('beer_distributions')
->onDelete('cascade');
I don't know if there is anyway to salvage this approach. I called sync and then tried to delete the children, not realizing the parent was already gone making the children inaccessible.:
$attachments = $beer->distributors()->sync(Input::get('distributors'));
foreach ($attachments['detached'] as $distributor_id) {
BeerDistribution::where('beer_id', '=', $id)
->where('distributor_id', '=', $distributor_id)->first()->destroy();
}
UPDATE:
Just to be clear, I have four models I am working with. Beers and distributors with a many to many relationship. The pivot model, BeerDistributions, hasMany kegs. When I call sync to update a beers distributors, the BeerDistributions do get deleted automatically, and I want the kegs to get deleted at the same time.
Here are some of my models:
class Beer extends Eloquent {
public function distributors()
{
return $this->belongsToMany('Distributor', 'beer_distributions');
}
Beer ^ manyToMany with Distributor:
class Distributor extends Eloquent {
public function beers()
{
return $this->belongsToMany('Beer', 'beer_distributions');
}
The pivot model...
class BeerDistribution extends Eloquent {
public function kegs()
{
return $this->hasMany('Keg', 'beer_distribution_id');
}
has many kegs:
class Keg extends Eloquent {
public function beerDistribution()
{
return $this->belongsTo('BeerDistribution');
}

There seems to be some confusion here about pivot tables, detach, and cascades.
Basically, the detach method should be all you need for this. All it's doing is managing the pivot table through the belongsToMany() relationship.
So if you do $keg->distributors()->detach([1,2,3]) and $keg->id is 5, it will search and remove items from the pivot where the distributor is 1, 2, or 3 AND the keg id is 5. In most cases, you shouldn't even need an eloquent model for the pivot tables. detach(), attach(), and sync() will handle that table for you.
Delete on cascade is likely what is causing you issues because you have it backwards. In this case, the delete on cascade key would be put on the pivot table. What this means is when you delete a parent (keg or distribution), those items belonging to that parent in the pivot table would automatically be removed. This saves you from having orphaned data in the table.
Why I say you are doing this backwards is because you put the key on your kegs table which means when an item in the pivot table is deleted, your keg is also deleted. This is bad and not at all what you want. You want to delete items from the pivot table when your keg is deleted.
Remove that key and in your schema to create the beerDistributions add two keys that will look something like this...
$table->foreign('beer_id')->references('id')->on('beers')->onDelete('Cascade');
$table->foreign('distribution_id')->references('id')->on('distributors')->onDelete('Cascade');
And if you have a key which is deleting on cascade on your distributor's table, remove that too. Should only have those foreign keys on the pivot table.
Edit: Seeing as how you want to delete kegs, it looks as though you are referencing the wrong columns in the pivot table.
$table->foreign('id')
->references('beer_distribution_id')
->on('beer_distributions')
->onDelete('cascade');

Related

Laravel one-to-many relationship - a reference to a non-existent field

What do I forget about doing migrations in Lavarel that once relations act to me like this and once so? Presenting what I want to get is I want to assign the user his order and order the products he ordered. So I have a User table, the Order table, and the OrderProduct table. Usera table with one-to-many relation with the Order table, Order table with one-to-many relation with the OrderProduct table. Starting from the relation Order and OrderProduct, I get an error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'order_products_tables.order_table_id' in 'where clause' (SQL: select * from `order_products_tables` where `order_products_tables`.`order_table_id` = 1 and `order_products_tables`.`order_table_id` is not null)
And this error says clearly and clearly that he can not find the order_table_id column in the order_products_tables table and I am not surprised what it may sound silly because there is no such field but there is an order_id field and in migrations is described with which field is the relationship and I can not understand why Laravel tries refer to order_products_tables.
Migrations Order:
Schema::create('order_tables', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned()->nullable();
$table->foreign('user_id')
->references('id')
->on('users');
$table->timestamps();
});
Migrations OrderProduct:
Schema::create('order_products_tables', function (Blueprint $table) {
$table->increments('id');
$table->integer('order_id')->unsigned();
$table->integer('count')->unsigned();
$table->integer('price')->unsigned();
$table->foreign('order_id')
->references('id')
->on('order_tables');
$table->timestamps();
});
As it results from the migration, the order_products_tables table stores the record ID from the order_tables table and the relationship is based on that ID.
Model Order table:
class OrderTable extends Model
{
protected $table = 'order_tables';
public function user()
{
return $this->belongsTo(User::class, 'id');
}
public function products()
{
return $this->hasMany('App\OrderProductTable');
}
}
Model OrderProduct table:
class OrderProductTable extends Model
{
protected $table = 'order_products_tables';
public function order()
{
return $this->belongsTo(OrderTable::class, 'id');
}
}
I do not understand why the reference to order_table_id is going. I have done other relations, eg User and Order on the same principle, and it works without a problem, suddenly here I have such a case. Where should I look for a solution and why does it wo
This error comes from using wrong table names or, to be more correct, not defining the relationship correctly. The following relationship definitions will fix your issue:
class OrderTable extends Model
{
protected $table = 'order_tables';
public function products()
{
return $this->hasMany('App\OrderProductTable', 'order_id', 'id');
}
}
class OrderProductTable extends Model
{
protected $table = 'order_products_tables';
public function order()
{
return $this->belongsTo(OrderTable::class, 'order_id', 'id');
}
}
The reason your previous code did not work is that Laravel uses default values which are basically assumptions and require your database to follow certain naming conventions. Here is a list of conventions you should follow*:
Tables should be named after the model name in plural form and snake_case:
Model User is supposed to have a table named users.
Model Category is supposed to have a table named categories (see the english plural).
Model AccountManager is supposed to have a table named account_managers.
If there is no model for a table, i.e. a many-to-many relationship table, the table name is expected to be in singular form and snake_case, where the model names that hold the relation are ordered alphabetically:
If there are models Category and Product (with tables categories and products) and there is a many-to-many relationship (belongsToMany()) between them, the table for the pivot table is expected to be called order_product (and not product_order because o comes before p in the alphabet).
Foreign key columns are expected to be called after the model they represent with _id as postfix:
When referencing the User model on a BlogPost model for example, Laravel expects a user_id column as foreign key on the BlogPost model. The referenced primary key on the User model is taken from the $primaryKey property, which is 'id' by default.
For your particular scenario, this means we would expect the following models, tables and columns:
Model User with table users and columns like in the default migration of Laravel.
Model Order with table orders and columns like id, user_id, created_at, ...
Model Product with table products and columns like id, name, price, ...
Model OrderProduct with table order_products and columns like id, order_id, product_id, quantity, ...
In theory, the model OrderProduct is not necessary. You should also be able to build the same system without it by defining $this->belongsToMany(Product::class)->withPivot('quantity') on the Order model and $this->belongsToMany(Order::class)->withPivot('quantity') on the Product model (note the pivot fields). Personally, I prefer extra models for many-to-many relations though.
For reference to Eloquent relationships, have a look at the documentation. There are examples for all relationship types and additional information for the additional parameters when you need to override the default table or column names for your relations.
* This list may lack important information. It was created as best effort.

Laravel automatically deleting one-to-many polymorphic relation

I have a relation between Categories and Expenses. An Expense belongsTo a Category, and it's linked using a foreign key in the database. Whenever I delete a category, all linked expenses will be deleted as well, as expected.
Now, I also have a relation between an Expense, and many Transactions, using a One-to-Many polymorphic relation. Using the method below, when I delete an Expense, all Transactions will be deleted with it, as expected.
But, when I delete a Category, all Expenses will be deleted as well as expected, but not the Transactions from either Expense. Why is this and how can I make sure all Transactions are deleted as well for all Expenses, when I delete a Category?
Deleting event in Expense model:
protected static function boot() {
parent::boot();
static::deleting(function($expense) {
$expense->transactions()->delete();
});
}
Relation Expense -> Transactions:
public function transactions()
{
return $this->morphMany(Transaction::class);
}

Multiple "statuses" for many-2-many table relationship in a Laravel Data Model? Best approach?

I am new to Laravel (only been coding a few months). I've created a DB model that connects two tables "Players" and "Teams" via a pivot table.
class Player extends Model
{
public function teams()
{
# With timetsamps() will ensure the pivot table has its created_at/updated_at fields automatically maintained
return $this->belongsToMany('p4\Team')->withTimestamps();
}
}
class Team extends Model
{
public function players()
{
return $this->belongsToMany('p4\Player')->withTimestamps();
}
}
Players can be members of (many) teams, "own" teams, be "recruited" to teams, and be "rejected" from teams. Both Teams and Players can initiate requests for each of these statuses, allowing the other party to confirm/reject. In each case, they should be linked and can only occupy one of the four statuses. What is the best way to link these two tables such that the relationship between any two instances can be given 4 "statuses"?
I need to give the users (whom control teams and players in this open management/recruitment environment), the ability to request/approve classification in each of the "statuses".
It strikes me that the cleanest way to do this would be to use a single pivot table that "assigns" these given statuses to each linked ID pair. I, however, have only seen simpler examples of this concept executed and am as a result unsure as to what the best way to do that is. I would appreciate some guidance here.
Schema::create('player_team', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
# `book_id` and `tag_id` will be foreign keys, so they have to be unsigned
# Note how the field names here correspond to the tables they will connect...
# `book_id` will reference the `books table` and `tag_id` will reference the `tags` table.
$table->integer('player_id')->unsigned();
$table->integer('team_id')->unsigned();
# Make foreign keys
$table->foreign('player_id')->references('id')->on('players');
$table->foreign('team_id')->references('id')->on('teams');
});
*Again, I'm pretty fresh. Apologies if this has an obvious solution I'm just missing.
If I understood you correctly, you can add status column to a pivot table:
$table->tinyInteger('status')->unsigned();
Don't forget to add withPivot() to relations:
return $this->belongsToMany('p4\Player')->withPivot('status')->withTimestamps();
You can access this column with pivot and set or unset it by adding an array to attach() and detach() methods:
$team->players()->attach($playerId, ['status' => 3]);
Read more about it in docs:
https://laravel.com/docs/5.3/eloquent-relationships#many-to-many
https://laravel.com/docs/5.3/eloquent-relationships#inserting-and-updating-related-models

naming tables in many to many relationships laravel

I concerned about auto naming tables in many-to-many Laravel relationship.
for example:
Schema::create('feature_product', function (Blueprint $table) {}
when change the table name to:
Schema::create('product_feature', function (Blueprint $table) {}
I have an error in my relationship.
What's the matter with product_feature?
Laravel's naming convention for pivot tables is snake_cased model names in alphabetical order separated by an underscore. So, if one model is Feature, and the other model is Product, the pivot table will be feature_product.
You are free to use any table name you want (such as product_feature), but you will then need to specify the name of the pivot table in the relationship. This is done using the second parameter to the belongsToMany() function.
// in Product model
public function features()
{
return $this->belongsToMany('App\Feature', 'product_feature');
}
// in Feature model
public function products()
{
return $this->belongsToMany('App\Product', 'product_feature');
}
You can read more about many to many relationships in the docs.

Laravel migration foreign keys

I'm trying to build some relationships in Laravel, i'm a little confused about relationships and migrations. Here is a simple example of what i'm doing:
Users -> has_many -> Cats
So my Users migration file has a relationship to Cats like so:
$table->foreign('cats_id')->references('id')->on('cats')
But when I run my migration, I get:
Error: relation cats does not exist...
Do I need to build the Cats table before the Users table?
Do I also need to specify the foreign relation between the two, or if the models contain "hasMany" and "belongsTo" wouldn't Laravel build those relationships automatically on migration?
Do I actually need migrations?
You can't reference a table that not exists. It has nothing to do with Laravel or Eloquent, it's (My)SQL thing.
First create the parent table users, then the child table cats referencing the first:
$table->foreign('user_id')->references('id')->on('users')
this is how User hasMany Cat would look like. cats table has foreign key referencing users table, not the other way around like you tried.
You need to set the foreign key to the table where the 'many' are.
$table->foreign('id')->references('cats_id')->on('Users')
You need to make sure that
Table 'Users' exists before you create table Cats (Or any other table that is referenced)
Column 'id' exists before you create the foreign key. (Or any other column that is referenced)
A quite bulletproof solution for me is to setup the tables with a first migration eg
public function up()
{
Schema::create('users', function(Blueprint $table)
{
$table->increments('id');
$table->timestamps();
$table->string('cats_id');
});
//and
Schema::create('cats', function(Blueprint $table)
{
$table->increments('id');
$table->timestamps();
$table->string('cat_name');
});
}
in one migration file and then I create another migration file that runs in the end and creates all the foreign keys for all migrations that were running before:
public function up()
{
Schema::table('cats', function(Blueprint $table)
{
$table->foreign('id')->references('cats_id')->on('users');
});
}
You can also choose what should happen to your cats table on update or delete of a user by adding eg
->onUpdate('CASCADE')->onDelete('CASCADE');
to the $table->... line
You will have to run the migration for cats table and create that table before you can associate it with users table.
Foreign key relation will be helpful when you are required to do cascade delete or update. Also an insert like the following will be easier for you with the cats relationship set.
$user = User::find(1);
$cats = array(
new Cat(array('name' => 'Kitty')),
new Cat(array('name' => 'Lily')),
);
$user->cats()->save($cats);
When specifying a relationship on User Model the Cat model also needs to exist.
In migration
Users
$table->foreign('cats_id')->references('id')->on('cats');
Cats
$table->foreign('user_id')->references('id')->on('users');
Now you force integrity on database level.
Migrate
run the migration using php artisan migrate
Next step is to add the integrity on you Model
Model
User.php
public function cats()
{
return $this->hasMany('Cats');
}
Cat.php
public function user()
{
return $this->belongsTo('User');
}

Categories