How to setup laravel hasOne relationships with itself? - php

I am working on an application in Laravel where users can be matched up with one users. I am trying to figure out how to setup the hasOne relationship between users.
Here is how I have done it so far. Users when they are created have a match_id which represents the user that they are matched to. When they are matched this record gets updated. Here is my table:
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('event_id');
$table->foreign('event_id')->references('id')->on('events')->onDelete('cascade');
$table->unsignedBigInteger('match_id')->nullable();
$table->foreign('match_id')->references('id')->on('users')->onDelete('cascade');
$table->string('name');
$table->string('email');
$table->timestamps();
});
In the user model I have set up this relationship:
public function match() {
return $this->hasOne(User::class);
}
but as of right now I cannot do something like this and get the expected matched user's information:
$user->match()->get();
After reading the docs here, I see that in such situations you can setup the foreign key and local key. Nevertheless I am having a difficult time figuring this out.
Could someone give me some guidance on how I could do this? I am also open to any suggestions on different implementations.
-----edit------
after discussing with some people in the comments I made some progress in my endeavors. I am able to get some results and have experimented by logging the matches. For whatever reason though I am getting the wrong matches. Look below for more information:
in my controller I log the users name and match after it has been formed:
Log::error($user->match);
Log::error('has matched with:');
Log::error($user);
Log::error('participant:');
in the database here are the users and their matches:
yet in the logs, the match is not the same:

Since the match_id is on the users table, the relationship should be belongsTo, not hasOne.
belongsTo is specified on the model that contains the foreign key.
Also, you should specify the foreign key:
return $this->belongsTo('App\User', 'match_id');

Related

I have two pivot tables but I want to insert into one when I run a function, how do I do this?

So, as the title says, I have two pivot tables in my Laravel project (I am still new to Laravel). My project is an Instagram clone and I have made the like function, which uses one of the two pivot tables I made and now I'm trying to make a "Save" function, similar to the actual Instagram app where people can save posts and view them at a later time. The problem is, when I check my tinker and do:
As you can see, it looks like the likes and saves are using the pivot table for likes, I have not inserted anything into the pivot table for saves yet. Here are my migrations
For likes:
Schema::create('post_user', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('post_id');
$table->timestamps();
});
And for saves:
Schema::create('post_user_saves', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('post_id');
$table->timestamps();
});
I'm aware that they are literally the same but, just like the Instagram app, a user can like a post without saving it and vice-versa and they can also like and save it at the same time. I want to insert into the post_user_saves when I click a button. Here are pics of my database as well, as you can see the post_user_saves table is empty while post_user is not, meaning that tinker is getting the pivot data from post_user
and lastly, here are the codes from my model:
public function likes()
{
return $this->belongsToMany(Post::class);
}
public function saves()
{
return $this->belongsToMany(Post::class);
}
EDIT:
here is my store method for the likes:
public function store(Post $post){
return auth()->user()->likes()->toggle($post->id);
}
I don't have the store method for my saves yet but my plan is to make it the same as the one above hence why I need to be able to store to post_user_saves.
If your relationships are correct you can use attach() to save to the pivot:
auth()->user()->likes()->attach(auth::id(), ['column' => 'value']);
The reason your save is updating the same table is because you are pretty much using the exact same relationship so eloquent doesn't know about your post_user_saves table what you would need to do is Create a model Save() then rename your migration to save_users and update your relationship accordingly.

How to use two foreignId in Laravel

