Laravel whereHas count on Many-to-Many relationship - php

In my current project, a User may join many Organisations and vice versa - An example of a many to many relationship. I'm trying to count the number of users who are currently unverified (where the Verified column on the user table is equal to 0).
My User model:
/**
* Get the organisations that the user is a part of.
*/
public function organisation()
{
return $this->belongsToMany(
Organisation::class, 'organisation_users', 'user_id', 'organisation_id'
)->withPivot(['role'])->orderBy('name', 'asc');
}
My Organisation model:
/**
* Get all of the users that belong to the organisation.
*/
public function users()
{
return $this->belongsToMany(
User::class, 'organisation_users', 'organisation_id', 'user_id'
)->withPivot('role');
}
So if I want to count the number of unverified users I have the following method on the Organisation model:
/**
* An organisation may have unverified users attached.
*/
public function unverifiedUsers()
{
return $this->whereHas('users', function($query) {
$query->where('verified', 0);
})->get();
}
However, running dd(\App\Organisation::find($org->id)->unverifiedUsers()->count()); only shows 1 when in fact there should be 10. Am I structuring my relationships incorrectly?

whereHas() will return 0 or 1. It just tells you if such a user exists.
The sollution is much simpler:
public function unverifiedUsers()
{
return $this->users()->where('verified', 0)->get();
}
If you only need the count:
public function unverifiedUsersCount()
{
return $this->users()->where('verified', 0)->count();
}

Related

Laravel pivot consultation

I need advice about my model relationships,
Logic
Group has many users
Group has many admins
User has many groups (as user)
User has many groups (as admin)
Database Structure
Group Table
User Table
Group_Users table (save id of user and id of group)
Group_Admins table (save id of user and id of group)
Relationship Code
User model
public function groupsUser() {
return $this->hasMany(GroupUser::class);
}
public function groupsAdmin() {
return $this->hasMany(GroupAdmin::class);
}
Group model
public function users() {
return $this->hasMany(GroupUser::class);
}
public function admins() {
return $this->hasMany(GroupAdmin::class);
}
GroupUser model
public function group() {
return $this->belongsTo(Group::class);
}
public function user() {
return $this->belongsTo(User::class);
}
GroupAdmin model
public function group() {
return $this->belongsTo(Group::class);
}
public function user() {
return $this->belongsTo(User::class);
}
Help wanted
Basically as the relationships between users and groups is many to many normally I shouldn't need models of GroupUser and GroupAdmin and then just using sync() function in order to add/remove users and group id's from those tables.
What is my concerns then?
Normally I use that type of connection when I want input bulk ids into database (let say adding tags to posts, suddenly relate 10 tags id to 1 post) that moment using sync() and removing GroupUser and GroupAdmin models makes sense but in my case as users joins/adds to groups one by one, what do you suggest for this relationships?
Is my current approach makes sense?
Is is better if I remove those GroupUser and GroupAdmin models and add them to user, group model like:
public function users()
{
return $this->hasMany(User::class, 'group_users', 'user_id', 'id');
}
and such so?
What do you think is the best practice?
users and groups is many to many
your tables like this?
users, user_group , groups ?
How about use 'belongsToMany' relation?
Laravel many to many relation
// User model
public function groups() {
return $this->belognsToMany(Group::class);
}
// Group model
public function users() {
return $this->belognsToMany(User::class);
}
And use like.
User::find(1)->groups; // return user 1 groups
User::find(1)->groups()->sync($groupIds); // relate user and groups
Group::find(1)->users; // return group 1 users
If you have role column in your users table, you cold add relation like.
// Group model
public function users() {
return $this->belognsToMany(User::class)->where('role', 'the role of normal user');
}
public function admins() {
return $this->belognsToMany(User::class)->where('role', 'the role of admin user');
}
Hope it helps you.

Eloquent relationship structure for league app

