Laravel Many-to-Many Relationship Field Names - laravel

I have a users table and a permissions table. One user can have many permissions, and one permission can have many users:
USER ID | PERMISSION ID
1 | 1
1 | 2
2 | 1
2 | 1
There is a linking table called permission_user as defined by the Laravel spec for auto-inferring these tables.
If I define the following functions:
User Model:
public function Permissions()
{
return $this->belongsToMany('App\Permission');
}
Permission Model:
public function Users()
{
return $this->belongsToMany('App\User');
}
I get an error when calling App\User::first()->Permissions()->attach(App\Permission::first()); that says
Illuminate\Database\QueryException : SQLSTATE[42S22]: Column not found: 1054 Unknown column '' in 'field list' (SQL: insert into `permission_user` (``, `user_id`) values (3, 1))
The database migration file:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Carbon\Carbon;
use App\User;
use App\Permission;
use App\Http\Resources\User as UserResource;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('permissions', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->timestamps();
});
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('username')->unique();
$table->string('name')->unique();
$table->string('email')->unique();
$table->boolean('verified');
$table->timestamps();
});
Schema::create('permission_user', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('permission_id');
$table->unsignedBigInteger('user_id');
$table->timestamps();
$table->foreign('permission_id')->references('id')->on('permissions');
$table->foreign('user_id')->references('id')->on('users');
});
$this->add_permission('View Projects');
$this->add_permission('Edit Projects');
$this->add_permission('View Users');
$this->add_permission('Edit Users');
$user = new User();
$user->name = 'John Smith';
$user->email = 'john#smith.com';
$user->username = 'jsmith';
$user->verified = 1;
$user->save();
$user->permissions()->attach(Permission::where('name','View Users')->first()->id); // -> This line it can't tell that permission_user.permission_id is where the permission.id field goes;
$perms = $user->permissions()->get();
foreach($perms as $perm)
{
echo $perm->name . '\n';
}
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
Schema::dropIfExists('permission_user');
Schema::dropIfExists('project_user');
Schema::dropIfExists('permissions');
Schema::dropIfExists('deployment_log');
Schema::dropIfExists('branches');
Schema::dropIfExists('projects');
Schema::dropIfExists('users');
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
}
private function add_permission($permission_name)
{
DB::table('permissions')->insert(
array(
'name' => $permission_name,
)
);
}
}
It appears Laravel(5.8) is unable to disscern from the linking table that permission_id is the field for the foreign reference to the permission.id field even though the database migration reflects that user_id is a foreign reference to user.id and permission_id is a foreign reference to permission.id.
I can solve this by specifying the linking table name, field name, and foreign key name in the belongsToMany function, however, Laravel's own documentation states that this isn't needed when tables and fields are named appropriately, which mine are. Is this a bug in Laravel? Do I need to change the name of the permission_user.permission_id field? How do I solve this without having to specify these names in my models as it's time consuming and not needed according to Laravel(5.8)'s documentation.

According to laravel docs:
[...] Many users may have the role of "Admin". 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.
The linking table must contain only the foreign keys from each model. Otherwise, you need to specify which relationship table you are using and the primary key for each model of the relation, as specified on laravel documentation.
As i said in the comments section, if you create your permission_user table with only permission_id and user_id columns and with this columns as primary keys, it will work as expected:
Schema::create('permission_user', function (Blueprint $table) {
$table->unsignedBigInteger('permission_id');
$table->unsignedBigInteger('user_id');
$table->foreign('permission_id')->references('id')->on('permissions');
$table->foreign('user_id')->references('id')->on('users');
$table->primary(['permission_id', 'user_id']);
});
Here is a package that i have developed to handle user permissions and you can check the user_has_permissions table definition, which is, basically, a table that does exactly what your permission_user table does, by clicking this link.
Hope it helps.

Related

Laravel 8 - Foreign key constraint is incorrectly formed

