Laravel relationship query optimization - php

First of all I apologise for the title, I could not find anything better.
In my project I have Users and Groups. Users can join a group and create a group. The relationships are defined as follows.
User Model
/** Get all the groups the user is administrator of
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function groupsAdmin()
{
return $this->hasMany('App\Group','group_admin_id','id');
}
Group Model
/** Get the users in a group
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function users()
{
return $this->belongsToMany(\App\User::class,'groups_users', 'group_id','user_id');
}
What I am trying to do is get all the users who have joined the groups created by an user. For that I have written a method in my User model:
/**
* Returns all the users who have attended groups created by this user
*/
public function getPastGroupAttendees()
{
// first verify if the user is admin of any group
if(!$this->groupsAdmin)
{
return false;
}
$attendees = array();
foreach($this->groupsAdmin as $group)
{
if(count($group->users) > 0) $attendees[] = $group->users;
}
return $attendees;
}
But the problem with this method is its slow and will get slower with new data. And also as a user can join multiple groups, I would get duplicate users from this method.
So if anyone can show me some directions to optimize and correct this it would be very helpful.

You can setup two relations in User model:
public function groupsAdmin()
{
return $this->hasMany('App\Group', 'group_admin_id', 'id');
}
public function groups()
{
return $this->belongsToMany('App\Group');
}
It's one-to-many for admin and many-to-many for groups and users (you'll need pivot table here).
To load the data, use eager loading:
$groupWithUsers = Group::where('group_admin_id', $adminId)->with('users')->first();
$groupsOfUsers = User::with('groups')->get();
To remove duplicates you can iterate over groups and merge() all users collections into one and then use unique() method to remove duplicates.
Another way to do it is to create model for pivot table and get all users of the group with simple and readable code:
$groups = Group::where('group_admin_id', $adminId)->pluck('id'); // Get IDs of groups.
UserGroup::whereIn('group_id', $groups)->get()->unique(); // Get unique users from these groups.

Related

Correct way to set up this Laravel relationship?

I'm after a bit of logic advice. I am creating a system where users login and register their participation at an activity. They can participate at an activity many times. What is the best way to do this? I want to ensure I can use eloquent with this rather than creating my own functions.
I am imagining...
Users:
id
Activitys:
id
name
Participations:
id
user_id
activity_id
time_at_activity
I want to later be able to do such things as:
$user->participations->where('activity_id', 3)
for example.
What is the best way to set this up? I had in mind..
User: hasMany->Participations
Activity: belongsTo->Participation
Participation: hasMany->Activitys & belongsTo->User
Does this look correct?
The users schema can relate to activities through a pivot table called participations:
/**
* Indicate that the model belongs to a user.
*
* #see \App\Model\User
*
* #return BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* Indicate that the model belongs to many activity participations.
*
* #see \App\Model\Activity
*
* #return BelongsTo
*/
public function participations()
{
return $this->belongsToMany(Activity::class, 'participations');
}
$user->participations()->attach($activity);
You may want to add the reciprocal relationships. These can be separated out into traits for code reuse. ->attach(['participated_at' => now()])
You can use Many-to-Many Relationship.
Use Participation as your pivot table. Define relationships as
/* in User */
public function activity()
{
return $this->belongsToMany('App\Models\Activitys','participation','user_id','activity_id')->as('participation')->withPivot('time_at_activity');
}
/* in Activity */
public function user()
{
return $this->belongsToMany('App\Models\Users','participation','activity_id','user_id')->as('participation')->withPivot('time_at_activity');
}
DB schema
// App\User
public function participations()
{
return $this->hasMany('App\Participation');
}
// You may create App\Participation Model
// App\Participation
public function user()
{
return $this->belongsTo('App\User');
}
// Controller
$userParticipations = $user->participations->where('activity_id', 3);
// eager loading version
$userWithParticipations = $user->with(['participations' => function($q) { $q->where('activity_id', 3) }])->get();

Laravel many-to-many query controller

