How to chain eloquent relations in laravel? - php

So far I was extracting the relation objects as arrays and then doing something like:
App\Model::find($id)
But however is there a way to do something like:
Auth::user()->group()->members()
It works until Auth::user()->group but no further chaining. Please help if you've done something. Or I'm just newbie.

You could use eager loading to load the user's group and then load all of the members of that group.
$user = User::with(['group', 'group.members'])->find(1);
// OR if you already have a user object (Like when using the Auth facade)
$user = Auth::user()->load(['group', 'group.members']);
foreach ($user->group->members as $member) {
// Do something with a member
}
However, if you essentially want to jump down the structure a level, and get all the members related to a user, you could use the hasManyThrough relationship, in that a user has many members, through a group.
// In your User model
public function members()
{
return $this->hasManyThrough(Member::class, Group::class);
}
That way you can simply access the members directly through the user:
$members = Auth::user()->members;
Instead of doing a query to access the user's group and then doing another query to access that group's members, Laravel would use a single query with a join to get you the members.
Take a look at the hasManyThrough relationship here

Try this
Auth::user()->group->members

Related

Laravel: get a model through another table

I have gotten a model called User. in this model i have a function called UserActivity where i return this:
return $this->hasMany('App\UserActivity', 'userid');
After this i have a function that gets the activity name from the Activity table, but i do not know how to do this, i have currently gotten this:
public function Activity() {
return $this->hasManyThrough('App\UserActivity', 'App\Activity', 'userid', 'activityid');
}
And in my view i want to use this like this:
$activiteiten = \App\User::find(Auth::user()->id);
dd($activiteiten->UserActivity()->Activity());
But then i get an error saying this:
Call to undefined method Illuminate\Database\Query\Builder::Activity() (View: /var/www/vhosts/cpned.nl/intranet.cpned.nl/laravel/resources/views/dashboard.blade.php)
I can do it using inner joins but i am really wondering if i can do this with Laravel models. I do not know how currently, and because i don't know what the name is for the function in laravel i can't find it either, so I am sorry if this will be a duplicate.
My tables have the following keys and foreign keys:
Users: pk: id
user_activities: pk: userid, activityid
activities: pk: id
Thank you in advance!
You can try using eager loading.
So something like:
$activiteiten = \App\User::with('UserActivity.Activity')->find(Auth::user()->id);
dd($activiteiten->UserActivity->Activity);
Edit: So then you can do something like this:
foreach($activiteiten->UserActivity as $user_activity) {
foreach($user_activity->Activity as $activity) {
print_r($activity);
}
}
Shows how you can loop through the relationships.
The Activity function should be in the User model. So then you can call it this way:
$activiteiten = \App\User::find(Auth::user()->id);
dd($activiteiten->Activity); //gives you a collection of activities
However, you need belongsToMany function rather than hasManyThrough since the relationship between users and activities is many-to-many.
This mean you cut down going through the Intermediate model. You can check the doc on belongsToMany to understand what to do better.
A side note: Why are your functions name starting with capital letter?
Also using hasMany relations should affect the function name as well, so that 'activity' becomes 'activities' (just to keep the functionality and interpretation in sync).
Update:
//User model
public function activities() {
return $this->belongsToMany('App\Activity', 'user_activity', 'userid', 'activityid');
}
This uses the 'user_activity' table, to find the relationship between users and activities, so that you may now access by User::first()->activities for example.

Laravel Eloquent query unexpected result