I've been working on an Angular app with a Laravel rest API and have realised my relationships aren't quite as they should be.
I have 3 entities - seasons, divisions and teams.
Season:
A season has many divisions, and many teams.
Division:
A division belongs to many seasons, and has many teams.
Team:
A team belongs to many divisions, and belongs to many seasons.
This is because a division may not be used for every season, and a team may change divisions each season, or may not play in all seasons.
I'm struggling to understand how to implement the relationship logic.
For example, I'd like to get divisions for a season and the teams present in that division for that particular season, be it the current one or when the user is viewing an old season.
Here's what I've got at the moment:
Season model
class Season extends Model
{
protected $guarded = [];
protected $hidden = ['pivot'];
/**
* Return divisions for this season
*
* #return BelongsToMany
*/
public function divisions()
{
return $this->belongsToMany('App\Division');
}
public function teams()
{
return $this->belongsToMany('App\Team');
}
}
Division Model
class Division extends Model
{
protected $guarded = [];
public function matches()
{
return $this->hasMany('App\Fixture');
}
public function seasons()
{
return $this->belongsToMany('App\Season');
}
public function teams() {
return $this->hasMany('App\Team');
}
}
Team Model
class Team extends Model
{
protected $guarded = [];
protected $hidden = ['pivot'];
public function division() {
return $this->belongsTo('App\Division');
// should be belongsToMany
}
public function seasons()
{
return $this->belongsToMany('App\Season')->select('season_id');
}
}
With the following tables:
seasons
divisions
teams
division_season
season_team
But this doesn't enable me to have a team belonging to a different division per season.
If I change the division() method on the team model to be divisions() and with a belongsToMany() and have a new table - division_team (is this the right approach?) how would I then query all teams by their division on a per season basis?
Eg:
Get all divisions and their teams by season id
Bearing in mind teams have the potential to change divisions each season.
EDIT
As per answer from Thomas Van Der Veen's answer below, I have added a table division_season_team and used the relations in his answer.
Trying to get divisions with their teams based on a season id though is proving difficult - the below returns the correct divisions, but the teams aren't necessarily part of the current season!
DivisionsController
if ($request->query('seasonId')) {
$seasonId = $request->query('seasonId');
return $this->respond(new DivisionCollection(Division::with('teams')->whereHas(
'seasons', function($q) use ($seasonId) {
$q->where('season_id', '=', $seasonId);
})->get()
));
}
By reading your desired outcome a suggestion would be to create a single table that holds al those three relationships.
season_division_team // Or whatever you want to call it.
id
season_id
division_id
team_id
This allows you to easily create and update relations. All models can belong to many others.
Relations you now can have are:
// Season
public function divisions()
{
return $this->belongsToMany(Division::class, 'season_division_team');
}
public function teams()
{
return $this->belongsToMany(Team::class, 'season_division_team');
}
// Division
public function seasons()
{
return $this->belongsToMany(Season::class, 'season_division_team');
}
public function teams()
{
return $this->belongsToMany(Team::class, 'season_division_team');
}
// Team
public function seasons()
{
return $this->belongsToMany(Season::class, 'season_division_team');
}
public function divisions()
{
return $this->belongsToMany(Division::class, 'season_division_team');
}
You just don't use the correct query. Your query should be:
if ($seasonId = $request->input('seasonId')) {
$season = Season::findOrFail($seasonId);
$divisionIdsQuery = $season->divisions()
->select('divisions.id')
->groupBy('divisions.id')
->getQuery(); // can't just ->get(), because MYSQL FULL GROUP BY.
$divisions = Division::whereIn('id', $divisionIdsQuery)
->with(['teams' => function ($query) use ($season) {
$query->where('season_id', $season->id);
}])
->get();
return $this->respond(new DivisionCollection($divisions));
}
After read your edit I think that you can get this with a join inside the teams with.
Tables:
seasons
divisions
teams
season_divisions_teams
- season_id
- division_id
- team_id
In your controller:
if ($request->query('seasonId')) {
$seasonId = $request->query('seasonId');
$result = Division::with(['teams' => function (Builder $query) use ($seasonId) {
$query->select(["teams.*"]);
$query->join('season_divisions_teams', 'season_divisions_teams.team_id', '=', 'teams.id');
$query->where('season_divisions_teams.season_id', '=', $seasonId);
}])
->whereHas('seasons', function(Builder $query) use ($seasonId) {
$query->where('season_id', '=', $seasonId);
})->get();
return $this->respond(new DivisionCollection($result));
}
Explanation:
Like we are calling the relation between divisions and teams when we calling with('teams'), the teams that isn't in that division are excluded. After, we use a join between teams and season_divisions_teams to get the information about what teams are in the seasons, and we use a where to filter them by season id.

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.