I have two tables like:
User:
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('loginid')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
IP:
Schema::create('i_p_s', function (Blueprint $table) {
$table->id();
$table->string('address')->unique();
$table->foreignId('user_id')->nullable();
$table->string('hostname');
$table->string('description')->nullable();
$table->timestamps();
$table->index('user_id');
});
IP Model:
public function User() {
return $this->belongsTo(User::class);
}
User Model:
public function IPs() {
return $this->hasMany(IP::class);
}
The user_id column means this IP is using by which user.
And now I want to add a new column last_modified which means who is the last editor of this row.
So I think the last_modified should be $table->foreignId('user_id')->nullable(); too.
But how to define the relationship in IP model?
Additionally, I call the user_id like this now.
$ips = IP::with('user')->get();
#foreach ($ips as $ip)
{{ $ip->user }}
#endforeach
So how can I call the last_modified after the definition?
Thanks a lot
As shown in the docs (https://laravel.com/docs/7.x/migrations#foreign-key-constraints),
$table->foreignId('user_id')->nullable();
is just a shortcut of the "old" way
Schema::table('i_p_s', function (Blueprint $table) {
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
});
The problem with your code would be, that you need also the constrained()-method. It will dissolve a given column name like user_id into like "Laravel, please use the column id of the table users here".
I'm not sure if the nullable()-method will be useable for this shortcut.
In the same way, your relations will be dissolved within your models. If you're not adding additional values to the belongsTo() and haveMany()-methods, Laravel will try to find its way through your databse by assuming standard naming conventions for primary keys and table names (if the table names are not set within your model).
primary keys are assumed as id. This is also the reason why $table->ip() works.
table names are assumed as the plural of the model name. That means also, you have to make sure to set the name of your i_p_s table within your IP-model as it does not follow the convention. Event better would be to think about an adaption to the convention and call your table ips.
foreign keys should be (to be able to dissolve things that way) named by the singular table name, underscore, primary key. in other words user_id.
So, your assumption should be right apart from the fact that you cannot add a second column called user_id. For your second foreign key, your code should look like the "normal/ traditional" way of doing this.
Schema::table('i_p_s', function (Blueprint $table) {
$table->unsignedBigInteger('last_modified')->nullable();
$table->foreign('last_modified')->references('id')->on('users');
});
I'm pretty sure that this will work, although I didn't tested this yet. Unfortunately, I'm not sure if you can provide the column name and table also within the constrained method. If so, that would be pretty handy. Give it a try, otherwise use the traditional way.
The relation within the model should then look like this:
public function hasChanged() {
$this->hasMany(IP::class, 'last_modified', 'id');
}
last_modified is the foreign key within the i_p_s-table while id is the local column of your owning User-model.
Bringing this into reverse for the IP-model:
public function wasChangedBy() {
$this->belongsTo(User::class, 'last_modified', 'id');
}
In both cases you can dispense on setting the 'id' column as primary key of your users-table as it is standard.
The relations are the same as in your example because of the construction/ architecture. In 99% this is always a one-to-many relation.
Last but not least, it was a bit strange to see this construction of the same foreign key two times referencing in the same table. But I found this post, which says it is eventually totally normal to do so.
Mysql: using two foreign keys to the same table
The only other way I could think of would be to have an intermediate table between i_p_s and users but this would lead to a loop in your database between these two tables which also is a thing you should avoid. So I would say this is ok.
Hope this helps ;)

Laravel Making Acl Users with permissions(without ranks/groups)