I've found some query result really unexpected.
It's Laravel 5.2
We have following entity:
User with method:
public function roles() : BelongsToMany
{
return $this->belongsToMany(Role::class)->withPivot('timestamp');
}
Each User can have many roles, so we have also Role entity (but it doesn't matter much in my question) and pivot table user_role with timestamp field (and ids of course), because we hold information about time, when User achieved specific role.
I want to get all Users with theirs last assigned Role
When I create query (in User context in some repository):
$this->with(['roles' => function($query) {
$query->orderBy('timestamp', 'desc');
}])->all();
the result will contain Users with Roles entities inside itself ordered by timestamp - it's ok. But I want to retrieve only one last role inside each User entity not all ordered.
So...
$this->with(['roles' => function($query) {
$query->orderBy('timestamp', 'desc')->limit(1);
}])->all();
And then I retrieve Users but only User which achieved some Role for the very last time contains it! All the other Users have their roles field containing empty array.
Why ordering was performed on each Users relation separately, but when I added limit it behaved like a global limit for all.
It drives me crazy...
Thanks for advices.
EDIT
I've created lastRoles() method to get all Roles ordered desc. But all, retrieving one is impossible.
public function lastRoles() : BelongsToMany
{
return $this->BelongsToMany(Roles::class)->withPivot('timestamp')->latest('timestamp');
}
And for testing:
$users = (new User())->with('lastRoles')->get();
But now I must iterate over Users and invoke lastRoles() on each one:
foreach ($users as $user) {
var_dump($user->lastRoles()->get()->first()->name);
}
Then I retrieve names of latest Roles assigned to each User.
So... There is no way to do it in one query? This is the only way?
For this to work, you would need a helper function:
public function latestRole()
{
return $this->hasOne(Role::class)->withPivot('timestamp')->orderBy('timestamp', 'DESC');
}
And then:
$this->with('latestRole')->get();
Credits to this awesome article.
When you eager load a relationship with query constraint(s), the query will be run once to load all relationships, not each one individually. This is the expected behavior. Think about it, eager loading exists to turn many queries into one query in order to optimize performance. There is only one query executed, so your limit constraint will limit the entire result set, rather than on a per model basis.
To circumvent this, you could try creating another belongsToMany method that adds the desired limit constraint. The following code is untested:
public function lastRole() : BelongstoMany
{
return $this->belongsToMany(Role::class)
->withPivot('timestamp')
->orderBy('timestamp', 'desc')
->limit(1);
}
Assuming this works, you can then simply change the relationship method from roles to lastRole and remove your query constraint:
$this->with('lastRole')->all();

Laravel nested relationship not working

I have a user model which stores basic user information such as username, password etc.
There are also 3 types of user, Student, Staff and Parent. Each type also has a seperate model. For example, there is a Student model which belongs to a User model.
I also have a relationships table, which stores relationships between students and parents. This relationship is stored in the User model.
If I do something like:
App\Student::first()->user->relations;
It happily returns a collection of related parents.
In my Students model, I have a method called hasParent() which accepts a given user ID, and checks to ensure the student has a parent with that id. In that method, I have the following:
public function hasParent($parent)
{
return $this->user->relations->where('id', $parent)->count() === 1;
}
However, this returns an error Cannot call 'where' on a non-object. If I debug further, $this->user->relations returns an empty array.
The problem is, like above, if I call the methods separately, I get the results I want.
So to clarify, if I run:
App\Student::first()->user->relations;
This returns a collection of users just fine.
In my Student model however, if I call:
$this->user
Then I get the correct student
If I call
$this->user->relations
I get an empty array. Which doesn't make sense! Can anyone shed any light on this, or what I might be doing wrong? If you need any further info, please let me know.
You need to call where on the relation like below.
public function hasParent($parent)
{
return $this->user->relations()->where('id', $parent)->count() === 1;
}
See the parenthesis after the relations. If you call the relation without the parenthesis Laravel returns you a collection. To get the builder you need to call the relation with the parenthesis.
I'd suggest - to avoid creating a huge query overhead (which you'll do by calling where and count on the Query builder, not the collection) - to do what you're doing already, except using Illuminate Collections filter-method:
public function hasParent($parent)
{
return $this->user->relations->filter(function($relation) use ($parent){return $entity->id === $parent;})->count() === 1;
}

Eloquent where owner equals follow

I am trying to make a twitter like feed in an application, I have a database called connections where inside there's user and follow and another database called feed containing owner which would equal to the follow column in connections.
What I could do if had every id of a follower statically is to use where('owner', '=' $follow) on each follower and return it.
I tried this approach but it wasn't ideal:
Get each follower inside connections;
foreach(follower) {
Get 10 of the latest posts orderBy "created_at";
Push into array;
}
shuffle array;
limit array to 15;
return array;
That also ended with the returned array not being ordered by created date.
How would I use eloquent to get the feed item only if the user follows the owner in the best/simplest way?
Are there any specific Laravel tools that can be used?
Also the database layout isn't fixed as it is, it can be altered if needed to better suite this.
You need to create a many-to-many relationship between the user and itself. See the laravel eloquent documentation http://laravel.com/docs/eloquent#relationships. I didn't test this code but it should be enough to get you started down the right track.
Create a pivot table called "user_following" with:
(int) id, (int) user_id, (int) following_id
Then do something like this:
<?php
// Model
class User extends Eloquent {
public function following()
{
return $this->belongsToMany('User', 'user_following', 'user_id', 'following_id');
}
public function tweets()
{
return $this->hasMany('Tweet')->orderBy('created_at', 'desc');
}
}
// controller
$tweetsOfWhoImFollowing = User::find($id)->following->tweets;

Kohana 3.3: WHERE statement with relationships

I have setup Kohana models with relationships define (1:* and : using through) but wanted to know the best way to use them in WHERE statements.
Example: I have a User model and a Post model. Post has a foreign key (userID) to User
I have setup a $_has_many relationship on the User model with an alias user_created
I know I can use the actual field userID, but I want to be able to do something like this
$user->where('user_created', 'IS', NULL);
This would also be handy for checking whether a many-to-many with something like this
'm2m_relation_count', '>', 0
Is this possible? Thanks!
You can do this with the ORM in Kohana in the following way.
$user->user_created->find_all();
This will result in all Posts the User created.
If you want to check if the User has created posts you just use the same function and do a count() on the result and check if it is greater than 0.
I don't think you can use the alias in a where clause itself. You have to call it from the ORM object.
Many-to-many relations work the same way.
1) There is no quick method to get all users without posts. Id prefer to add special column post_count and increment it after posting.
2) You can check user for m:m relationship with has()/has_any() methods:
if ($user->has('roles')) {
// user has at least one role
}
if ($user->has('roles', $roleId) {
// user has role with id=$roleId
}
if ($user->has('roles', array($roleId1, $roleId2))) {
// user has both $roleId and $roleId2 roles
}
if ($user->has_any('roles', array($roleId1, $roleId2))) {
// user has on of the $roleId1 or $roleId2
}

Categories