So i have a films table and a conversations table and a pivot table film_conversation
Film (films)
'id'
'filmable_id',
'filmable_type',
Conversation (conversations)
'id'
'last_message_id'
FilmConversation (film_conversation)
'film_id'
'conversation_id'
I am wanting to create a GET request to grab all of the conversations that belong to that specific film, I have this query but unsure if I am grabbing it correctly and how would I write whats being returned in the response?
ConversationController:
/**
*
*/
public function conversations()
{
$this->user = Auth::user();
$film = Film::whereHas('conversations', function ($query) {
return $query->where('id');
})->get();
return $film;
}
I have an additional question, should you directly include this query into the request method or split it out into a private method and include it in to increase readability and clutter of the call? what would be the best practice? It's an endpoint I'm exposing for the front end.
first of all, you're not using the authenticated user, second, you're returning a collection of films that has any conversation, the query constraint isn't doing anything and you can just access $film->conversations to get the collection
public function conversations($id)
{
// Get all conversations for a specified film
return Film::find($id)->conversations;
// Get all conversations in all films that have a conversation
$films = Film::whereHas('conversations')->with('conversations')->get();
$conversations = $films->flatMap->conversations;
return $conversations;
}
Hope this helps
/**
* #param Film $film
* #return Collection|Conversations[]
*/
public function conversations(Film $film)
{
return $film->conversations;
}
in route file
Route::get('conversations/{film}');
this code will return json representation of conversations models collection https://laravel.com/docs/5.7/eloquent-serialization#serializing-to-json
or it return 404 if film id not exist.

Laravel 5.* - Eloquent Many to Many Relationships

I have the following tables
USERS = username | email | name
FOLLOWERS = user_id | follower_id
When a logged-in user clicks on "follow" my code saves his id inside followers.follower_id, and the id of user who he wants to follow is saved inside followers.user_id.
To see how many followers a user has and how many users a user is following I use:
$followers = Follower::where('user_id', $user->id)->count();
$following = Follower::where('follower_id', $user->id)->count();
This works well, but I would like to show information about the followers of one user. I've tried the following:
$first_follower = $followers[0]->user->username;
But it return the user followed not the follower.
I am wondering how I can get information about the follower
User Model
protected $fillable = ['username','email','name'];
public function follow() {
return $this->hasMany('Shop\Follower');
}
Follower Model
protected $fillable = ['user_id','follower_id'];
public function user() {
return $this->belongsTo('Shop\User');
}
An example how you should implement relations, is above. It's many users to many users thru followers table. You probably dont need a follower model, cause you already have a user model. Your code will work after some analyze and enhancements, but i will highly recomend you to make something like this inuser model instead:
public function followers()
{
return $this->belongsToMany('App\User','followers','user_id','follower_id');
}
public function following_users()
{
return $this->belongsToMany('App\User','followers','follower_id','user_id');
}
Than you can access a followers $user->followers (this will return eloquent collection and you will be able to do whatever you want with this according to laravel docs collection api) and a certain one like $user->followers[0]
Hope i get your answer rigth.
If I get this right the followers are instances of the User class/model, so you don't need a Follower model. You can just define a Many To Many Relationship
In your User model you can add:
public function followers()
{
return $this->belongsToMany('Shop\User', 'followers', 'user_id ', 'follower_id');
}
public function following()
{
return $this->belongsToMany('Shop\User', 'followers', 'follower_id', 'user_id');
}
Than you can access the user followers just by $user->followers which will return a Laravel Collection and with $user->following you can access the ones that the user is following.
//Get the count of all followers for $user
$count = $user->followers()->count();
//Get the count of all followed by $user
$count = $user->following()->count();
//Get the username of the first follower
$follower = $user->followers()->first();
echo $follower->username;
//Loop trough all followers
foreach ($user->followers as $follower) {
echo $follower->username;
}
Defining this relation can help you save/delete followers just by using the attach() and detach() methods
// The $user will be followed by an user with $followerId
// A record in the `followers` table will be created with
// user_id = $user->id and follower_id = $followerId
$user->followers()->attach($followerId);
// The $user will stop be followed by an user with $followerId
$user->followers()->detach($followerId);
A side note:
There is a difference between calling the followers() method and calling the followers property. The first will return BelongsToMany relation and you can call all the Eloquent query builder method on it and the later will return a Collection
/** #var Illuminate\Support\Collection */
$user->followers;
/** #var Illuminate\Database\Eloquent\Relations\BelongsToMany */
$user->followers();

Polymorphic many to many from User to Post and Category