I want to create users which I can grant permissions.
I created a permissions model, which contains following attributes (id|name|displayName|desc)
1|xyz.edit|Edit xyz| Allow to edit xyz
2|xyz.create|Create xyz| Allow to create xyz
Thus I want to create relations like following:
public function getPermissions(){
return $this->hasMany('App\Permission');
}
But it does not work. Is there any way to create relations like
user has a lot of permissions but without creating same permissions for user?
I can make user model like id|pass|login|...|permissions
and in permissions storage permissions id splited with "," and in the getPermissions() function make something like this:
public function getPerms(){
foreach (explode($this->permssions,',')as $id ){
//here load from database perm by id add to array and return
}
}
Or a second option I see in this tutorial https://www.youtube.com/watch?v=Kas2w2DBuFg is to make another table like user_perms with fields
id|user_id|perms|id
but what option is the best to do this?
Can you post the code in both your models? (the user model and permission model?) without seeing that, I can't see what type of relationship you're using, although it looks like you're using a one-to-many.
Either way...
The way you can have users an assign them permissions, without worrying about groups is by using a many-to-many relationship. This requires 3 tables. The user table, the permission table, and a pivot table.
You can read about many-to-many relationships here: https://laravel.com/docs/5.5/eloquent-relationships#many-to-many
But to give you a rundown...
User model
public function permissions(){
return $this->belongsToMany('App\Permission');
}
Permission model
public function users(){
return $this->belongsToMany('App\User');
}
create_users_table migration (field names don't really matter, just make sure you have the increments() one)
$table->increments('id');
$table->string('name');
(etc, standard migration stuff)
create_permissions_table migration (field names don't really matter, just make sure you have the increments() one)
$table->increments('id');
$table->string('name');
$table->string('long_name');
(etc, standard migration stuff)
and for the pivot table, you need to use the singular name of the two tables in alphabetical order (or at least, thats the default)
create_permission_user_table (these two field names are important, laravel expects these names, you don't need any other fields... you can also setup foreign key relationships if you want to be thorough)
$table->integer('permission_id');
$table->integer('user_id');
and then to give a user a permission, you would just do
// Grab your user and permission, however you do that...
$permission = \App\Permission::first();
$user = \App\User::first();
// Then save the permission to the user (or the other way around, but it makes more sense this way)
$user->permissions()->save($permission);
And that will let you have users with permissions :)
then you can access an array of permissions with $user->permissions and do whatever logic checks you need to check if they are allowed to do things!

Create or Update with Laravel

Im making a site where users can post and read each others posts.
I want to make it possible for all users to mark the posts they have read, thus showing only posts which are not read. It should also be possible to undo this. So I have a model and table for both Users and Posts.
The way I thought of doing it was creating a PostsRead table which would contain the unique id for the user and for the post. Then I could fetch all the posts and remove the ones from the PostsRead table. So the question(s) is:
1) Is this a good (best practice-ish) way to do it?
2) How? I guess I need to check if there is a record in the PostsRead-table containing the user_id/post_id pair. If it is not, insert a new row. And if it is, I guess I need to update the current row. It should maybe have a boolean field telling if the post is read or not. How would the code for this be?
When I go through it in my head, this approach seems....weird. Am I on the wrong track here? I hope the question was understandable.
Yes, it's the best practice to do it this way.
Create a table called 'user_read' with 'user_id' and 'post_id' (both as primary key)
Schema::create('user_read', function (Blueprint $table) {
$table->integer('user_id')->unsigned()->index();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->integer('post_id')->unsigned()->index();
$table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
$table->timestamps();
$table->primary(['user_id', 'post_id']);
});
To set it up you should have this relation in your model.
User.php
public function reads()
{
return $this->belongsToMany(Post::class, 'user_read', 'user_id', 'post_id')
->withTimestamps();
}
To store a new post that the user read you can just do
//1 and 3 are the posts ids
$user->reads()->sync([1, 3]);

Laravel 5 Eloquent Many to Many 2ndary table

I have a question that I'm not sure how to solve or even phrase for finding an answer.
I have a Company Model & User Model that are related Many-to-Many.
I want to have a user_pins table. A user can belong to multiple companies and therefore have a different pin within each company. The pin is unique within a company, no two users within a company can have the same one. Users in different companies can have the same one.
So for the company it is One to Many, for the user it is One to Many, but altogether it is many to many, Im not sure if that makes sense.
I have the table set up as
Schema::create('user_pins', function (Blueprint $table) {
$table->integer('user_id')->unsigned();
$table->integer('company_id')->unsigned();
$table->string('pin');
$table->foreign('user_id')->references('id')->on('users')->onUpdate('cascade')->onDelete('cascade');
$table->foreign('company_id')->references('id')->on('companies')->onUpdate('cascade')->onDelete('cascade');
$table->primary(['user_id', 'company_id', 'pin']);
});
How do I relate this table in the models and use Eloquent to access/create/update it so it stores both the user and company?
Firstly, I would change the name to company_user so that it follows the same naming convention that Laravel would use out of the box. (you wouldn't have to do this as you can specify the pivot table name in the relationship but if there isn't a reason to stick with user_pin it makes sence to follow convention :) )
Then I would remove the primary key from being a compound of all 3 fields and just have it on the company_id and user_id.
Lastly, as a PIN only has to be unique for a company, I would just put the unique index on those two columns e.g.
Schema::create('company_user', function (Blueprint $table) {
$table->integer('company_id')->unsigned()->index();
$table->integer('user_id')->unsigned()->index();
$table->string('pin');
$table->foreign('user_id')->references('id')->on('users')->onUpdate('cascade')->onDelete('cascade');
$table->foreign('company_id')->references('id')->on('companies')->onUpdate('cascade')->onDelete('cascade');
$table->primary(['company_id', 'user_id']);
$table->unique(['company_id', 'pin']);
});
Then for the relationship in the model I would have something like:
return $this->belongsToMany('App\Company')->withPivot('pin');
and
return $this->belongsToMany('App\User')->withPivot('pin');
Examples of use with pivot
All user pins for a company:
$company->users->lists('pivot.pin');
Users pin for a specific company
$user->companies()->where('id', $id)->get()->pivot->pin;
Users pin for the first company relationship:
$user->companies->first()->pivot->pin;
Hope this helps!

Categories