I don't know what's wrong because I'm very new to this.
// Product Model
class Product extends Model
{
use HasFactory;
public function store()
{
return $this->belongsTo(Store::class);
}
}
// Store Model
class Store extends Model
{
use HasFactory;
public function products()
{
return $this->hasMany(Product::class);
}
}
// Products table migration
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->float('price');
$table->string('description');
$table->timestamps();
$table->foreignId('store_id')->constrained()->onDelete('cascade');
});
// Stores table migration
Schema::create('stores', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('image_url');
$table->string('phone');
$table->timestamps();
});
When I run the migration, it gives me this error
I've tried changing the data type of the 'id' but still not working. I've also tried with
$table->foreign('store_id')->references('id')->on('stores')->onDelete('cascade');
but still not working.
What I want is a relation so that when I delete a store, all products that belong the store are also deleted.
Thanks 🙏
Change the name of the stores migration file to a date prior to 2021-07-28 so the table stores is migrated before the table products
Example: 2021_07_27_004700_create_stores_table
Laravel uses the name of the migration files for the order of migration. With the format of the date as the start of the file name, it is dependant on the date of the creation of the file.

Laravel ManyToMany relationship Is reversing tables names

i need to make the post accepts as many language as i need so i have tow models post and language
in post model:
public function languages(){
return $this->belongsToMany(\App\Models\Language::class);
}
in my language model :
public function posts()
{
return $this->belongsToMany(\App\Models\Post::class);
}
post migration :
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->boolean('puplished')->default(false);
$table->bigInteger('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');
$table->timestamps();
});
language migration :
Schema::create('languages', function (Blueprint $table) {
$table->id();
$table->string('text')->unique();
$table->string('value');
$table->timestamps();
});
i also created post_language_table migration to connect post and language:
Schema::create('post_language', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('post_id');
$table->unsignedInteger('language_id');
$table->timestamps();
});
in my controller :
$langs = collect(json_decode($request->languages,true))->pluck('id');
$post = [];
$post['body'] = $request->body;
$post['title'] = $request->title;
$post['user_id'] = 1;
$new_post = \App\Models\Post::create($post);
$new_post->languages()->attach($langs);
but when i try to insert new record to tha database this error shows:
Illuminate\Database\QueryException
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'laravel.language_post' doesn't exist (SQL:
insert into `language_post` (`language_id`, `post_id`) values (1, 4), (3, 4), (4, 4))
so the problem is that for some reason the table name is exchanged !
The Laravel convention for pivot tables based on models is table1_table2, where table1 is the lowercase, singular version of the model that comes first in the alphabet, and table2 is the lowercase, singular version of the model that comes last in the alphabet. So in your case, the pivot table should be language_post, since L comes before P.
You can modify the pivot table in your migration:
Schema::create('language_post', ...)
Or override it on the relationship:
Post.php:
public function languages() {
return $this->belongsToMany(Language::class, 'post_language');
}
Language.php
public function posts() {
return $this->belongsToMany(Post::class, 'post_language');
}
The 2nd param passed to belongsToMany is the name of the pivot table.
From the documentation:
To determine the table name of the relationship's intermediate table, Eloquent will join the two related model names in alphabetical order.
https://laravel.com/docs/8.x/eloquent-relationships#many-to-many

laravel eloquent relationships between 3 model

actually i have two kind of users which has two different table (user and seller table).
i have comment table with this fields:
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned()->nullable();
$table->foreign('user_id')->references('id')->on('users');
$table->integer('parent_id')->unsigned()->default(0);
$table->text('comment_text');
$table->integer('commentable_id')->unsigned();
$table->string('commentable_type');
$table->timestamps();
});
how can I add seller_id to this table? if seller wants to response to a user comment.
same issue for message table.
Actually the good practice is you must add a role field in the user table that determines the user is a user or seller. But if you want to keep your table like that you don't need to add seller_id, just use one to many polymorphic relations. Change your comments table schema like this :
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->integer('parent_id')->unsigned()->default(0);
$table->text('comment_text');
$table->integer('commentable_id')->unsigned();
$table->string('commentable_type');
$table->timestamps();
});
Then in the user and seller model, you must add the relationship method like this :
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
And in the comment model like this :
public function commentable()
{
return $this->morphTo();
}
Then you can get the seller comment like this :
$seller = App\Seller::find(1);
$seller->comments;
And to save the comment from the seller you can use this :
$seller = App\Seller::find(1);
$comment = $seller->comments()->create([
'comment_text' => 'A new comment.',
// Add other field
]);