I need to implement a follow system like twitter but with exception a user can follow many Post and can follow whole Category or a user can follow a User.
I have come up with this relationship. I am using Laravel 5.1
User Model
public function followers()
{
return $this->belongsToMany('App\User', 'user_follows', 'user_id', 'follow_id');
}
public function follows()
{
return $this->belongsToMany('App\User', 'user_follows', 'follow_id', 'user_id');
}
and for follow a Category
Category Model
public function followers()
{
return $this->belongsToMany('App\User', 'category_follows', 'user_id', 'category_id');
}
and for Post is the same way, as you can see I need 3 tables (user_follows, category_follows, post_follows) to make this work.
I know there is Polymorphic Relation but I cant wrap my head around it.
Please help how i can simplify it. once again below are the requirements
User can follow many Posts
User can follow many Category
User can follow many User
You can use morphedByMany to create polymorphic many to many relations. Instead of having separate *_follows tables, you can have a single followables table with the following schema:
user_id int # user_id that is following something
followable_id int # id of the thing that is being followed
followable_type string # type of the thing that is being followed
Here's a sample implementation:
Category, Post and User models
/*
* This function gets the followers of this entity. The
* followable_id in the followables relation would be
* the id of this entity.
*/
function followers() {
return $this->morphToMany('App\User', 'followable');
}
User model
/*
* Gets the list of users that are followed by this user.
*/
function users() {
return $this->morphedByMany('App\User', 'followable');
}
/*
* Gets the list of posts that are followed by this user.
*/
function posts() {
return $this->morphedByMany('App\User', 'followable');
}
/*
* Gets the list of categories that are followed by this user.
*/
function categories() {
return $this->morphedByMany('App\User', 'followable');
}
Note that in this case, a User is both morphed by many and morphed to many, creating a self-reference many to many relationship.
Every new followable entity you create, you will need to add the followers() function to that entity, and a corresponding inverse relation to the Users entity. You could define a Followable trait containing that function, and simply add use Followable; to the new entity you add.

Laravel - many-to-many where the many-to-many table is (part-) polymorph

I have a table called bonus. A user can get a bonus (it's like an reward) for certain actions. Well, the bonus can be assigned to many users and many users can get the same bonus. So it's a many to many relation between user and bonus.
This is no problem so far. But users can get the same bonus for different actions. So let's say there is a bonus for voting on a picture. Well, one user could vote on one picture and another one could vote on another picture which I'd like to save in the many-to-many table.
Furthermore there could be a bonus for writing a comment which is clearly another table than picture votes.
The problem here is that I would need to save the polymorphic type in the bonus table and the ID in the many-to-many table.
I think this should be the best way but how would I realize it with laravel? I think this is not a normal use case. But still I'd like to use it as other relations in laravel so that I could fetch a user and get his bonuses with the correct polymorphic relation.
Do you have any ideas?
You are probably going to have to develop your own relationship classes.
Ex:
MODEL
public function answers()
{
$instance = new Response();
$instance->setSid($this->sid);
return new QuestionAnswerRelation($instance->newQuery(),$this);
}
RELATIONSHIP
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\Relation;
use Pivotal\Survey\Models\Answer;
use Pivotal\Survey\Models\Collections\AnswerCollection;
use Pivotal\Survey\Models\QuestionInterface;
use Pivotal\Survey\Models\SurveyInterface;
class QuestionAnswerRelation extends Relation
{
/**
* Create a new relation instance.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param \Illuminate\Database\Eloquent\Model $parent
* #return void
*/
public function __construct(Builder $query, QuestionInterface $parent)
{
$table = $query->getModel()->getTable();
$this->query = $query
->select(array(
\DB::raw($parent->sid.'X'.$parent->gid.'X'.$parent->qid . ' AS value'),
'id'
));
$this->query = $query;
$this->parent = $parent;
$this->related = $query->getModel();
$this->addConstraints();
}
public function addEagerConstraints(array $models)
{
parent::addEagerConstraints($models);
}
public function initRelation(array $models, $relation)
{
}
public function addConstraints()
{
}
public function match(array $models, Collection $results, $relation)
{
}
public function getResults()
{
$results = $this->query->get();
$answerCollection = new AnswerCollection();
foreach($results as $result)
{
$answer = new Answer($result->toArray());
$answer->question = $this->parent;
$answerCollection->add($answer);
}
return $answerCollection;
}
In this case we are using Lime Survey which creates a unique table (note the $instance->setSid() changes the table name) for each of its surveys and a unique column for each of its answer -> question values. ( note $parent->sid.'X'.$parent->gid.'X'.$parent->qid. 'AS value')
Where sid = survey_id, gid = group_id(I think) and qid = question_id
Its was quite irritating.
Note how I reference values from the parent to further develop the query.
You should be able to follow a similar route to achieve whatever your heart desires and still maintain the feasibility to use Eloquent.

Categories