Laravel many-to-many query controller - php

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.

Related

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();

Laravel Eloquent ORM whereHas with a foreach loop

Here are the relationships:
A user has many skills, there is a join table user_skills. I need to search this table to return the profiles that have the particular skill. This is part of a bigger query that is being built, so that is why there is not a ->get() on here.
User Model
/**
* A user may have many skills
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function skills()
{
return $this->hasMany('App\Core\Platform\Models\UserSkill');
}
Below is the query that isn't doing what I need it to. I need it to return the users who have the particular skill, based on the ID being passed in the search (the $this->misc['search_skills'] value).
// Skills
$this->user = $this->user->whereHas('skills', function ($q)
{
foreach ($this->misc['search_skills'] AS $skill)
{
$q->orWhere('id', $skill);
}
});
Any thoughts as to what I am doing wrong or how I could execute this in a different way?
$skills = $this->misc['search_skills']; // assuming this is an array
$this->user = $this->user->whereHas('skills', function ($q) use ($skills)
{
$q->whereIn('id', $skills);
});
Any time you end up using orWhere multiple times on the same field, you should most likely be using whereIn.
Put the search skills into a variable ($skills), import that variable into your callback with use, then use whereIn.

Laravel relationship query optimization

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.

Laravel -- Flatten data appended via `with`

I've got two models, User and Seminar. In English, the basic idea is that a bunch of users attend any number of seminars. Additionally, exactly one user may volunteer to speak at each of the seminars.
My implementation consists of a users table, a seminars table, and a seminar_user pivot table.
The seminar_user table has a structure like this:
seminar_id | user_id | speaking
-------------|-----------|---------
int | int | bool
The relationships are defined as follows:
/** On the Seminar model */
public function members()
{
return $this->belongsToMany(User::class);
}
/** On the User model */
public function seminars()
{
return $this->belongsToMany(Seminar::class);
}
I am struggling to figure out how to set up a "relationship" which will help me get a Seminar's speaker. I have currently defined a method like this:
public function speaker()
{
return $this->members()->where('speaking', true);
}
The reason I'd like this is because ultimately, I'd like my API call to look something like this:
public function index()
{
return Seminar::active()
->with(['speaker' => function ($query) {
$query->select('name');
}])
->get()
->toJson();
}
The problem is that since the members relationship is actually a belongsToMany, even though I know there is only to ever be a single User where speaking is true, an array of User's will always be returned.
One workaround would be to post-format the response before sending it off, by first setting a temp $seminars variable, then going through a foreach and setting each $seminar['speaker'] = $seminar['speaker'][0] but that really stinks and I feel like there should be a way to achieve this through Eloquent itself.
How can I flatten the data that is added via the with call? (Or rewrite my relationship methods)
Try changing your speaker function to this
public function speaker()
{
return $this->members()->where('speaking', true)->first();
}
This will always give you an Item as opposed to a Collection that you currently receive.
You can define a new relation on Seminar model as:
public function speaker()
{
return $this->belongsToMany(User::class)->wherePivot('speaking', true);
}
And your query will be as:
Seminar::active()
->with(['speaker' => function ($query) {
$query->select('name');
}])
->get()
->toJson();
Docs scroll down to Filtering Relationships Via Intermediate Table Columns

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