Relationship to users table without using the standard 'user_id' column name

I have 2 tables, one has different columns to record different users names based on authorisation level. but i would like to link to two together. at the moment i have tried the following:
User.php
public function approvals()
{
return $this->hasMany(Approval::class);
}
Approval.php
public function qs() {
return $this->belongsTo(User::class, 'id', 'qs');
}
index.blade.php
<td>{{ $approval->qs->name }}</td>
approvals db structure
Schema::create('approvals', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('project_id');
$table->integer('stage');
$table->unsignedBigInteger('qs')->nullable();
$table->unsignedBigInteger('pm')->nullable();
$table->unsignedBigInteger('rcm')->nullable();
$table->unsignedBigInteger('doc')->nullable();
$table->unsignedBigInteger('vpoc')->nullable();
$table->unsignedBigInteger('vpof')->nullable();
$table->timestamps();
});
users db structure
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email', 100)->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Am i going about this all wrong, the qs table column needs to be linking to the users.id?
It seems qs is the user id of the User model. So the relation to the Approval model is
public function qs()
{
return $this->belongsTo(User::class, 'qs');
}
And in User model
public function approvals()
{
return $this->hasMany(Approval::class, 'qs');
}
Now you can use
{{ $approval->qs->name }}
Eloquent determines the default foreign key name by examining the name of the relationship method and suffixing the method name with a _ followed by the name of the primary key column. However, if the foreign key on the Model is not parent_id, you may pass a custom key name as the second argument to the belongsTo method.
Laravel Documentation
If a parent model does not use id as its primary key, or you want to join the child model to a different column, you may pass a third argument to the belongsTo method:
public function qs() {
return $this->belongsTo(User::class, 'foreign_key_here_from_child_table', 'custom_column_from_parent_table');
}

Laravel hasOne (one to one relationship)

I am trying to learn Laravel-> one to one relationship.
In given code link(join) should be dependent on name(user2s table) and title(post2s table) but the link(join) is dependent on my_id(user2s table) and title(post2s table)
My full codes
Migrations:-
user2s table
Schema::create('user2s', function (Blueprint $table) {
$table->increments('my_id');
$table->string('name');
$table->string('email');
$table->string('password');
$table->string('remember_token');
$table->timestamps();
});
post2u table:
Schema::create('post2s', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->string('title');
$table->text('content');
$table->timestamps();
$table->tinyInteger('is_admin');
});
Model User2
protected $primaryKey = 'my_id';
public function postx(){
return $this->hasOne(Post2::class, 'title', 'name');
}
My Route Code
Route::get('user/{id}/post', function($id){
return User2::find($id)->postx;
});
http://localhost:8000/user/abc/post
error: Trying to get property of non-object
user2s table
post2s table
Let me explain what is your issue.
error: Trying to get property of non-object, it means it can't find the result. The result object is null, so when you looking for null->postx, it can't get them anything.
You search for User2::find($id), when you use find(), it is looking for primary key. And you User2 Model primary key is my_id, and you are looking for Post2->title. It not able to find it.
More infor about find()
https://laravel.com/docs/5.5/eloquent#retrieving-single-models
Since you are looking for the Post2 title. And you are referencing from User2. It is not correct.
What you should do is
In your route.php
Route::get('user/{title}/post', function($title){
//return Post2::all();
$post = Post2::with('userx')->where('title', $title)->first();
dump($post);
dump($post->userx)//<- you can get user info via
});
In Post2 Model.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post2 extends Model
{
public function userx(){
return $this->belongsTo(User2::class, 'user_id');
}
}

Resources