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

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.

Related

Laravel sends SQL request to the wrong table without reason

I have a problem with Laravel, let me describe all steps.
Created migration "create_user_role_table" with:
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('role_id');
Created Role and UserRole models
In UserRole model:
protected $table = "user_role";
protected $guarded = false;
In Role and User models added function belongsToMany.
IndexController: $user = User::find(1); dd($user->role[0]->title);
After those steps, laravel without reason trying to open "role_user" table, not "user_role". Where did he find this table in the code? "SQLSTATE[42S02]: Base table or view not found: 1146 Table 'blog.role_user' doesn't exist". If rename table in DataBase to "role_user", it's work. How to solve that, where did he found table with name "role_user"?
Laravel's naming convention for pivot tables is snake_cased model names in alphabetical order separated by an underscore. So, if one model is User, and the other model is Role, the pivot table will be role_user.
You are free to use any table name you want (such as user_role), 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.
return $this->belongsToMany(Role::class, 'user_role', 'user_id', 'role_id');

Laravel 5.4 Relationships between 3 tables

I am new in laravel, I already know how to join tables using the query builder. I just like to learn how to use relationships to avoid repetition of codes and to simplify it. I can join 2 tables, but I can't get the 3rd table.
I like to display employees assigned tasks information from the Tasks table, this table only has the project id that needs to be joined to the Projects table. Other employees can join in existing projects with other employees.
Employee model:
public function tasks()
{
return $this->hasMany('\App\Task', 'project_coder', 'id');
}
Task model:
public function projects()
{
return $this->hasMany('\App\Project', 'id', 'project_id');
}
Projects model:
public function belongsToTasks()
{
return $this->belongsToMany('\App\Task', 'project_id', 'id');
}
I can only get the IDS from Task model. the ID will be use to fetch the project info from project tables. Unfortunately I cant do that part.
Employees controller:
public function show($id)
{
$data = Employees::find($id);
return view('show-employee')->withInfo($data);
}
Is it good practice to use query builder rather than relationships?
UPDATE:
Employees table
{
"id":1,
"name": "Juan"
}
Tasks table
{
"id":1, // autoincrement and will not be use for linking to other tables.
"project_id": 1, //use to connect to project table
"project_coder": 1 // use to connect to employees table
}
Projects table
{
"id":1,
"name": "First Project"
}
To deal with this is best to create pivot table like employee_task
where table will have just two columns task_id and employee_id
then you can define in Task model
public function employees()
{
return $this->belongsToMany('\App\Employee', 'employee_task');
}
and Employee model
public function tasks()
{
return $this->belongsToMany('\App\Task', 'employee_task');
}
now you can see all employee tasks and rest of you relations work just fine
$data = Employees::find($id);
$data->tasks //you get all tasks assigned to this employee
$data->tasks()->first()->projects //you get all your projects for first task
$data->tasks()->first()->employees //all employees assigned to first task with this employee
Also recommend to you to lookup pivot tables, attach(), detach() and sync() functions. you can check it here : https://laravel.com/docs/5.7/eloquent-relationships#updating-many-to-many-relationships
UPDATE:
Ok I understand now what you are trying to do. You already have pivot table. I was little confused with your Project model which is not necessary.
You can remove your Project class if you have it just for this relation
and update your model relations as I wrote above. You don't need project_id and project_coder_id in this relation. Also change the column names to more conventional names like employee_id and task_id as I mentioned or whatever your table names are.
And you can rename employee_task pivot table name to your project table or you can rename it as well.
EDIT
When you use Project model for another data, you need to create 4th table as I mentioned above.
FINAL
drop project_coder column in Tasks table - unecessary column
create pivot table employees_task with employee_id,task_id
create mentioned relations with pivot table
I assume that Project hasMany() tasks and Task only belongsTo() one project. So need to create these relations as well.
Then you can use these relations like this:
$employee = Employee::find($id);
$task = Task::find($id);
$project = Project::find($id);
$employee->tasks //all tasks assigned to employee
$task->employees //all employees assigned to task
$task->project //project info assigned to task
$employee->tasks()->first()->project //project data from first task of employee
$project->tasks()->first()->employees //all employees assigned to first project task

Why the name convention of Laravel relationships so weird?

I have three tables:
users
columns: id, name
books
columns: id, name
book_user:
columns: user_id, book_id, state(not read yet, reading, read already)
I intended to user book_user as many-to-many relation table, so I follow the name convention from doc:
To define this relationship, three database tables are needed: users, roles, and role_user. The role_user table is derived from the alphabetical order of the related model names, and contains the user_id and role_id columns.
I wrote code:
class User extends Model
{
public function books()
{
return $this->belongsToMany('App\Book');
}
}
, and I can retrieve the books which related to the user by call user->books().
That works well, but when I try to retrieve the state of the book which related to a user, I create model:
class BookUser extends Model
{
//
}
When I use this Model, it claims:
Base table or view not found: 1146 Table 'myapp.book_users' doesn't exist
Conclusion:
the name convention of a table which can be used as many-to-many is <singular_noun>_<singular_noun> (such as book_user).
the name convention of table with multiple words which mapping to a Model is <singular_noun>_<plural_noun> (such as book_users).
I know I can set the table name manually which a model mappings to, but I just wonder:
Does that conflict is a design flaw or just I'm doing wrong in designing tables and models?
You don't need define a model for pivot table,just add withPivot
class User extends Model
{
public function books()
{
return $this->belongsToMany('App\Book')->withPivot('state');
}
}
Then you can retrieve book states from model pivot
Generally you don't need a model for pivot tables. You can define the inverse of the relationship. And if you want to store extra data in pivot table maybe you can check withPivot method. Or explain what are you trying to do.
But if you want to create a model, you need to specify your table name in your model manually. Because Laravel doesn't know if its a pivot table or normal table. It just tries to guess the table name by making it plural.

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.

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

Categories