Laravel inserting data as many-to-many and getting error - php

In my application design i each category should be have one or multiple user which i should associate them together, this middle category should be have category_id referenced with categories table and user_id referenced with users table. now i have this migration files:
User:
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->nullable()->constrained();
$table->boolean('active')->default(0); //activating account in register
$table->string('name')->nullable();
$table->string('family')->nullable();
$table->string('username')->unique();
$table->string('email')->unique();
$table->timestamp('email_verified_at')->index()->nullable();
$table->string('password');
$table->rememberToken();
$table->softDeletes();
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent();
});
categories:
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('parent_id')->nullable();
$table->string('title');
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent();
});
user_category:
Schema::create('user_category', function (Blueprint $table) {
$table->foreignId('category_id')->constrained()->onDelete('cascade');
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->primary(
[
'category_id',
'user_id'
]
);
});
running migrate command work fine and i don't get any error, now i want to create a category and then created category should be referenced with user_category table with attach:
$category = \App\Models\Category::create(
[
'title' => 'php',
]
);
$category->owner()->attach(
[
'user_id' => 1
]
);
here i get this error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'category_user_id' in 'field list'
(SQL: insert into `user_category`
(`category_id`, `category_user_id`) values (4, 1))
my Models:
category:
public function owner(): BelongsToMany
{
return $this->belongsToMany(CategoryUser::class );
}
user_category:
public function category(): BelongsToMany
{
return $this->belongsToMany(Category::class);
}

Laravel's naming convention for pivot tables is snake_cased model names in alphabetical order separated by an underscore.
So if you are to follow the conventions, the user_category table should be category_user instead.
Also, the relationships should be in plural form for many-to-many relations.
// Category model
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
// User model
public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class);
}
If you are however deviating from the conventions, you can also set it up by customizing the relationships like so:
// Category model
public function owner(): BelongsToMany
{
return $this->belongsToMany(User::class, 'user_category', 'category_id', 'user_id');
}
// User model
public function category(): BelongsToMany
{
return $this->belongsToMany(Category::class, 'user_category', 'user_id', 'category_id');
}

Related

Why does hasOne and BelongTo relationships load only ids but not the models

I've got a problem with relations in Laravel 9.14 Eloquent.
I have two models File and Project with coresponding tables in MySql database files and projects.
Project migration
Schema::create('projects', static function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('code');
$table->string('theme');
$table->foreignId('discipline_id')->constrained('disciplines');
$table->foreignId('user_id')->constrained('users');
$table->string('external_id');
$table->foreignId('preview')->nullable()->constrained('files');
$table->date('publish_date');
$table->timestamps();
});
File migration
Schema::create('files', static function (Blueprint $table) {
$table->id();
$table->string('original_name');
$table->string('extension');
$table->string('mime_type');
$table->integer('size')->unsigned();
$table->timestamps();
});
Project has field which is related to the File model called 'preview'. Project, basically can have only one preview file. So I did these relatioins in models:
class Project extends Model
public function preview(): BelongsTo
{
return $this->belongsTo(File::class, 'preview', 'id');
}
class File extends Model
public function previewProject(): HasOne
{
return $this->hasOne(Project::class, 'preview', 'id');
}
When i try to get preview of a project this way (controller method):
public function index(): Factory|View|Application
{
$userId = auth()->user()->id;
$projects = User::find($userId)->projects()->with(['user', 'preview'])->get();
//dd($projects->first()->user);
ddd($projects->first()->preview);
return view('user.index', [
'projects' => $projects
]);
}
Instead of File model object I get only integer id of File. Queries, however, look right:
queries image
What is the problem here?
There is no error, the integer your are getting is the value of the attribute but by "chance" both your attribute and relation have the same name.
Either change the relation name:
class Project extends Model
public function previewFile(): BelongsTo
{
return $this->belongsTo(File::class, 'preview', 'id');
}
public function index(): Factory|View|Application
{
$projects = auth()->user()->projects()->with(['user', 'previewFile'])->get();
//dd($projects->first()->user);
ddd($projects->first()->previewFile);
return view('user.index', [
'projects' => $projects
]);
}
Or change the attribute name
Schema::create('projects', static function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('code');
$table->string('theme');
$table->foreignId('discipline_id')->constrained('disciplines');
$table->foreignId('user_id')->constrained('users');
$table->string('external_id');
$table->foreignId('preview_id')->nullable()->constrained('files');
$table->date('publish_date');
$table->timestamps();
});
class Project extends Model
public function preview(): BelongsTo
{
return $this->belongsTo(File::class, 'preview_id', 'id');
}

