Laravel automatically deleting one-to-many polymorphic relation - php

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);
}

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.

translating database relationships to Eloquent relationships

So, I've been trying to watch and read eloquent relationships from laracasts. Unfortunately I still don't quite get how to translate database relationships to eloquent relationships (hasOne, belongsTo, hasMany, etc).
Let's say I have an Account and Customer tables. Account table has a "Customer_id" foreign key which references to the "id" on Customer table. Let's assume it's a one-to-many relationship. How should I put it on my models on laravel?
Which table should contain the "hasMany" and which one should have the "belongsTo"?
Just think about how you would say it. In your case it sounds like a Customer has many Accounts and an Account belongs to one Customer.
So you would put the hasMany() in your Customer model and the belongsTo() in your Account model.
class Customer extends Model {
public function accounts() {
return $this->hasMany('App\Account');
}
}
class Account extends Model {
public function customer() {
return $this->belongsTo('App\Customer');
}
}
You can read more about Laravel database relationships here.

Detach doesn't trigger cascade

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');

Is this many-to-many relationship possible in Laravel?

I'm trying to get my head around using polymorphic relationships for a many-to-many relationship between suppliers and products:
products
id
name
suppliers
id
name
product_supplier
id
product_id // belongsToMany easily takes care of this id
supplier_id // and this id
price // this can be fetched using withPivot('price')
deliverymethod_id // I'm having difficulties "joining" this one.
I'm confident in using belongsToMany(), I can easily do something like this:
public function products()
{
return $this
->belongsToMany('Supplier')
->withPivot('price');
}
But the catch here is joining to that third column in the relationship table:
deliverymethods
id
name
I am unsure how to do this. I've been told that Polymorphic Relationships are what I'm after however I'm unsure how to implement them for my situation.
http://laravel.com/docs/4.2/eloquent#many-to-many-polymorphic-relations
According to the documentation, I would have to rename my table columns to include *able_id and *able_type. This is really confusing.
I was expecting laravel to having something like belongsToMany('Supplier')->withAlso('Deliverymethod')
I'm afraid that method does not exist (yet?).
What I fall back to is manually filling in the 3rd relation:
public function products()
{
return $this
->belongsToMany('Supplier')
->withPivot('price', 'delivermethod_id');
}
Now I can access ->pivot->deliverymethod_id on every Product that I get via Supplier.
You could even add a function in your Product model that fills this in automatically:
Class Product ... {
protected $appends = array('deliverymethod');
public function getDeliverymethodAttribute()
{
return Deliverymethod::find($this->pivot->delivermethod_id);
}
Now every time you request a product via it's relation to the supplier, it automatically includes a deliverymethod attribute with the object in it.
(To have it not throw an error when you get a Product directly, just remove the $appends variable from the Product model and call the getDeliverymethodAttribute() method manually whenever you need it.)
Short explanation about polymorphic relations:
Polymorphic relations are for relations, where two models are related to a third model at the same time. So for example both a User and a Product can have a Picture of them. Now, it doesn't make sense to have two models for the pictures (UserPicture and ProductPicture), since they both have the same characteristics. This would be a perfect reason to use a polymorphic relation, where the Picture can both belong to a User or a Product.
However, in your case the Deliverymethod applies directly to the relation between Supplier and Product. So this is not where polymorphic relations would work, but it has instead to be done the way you did it.

Laravel 4 Polymorphic with multiple Pivot Tables

I'm trying to create a polymorphic relationship with multiple pivot tables. I have a table of requirements that can be assigned to accounts, roles, trips, and countries. This needs to be a many to many relationship because the same requirement could apply to multiple countries and/or trips and/or accounts etc.
I then need a table listing outstanding requirements for the user. For example: if a user has a certain account and there are requirements related to that account, then those requirements would be added to the user's list of requirements.
One solution I have is to first assign the requirements to the accounts, roles, trips, and countries using Pivot tables in a Many to Many relationship. Then using a polymorphic relationship I would connect the user to whichever pivot tables relate.
But I don't know how to do this or if it is even possible?
Here are my tables:
user_requirements
- id
- user_id
- requireable_id
- requireable_type
account_requirement
- id
- account_id
- requirement_id
role_requirement
- id
- role_id
- requirement_id
trip_requirement
- id
- account_id
- requirement_id
country_requirement
- id
- account_id
- requirement_id
Laravel 4.1 now has support for polymorphic many to many relationships.
Example below shows how I have implemented sharing Photos with both Products and Posts.
DB Schema
photos
id integer
filename string
alt string
photoable
id integer
photoable_id integer
photoable_type string
Models
Photo Model
class Photo extends Eloquent
{
public function products(){
return $this->morphedByMany('Product', 'photoable');
}
public function posts(){
return $this->morphedByMany('Post', 'photoable');
}
}
Product Model
class Product extends Eloquent
{
public function photos(){
return $this->morphToMany('Photo', 'photoable');
}
}
Post Model
class Post extends Eloquent
{
public function photos(){
return $this->morphToMany('Photo', 'photoable');
}
}
With the above, I can access all photos which are attached to a product as follows:
$product = Product::find($id);
$productPhotos = $product->photos()->all();
I can also iterate over to display all photos as any collection of models.
foreach ($productPhotos as $photo)
{
// Do stuff with $photo
}
The above can be replicated almost exactly to your requirements.
create a requirements table
create a requireable table
In Requirement model, declare all morphedByMany relationships
In Country, Trip, Role etc. declare morphToMany relationships
nb - I've typed this out freehand in S/O with no code editor, so there will probably be a typo, error or two - but concept remains the same.
A polymorphic relation in Laravel 4 is intended for single MODEL associations, therefore you cannot achieve what you are trying to build with this method. This is due to the fact that a pivot table doesn't represent a Model.

Categories