Can't get Eloquent's hasManyThrough to work for a basic example

Here are my relationships:
User
id
Collection
id
UserCollection
user_id
collection_id
How can I get something like $user->collections to return all Collection objects that belongs to the user? A UserCollection simply links a User to a Collection. This allows a user to have multiple collections, but also allows a collection to belong to multiple users.
What I'm currently trying is to specify that UserCollection belongs to a User on user_id, and belongs to a Collection on collection_id.
// UserCollection
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
public function collection()
{
return $this->belongsTo(Collection::class, 'collection_id');
}
Then specifying that a User has many Collections through UserCollection.
// User
public function collections()
{
return $this->hasManyThrough(Collection::class, UserCollection::class);
}
I've also tried explicitly setting the column names of the hasManyThrough relationship, but the join tries to use an id column on the UserCollection model, which does not exist as there is no primary key:
public function collections()
{
return $this->hasManyThrough(Collection::class, UserCollection::class, 'user_id', 'collection_id');
}
You're overcomplicating things here. You don't need hasManyThrough. What you need is belongsToMany() for a many-to-many relationship.
First, get rid of your UserCollection model. You don't need it.
Then change your relations to this:
public function collections(){
return $this->belongsToMany('Collection', 'user_collections');
}
public function users(){
return $this->belongsToMany('User', 'user_collections');
}
For more information take a look at the official docs on relations

Laravel hasManyThrough interrogate the intermediate relationship as well as distant

I want to perform the following (with some rather crude pseudo code):
SELECT a users orderLines WHERE the orderheader's status ='paid' AND the orderLine's productId>5
In other words, a user can place many orders. Each order has one or many order lines. I want to find all of the order lines that the user has placed (order lines, not orders) but only if the order header has a certain status, and only if the order line has another parameter checked. This could be the date the line was added, or the productId being x, and so on.
Simple enough to do with a standard MySql query.
I have the nescessary models:
User
OrderHeader (Intermediate relationship)
OrderLine (Distant relationship - this is what I want to fetch, via the intermediate)
Here are how the relationships are defined in each model:
User
public function orders()
{
return $this->hasMany('App\OrderHeader', 'user_id', 'id');
}
public function lines()
{
return $this->hasManyThrough('\App\OrderLine', 'App\OrderHeader', 'user_id', 'order_header_id');
}
OrderHeader
public function lines()
{
return $this->hasMany('App\OrderLine', 'order_header_id', 'id');
}
public function user(){
return $this->belongsTo('User', 'id', 'user_id');
}
OrderLine (Fetch these for the User, using hasManyThrough)
public function header()
{
return $this->belongsTo('App\OrderHeader', 'order_header_id');
}
public function products()
{
return $this->belongsToMany('App\Product');
}
So, I load the User, using:
$person = User::findOrFail($id)
Then I can use:
$user->lines()->where('product_id','>=',10)->paginate(20);
So, that works brilliantly to get ALL of the lines that the user has placed, which match the condition on the line records. However, I can't figure out how to add a second condition on the intermediate, so that not only do I check the product_id, but also interrogate the OrderHeader entity via the orders() relationship.
I've tried:
return $user->orders()->where('status','=','Paid')->lines()->where('product_id','>=',20))->paginate(20);
but that returns the error: Call to undefined method Illuminate\Database\Query\Builder::lines()
hasManyThrough is a special case in Eloquent, where table is joined (intermediate table), so it's pretty simple - just query that table. Nothing to do with the other relation.
This is what you want:
$throughTable = $user->lines()->getParent()->getTable();
$user->lines()
->where('product_id', '>=', 10)
->where('orderheaders.status', 'paid')
// or
// where("{$throughTable}.status', 'paid')
->paginate(20);
Btw this relation is wrong:
// OrderHeader model
public function user(){
return $this->belongsTo('User', 'id', 'user_id'); // wrong keys order
}
// should be
public function user(){
return $this->belongsTo('User', 'user_id', 'id');
}

Categories