Eloquent many to many relationship is always empty

I know this question has been asked a lot but all answers didn't seem to work for me - or at least the questions I found were about the pivot table.
I have a many to many relationship (User - Appointment) which is joined by the pivot table "apointment_user", see migrations below.
Schema::create('appointment_user', function (Blueprint $table) {
$table->unsignedInteger('user_id')->nullable();
$table->foreign('user_id')->references('id')->on('users');
$table->unsignedInteger('appointment_id')->nullable();
$table->foreign('appointment_id')->references('id')->on('appointments');
$table->primary(['user_id','appointment_id']);
$table->timestamps();
});
Schema::create('appointments', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->dateTime('date');
$table->string('location');
$table->dateTime('departure');
$table->timestamps();
});
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->date('last_login')->nullable();
$table->rememberToken();
$table->timestamps();
$table->softDeletes();
});
class User extends Model {
protected $with = ['appointments'];
public function appointments() : BelongsToMany {
return $this->belongsToMany(Appointment::class);
}
}
class Appointment extends Model {
public function users() : BelongsToMany {
return $this->belongsToMany(User::class);
}
}
I have a user with the ID 1 and about 10 appointments, which I do attach to the relationship in a seeder. The pivot table has 10 records, as intended (User ID is always 1).
However, if I dump my User object using dd(User::find(1)), the relationship is always an empty collection. However, a 1:n relationship (between a role works well).
Does anybody see what I'm missing? Any help is appreciated.
Many thanks and kind regards
Edit
I just tried some other kind of dumping. I've simply returned my User-Object as JSON-response and there the relationship is filled with 10 appointments... strange.
Though it seems that your table and column names are as Laravel would guess, have you tried expliciting the names?
class User extends Model {
protected $with = ['appointments'];
public function appointments() : BelongsToMany {
return $this->belongsToMany(Appointment::class, 'appointment_user', 'user_id', 'appointment_id');
}
}
class Appointment extends Model {
public function users() : BelongsToMany {
return $this->belongsToMany(User::class, 'appointment_user', 'appointment_id', 'user_id');
}
}

Implementing relationship in models

I have two models: Dish and DishCategory. I decided to implement a "One to many" relationship.
Here's a migration for Dish model:
Schema::create('dishes', function (Blueprint $table) {
$table->increments('id');
$table->string('dish', 50);
$table->string('photo');
$table->double('price', 8, 2);
$table->integer('category_id');
$table->integer('type_id'); /* 1 - menu for delivery; 0 - general menu */
});
And a migration for DishCategory model:
Schema::create('dish_categories', function (Blueprint $table) {
$table->increments('id');
$table->string('category');
});
I've created a method called dish() in DishCategory model:
public function dish()
{
return $this->hasMany('App\Dish');
}
And dish_category() in Dish model:3
public function dish_category()
{
return $this->belongsTo('App\DishCategory', 'category_id');
}
I'm trying to set up a foreign key in my relationship, so it's been set up in dish_category() method as a second parameter of belongsTo(). But it doesn't work. What is the workaround?
Change the dish() relationship definition to:
public function dish()
{
return $this->hasMany('App\Dish', 'category_id');
}
And dish_category() is defined correctly.
If you also want to add a constraint, add this to the dishes table migration:
Schema::table('dishes', function (Blueprint $table) {
$table->foreign('category_id')->references('id')->on('dish_categories');
});

Can I create custom pivot table name using another M:M entity?

