I'm having some trouble using Eloquent models and not sure if I am missing something. I have a User model and a Role model with a many to many relationship set up in Laravel 4...
// Find a user with their roles (none at the moment)
$user = User::with('roles')->find(2);
var_dump($user->toJson());
Notice the empty roles array, which is expected
{
"id":"2","username":"Test","email":"test#example.com","remember_token":"",
"roles":[]
}
Next, I attach a role to the user and show the user again...
$user->roles()->attach($role);
var_dump($user->toJson());
Now we have attached a role to the user, but the roles array is still empty
{
"id":"2","username":"Test","email":"test#example.com","remember_token":"",
"roles":[]
}
If I try and fetch the user again, the roles show up fine, so the DB is being updated correctly...
$user = User::with('roles')->find(2);
var_dump($user->toJson());
{
"id":"2","username":"Test","email":"test#example.com","remember_token":"",
"roles":[{"id":"1","name":"Admin","pivot":{"user_id":"2","role_id":"1"}}]
}
My Question: How come after I attach a role to my user model, it does not show up in the model until I reload it from the DB. I would have expected it to be reflected in the in-memory version of the model as well.
Since user has many role, you need a query to get all roles.
For example, if you attach new roles to a user, and that user has already another roles, then there will be a confusion.
You may try to set an attribute to your model after calling attach method, to do this please read the last thing on the Laravel Eloquent doc
protected $appends = array('local_roles');
Edit
Even if you pass a model instead of id (with save method), it will not be added to the collection, and I find it more logical. Because as I already said, it may be a difference between the database and the model. Let me explain more clearly:
Role table
-------------
| id| name |
-------------
| 1 | admin |
-------------
| 2 | root |
-------------
role_user table
-----------------
|role_id|user_id|
-----------------
| 1 | 1 |
-----------------
| 2 | 1 |
-----------------
user table
-----------
|id| name |
-----------
| 1| razor|
-----------
Now, suppose the attach(or save) method can add the model to the collection.If you do
$user = User::find(2); // without "with('roles')"
//Then you add a new role
$role = new Role;
$role->name = 'another_role';
$user->roles()->save($role);
Then, you will have in your model
"roles":[{"id":"3","name":"another_role","pivot":{"user_id":"1","role_id":"3"}}]
witch contradicts the results from the database. This problem does not appear in belongs_to and has_one relations, because there is only one model associated and even if you don't use with method, you get the same result. But in many relations(belongstomany,hasmany,hasmanythrough), the result will be depend on the use of the with method.
Related
I use laratrust as my user role, in the user role there are 1 = superadministrator, 2 = administrator, 3 = perusahaan, then I want to display the names of existing perusahaan in my view, which means calling user = role id 3, then how to call it? I also don't really understand eloquent relationships
You need to get users in this way
// This will return the users with 'superadministrator' role.
$users = User::whereRoleIs('superadministrator')->get();
check the documentation
I have 3 tables. The user permission table indicates which user has which permission.
User table
id | name
1 | jack
2 | bob
Permission table
id | name
1 | manage users
2 | manage jobs
User Permission table
id | user_id | permission_id
1 | 1 | 1
2 | 1 | 2
3 | 2 | 1
User model
public function userPermission()
{
return $this->hasMany(UserPermission::class);
}
UserPermission model
public function user()
{
return $this->belongsTo(User::class);
}
If I do auth()->user()->userPermission, I get all the data from the permission table for that user. Is there a way so that instead of getting the id of the permission table, I get all the permission name instead ? Maybe in an array. So user jack will have ['manage users','manage jobs']
I saw that User and Permission have "many to many" relationships.
https://laravel.com/docs/8.x/eloquent-relationships#many-to-many
Make sure that the name of User Permission table is "permission_user", and In User model, you should define the relation method like the following :
public function permission() {
return $this->belongsToMany(Permission::class);
}
So when you call auth()->user()->permission, you can get a collection of Permission Class belong to user.
If you want to get the name of all permissions belong to an user. Just use the pluck() collection method.
Link: https://laravel.com/docs/8.x/collections#method-pluck
You should first use foreign and local keys in your relations.
Also take a look at Many to Many relations and pivot tables on Laravel Doc.
I have a new Laravel 5.8 application. I started playing with the Eloquent ORM and its relationships.
There is a problem right away that I encountered.
I have the following tables. (this is just an example, for testing reasons, not going to be an actual application)
Login table:
--------------------------
| id | user | data_id |
--------------------------
| 1 | admin | 1 |
| 2 | admin | 2 |
| 3 | admin | 3 |
--------------------------
Data table:
--------------
| id | ip_id |
--------------
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
--------------
IP table:
----------------------
| id | ip |
----------------------
| 1 | 192.168.1.1 |
| 2 | 192.168.1.2 |
| 3 | 192.168.1.3 |
----------------------
What I wanted is to get the IP belonging to the actual login.
So I added a hasOne relationship to the Login table that has a foreign key for the Data table:
public function data()
{
return $this->hasOne('App\Models\Data');
}
Then I added a hasOne relationship to the Data table that has a foreign key for the IP table:
public function ip()
{
return $this->hasOne('App\Models\Ip');
}
Once I was done, I wanted to retrieve the IP address for the first record of the Login table:
Login::find(1)->data()->ip()->get();
But I get this error:
Call to undefined method Illuminate\Database\Eloquent\Relations\HasOne::ip()
What am I missing here and how can I get the IP of that login in the correct way? Do I need a belongsTo somewhere?
1st error: Wrong relationship definition
Laravel relationships are bi-directional. On one-to-one relationships, you can define the direct relationship (HasOne) and the inverse relationship (BelongsTo)
The direct relationship should be:
HasOne HasOne
[ Login ] <----------- [ Data ] <----------- [ IP ]
And the inverse relationship should be:
BelongsTo BelongsTo
[ Login ] -----------> [ Data ] -----------> [ IP ]
See Eloquent: Relationships - One-to-One docs for details on how defining it.
Note that you don't need to define both directions for a relationship unless you need it. In your case, I think you just need to define the belongsTo direction.
2nd error: You are calling the relationship method, not the relationship itself
When you do:
Login::find(1)->data()->ip()->get();
You are calling the method data that defines your relationship, not the related model. This is useful in some cases, but not on your case.
The correct is call the relationship magic property instead:
Login::find(1)->data->ip;
Note that we don't use the () and we do not need the get() here. Laravel takes care of loading it for us.
Use Eager Loading
Laravel Eloquent have a Eager Loading for relationships that's very useful in some cases because it pre-loads your relationships, and reduce the quantity of queries you do.
In the situation that you described (loading a single Login model) it doesn't make any performance improvement, but also it doesn't slow down.
It's useful when you load many models, so it reduces your database query count from N+1 to 2.
Imagine that you are loading 100 Login models, without eager loading, you will do 1 query to get your Login models, 100 queries to get your Data models, and more 100 queries to get your Ip models.
With eager loading, it will do only 3 queries, causing a big performance increase.
With your database structure:
Login belongsTo Data
Data hasOne Login
Data belongsTo IP
IP hasOne Data
After fixed your methods you can use of your relations like this
$login = Login::with(['data.ip'])->find(1);
You can try like this:
Login
public function data()
{
return $this->belongsTo('App\Models\Data', 'data_id');
}
Data
public function ip()
{
return $this->belongsTo('App\Models\Ip', 'ip_id');
}
$login = Login::with(['data.ip'])->find(1);
And inside data you will have ip like $login->data->ip.
This question is best illustrated by an example:
users
id
name
roles
id
name
role_user
user_id
role_id
rank_id
group_id
...
ranks
id
name
groups
id
name
I can easily eager load a users table by specifying the following relationship in my User.php model:
public function roles() {
return $this->belongsToMany('Role');
}
Which will output the table below when calling User::with('roles'):
User | Role
-------------
Jon | Admin
Jan | Mod
However I have no idea how to extend this to include:
User | Role | Rank | Group
-----------------------------
Jon | Admin | Boss | Blue
Jan | Mod | Minion | Red
What I've tried doing User::with('roles', 'ranks', 'groups') but that is certainly wrong since I'm telling Laravel there are rank_user and group_user intermediate tables too but there aren't. What is the correct way?
PS: I know it's better to separate the ranks and groups into their own relationship/pivot tables, this is simply an example.
EDIT: Closest example I can find for this: https://github.com/laravel/framework/issues/2619#issuecomment-38015154
You can just treat your model's relations methods as ordinary queries and build upon them:
public function roles() {
return $this->belongsToMany('Role')
->join('role_user', 'role_user.role_id', '=', 'roles.id')
->join('ranks', 'ranks.id', '=', 'role_user.rank_id')
->join('groups', 'groups.id', '=', 'role_user.group_id');
}
Relations queries like the above are not so intuitive to understand when they get too complex, so it may be better to rethink database design, but in theory it's possible to manipulate them.
I have the following db table set up
+--------------+ +--------------+ +-----------------------+
| users | | clients | | user_clients |
+--------------+ +--------------+ +----------------------+
| id | | id | | usersid |
| name | | name | | clientid |
| authid | | email | +----------------------+
| (plus) | | (plus) |
+-------------+ +-------------+
I have set up the a relationship table [b]user_clients[/b] with foreign keys to the relevant db, so userid is link to users->id and clientid is linked to clients->id.
Dependant on the Users Authid is how many clients are linked:
Authid 1: User can only have one client associated to them.
Authid 2: User can only have one to many clients associated to them
Authid 3: User has access to ALL clients.
So as i am new to this relationship side of laravel currently i would do a lot of querying to get some details eg:
I would done something like:
$userClient =UsersClients::select('clientid')->where('userid','=',$userid)->get();
Then I would probably loop through the result to then get each client details and output to the page.
foreach($userClient as $i ->$cleint){
echo '<div>' .$cleint->name . '</div>';
........
}
Would this be an old way and could it be handled better??
----------------EDIT---------------
i have managed to sort it as the following:
User Model:
public function clients() {
return $this->hasMany('UsersClients','userid');
}
User Controller
$selectedUserClients = User::find(24)->clients;
I get the same out come as my previous result as in client id's 1 & 2, but now how to get the client details from the actual client db basically is there an easier way that the following:
foreach ($selectedUserClients as $key => $client) {
$clientInfo = Client::select('id','clientname')->where('id','=',$client->clientid)->get();
echo $clientInfo[0]->clientname;
}
The users_clients table needs it's own ID column in order for many-to-many relationships to work.
On your User Model, try
public function clients()
{
return $this->belongsToMany('Client','user_clients','userid','clientid');
}
Now you can find the clients assigned to each individual user with
User::find(24)->clients.
You could also do the inverse on your Client model...
public function users()
{
return $this->belongsToMany('User','user_clients','clientid','userid');
}
This would allow you to find all the users belonging to each client
Client::find(42)->users;
I would also like to mention that it's best practice to use snake case for your id's such as user_id or client_id.
Your table names should be plural. users and clients.
Your pivot table should be snake_case, in alphabetical order, and singular. client_user.
This would make working with Eloquent much easier because it's less you have to worry about when setting up the relationships and it might be easier for someone else to help you work on your project.
Instead of return $this->belongsToMany('Client','user_clients','userid','clientid'); all you'd have to do is return $this->belongsToMany('Client'); which should keep your app much cleaner and easier to read.