I'm still thinking is there a ways how can I create a custom pivot table name? Because I created a documents and users table has a many to many relationship with document_user which is my pivot table and this table was created for received document that user created. And I'm planning to create a another pivot table for document and user this table was for sent documents so I can have history. See my code below.
create_document_user_table
public function up()
{
Schema::create('document_user',function (Blueprint $table)
{
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->integer('document_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('document_id')->references('id')->on('documents')->onDelete('cascade');
$table->unsignedInteger('sender_id')->nullable();
$table->foreign('sender_id')->references('id')->on('users')->onDelete('cascade');
$table->dateTime('dateReceived')->default(DB::raw('CURRENT_TIMESTAMP'));
$table->timestamp('dateModified')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'));
});
}
documents_table
public function up()
{
Schema::create('documents', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->text('content');
$table->integer('category_id')->unsigned();
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
$table->timestamps();
});
}
users_table
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('first_name');
$table->string('last_name');
$table->string('middle_name');
$table->string('email');
$table->string('username');
$table->string('address');
$table->string('password');
$table->string('remember_token');
$table->integer('role_permission_id')->unsigned();
$table->foreign('role_permission_id')->references('id')->on('roles_permissions_dt')->onDelete('cascade');
$table->timestamps();
});
}
This works well inserting records to my pivot table. What I'm planning to achieve is every-time I inserted a records for documents this will inserted too in my custom pivot table not only in my document_user pivot table. Any help would appreciated! Thanks for your info or tips.
UPDATE
#Mina thanks for the tips that you given but actually this is my insert or save for my pivot table. How can I inserted this in my revisions table?
DocumentController
public function postDocuments(Request $request)
{
$this->validate($request,
[
'title' => 'required|regex:/(^[A-Za-z0-9 ]+$)+/|max:255',
'content' => 'required',
'category_id' => 'required',
'recipient_id' => 'required',
]);
$document = new Document();
//Request in the form
$document->title = $request->title;
$document->content = $request->content;
$document->category_id = $request->category_id;
$document->save();
$user = Auth::user();
foreach($request->recipient_id as $recipientId)
{
$document->recipients()->sync([ $recipientId => ['sender_id' => $user->id]],false );
}
return redirect()->back();
}
You can call your pivot tables as you like.
As mentioned previously, to determine the table name of the
relationship's joining table, Eloquent will join the two related model
names in alphabetical order. However, you are free to override this
convention. You may do so by passing a second argument to the
belongsToMany method:
return $this->belongsToMany('App\Role', 'user_roles');
(Eloquent: Relationships #Many To Many)
In your case you would need to define the relations like this:
class User extends Model
{
// other stuff
createdDocuments()
{
return $this->belongsToMany('App\Document', 'document_user_created');
}
sentDocuments() // or receivedDocuments()
{
return $this->belongsToMany('App\Document', 'document_user_sent');
}
// other stuff
}
class Document extends Model
{
// other stuff
createdByUsers()
{
return $this->belongsToMany('App\User', 'document_user_created');
}
sentToUsers() // or sentFromUsers() or whatever it does mean
{
return $this->belongsToMany('App\User', 'document_user_sent');
}
// other stuff
}
You do not need another pivot table. You need a table like that:
public function up()
{
Schema::create('revisions', function (Blueprint $table) {
$table->increments('id');
$table->integer('document_id')->unsigned();
//Rest of table structure
$table->foreign('document_id')->references('id')->on('document_user')->onDelete('cascade');
$table->timestamps();
});
}
When you need to create a new Revision:
$document = new Document;
//add $document attributes
$user->documents()->save($document);
$document->recipients->each(function($recipient){
$id = $recipient->pivot->id;
Revision::create(['document_id'=>$id]);
})

Laravel - model for table with only foreign keys (Pivot Table)

I need to implement model for table with only two foreign keys. In my db I have tables like this:
product (id_product, ...)
category_to_product (FK id_category, FK id_product)
category (id_category, ...)
How to manage this connections in Laravel? Should I implement model for merge table and how it may looks? category_to_product table does not represent entity(/model) and have only design-relation property.
Database Migrations
CategoryToProduct
Schema::create('category_to_product', function(Blueprint $table)
{
$table->integer('id_category')->unsigned();
$table->foreign('id_category')
->references('id_category')
->on('categories')
->onDelete('cascade');
$table->integer('id_product')->unsigned();
$table->foreign('id_product')
->references('id_product')
->on('products')
->onDelete('cascade');
});
Products
Schema::create('products', function(Blueprint $table)
{
$table->increments('id_product');
// ...
});
Categories
Schema::create('categories', function(Blueprint $table)
{
$table->increments('id_category');
// ...
});
#pc-shooter is right about creating methods.
But you still have to create the pivot table with your migration first
Schema::create('products', function(Blueprint $table)
{
$table->increments('id')
$table->string('name');
}
Schema::create('categories', function(Blueprint $table)
{
$table->increments('id')
$table->string('name');
}
Then your pivot table
Schema::create('category_product', function(Blueprint $table)
{
$table->integer('category_id')
$table->foreign('category_id')->references('id')->on('categories');
$table->integer('product_id');
$table->foreign('product_id')->references('id')->on('products');
// And finally, the indexes (Better perfs when fetching data on that pivot table)
$table->index(['category_id', 'product_id'])->unique(); // This index has to be unique
}
Do the following:
In the model Category:
public function products(){
return $this->belongsToMany('Category');
}
In the model Product:
public function categories(){
return $this->belongsToMany('Category', 'category_to_product');
}
In the model CategoryToProduct:
public function categories() {
return $this->belongsTo('Category');
}
public function products() {
return $this->belongsTo('Product');
}
Note the naming of these methods!
Those are the same as the DB-table names. See ChainList's
answer.

Categories