Restricting eloquent result with scope on relation not filtering results - laravel

We have 3 tables transaction, transactionItem and transactionItemStatus all setup with relationships in their respective models, yet when trying to filter transactions by the status of their items the results are unchanged.
Definition of the relationships in the models:
In Transaction.php
public function items()
{
return $this->hasMany('App\Entities\Transaction\TransactionItem\TransactionItem', 'transactionId', 'transactionId');
}
In TransactionItem.php
public function currentStatus()
{
return $this->belongsTo('App\Entities\Transaction\TransactionItem\StatusDefinition', 'currentStatusId', 'statusId');
}
The scope in Transaction.php used to restrict based on the relationship
public function scopeOfTypeDraft($query)
{
$query->whereHas('items.currentStatus', function ($query) {
$query->where('label', '=', 'Draft');
});
}
The use of the scope in a controller
return Transaction::with([
'items',
'items.toTaxYear',
'items.counterpartyId',
'items.currentStatus',
])
->ofTypeDraft()
->get()
As you can see, Transaction is setup with a hasMany relationship with TransactionItem, TransactionItem is setup with a belongsTo relationship with Status. However when we try to restrict the results of an Eloquent query of Transaction by using a scope (or just a simple ->whereHas() with a ->where()) then the results aren't changed compared to omitting the scope. However if we perform a simple the where() on the collection that is returned, then the results are correctly filtered.

Your scope method has to return the changed query:
public function scopeOfTypeDraft($query)
{
return $query->whereHas('items.currentStatus', function ($query) {
$query->where('label', '=', 'Draft');
});
}

Related

Retrieve related models using hasManyThrough on a pivot table - Laravel 5.7

I'm trying to retrieve related models of the same type on from a pivot table.
I have 2 models, App\Models\User and App\Models\Group and a pivot model App\Pivots\GroupUser
My tables are have the following structure
users
id
groups
id
group_user
id
user_id
group_id
I have currently defined relationships as
In app/Models/User.php
public function groups()
{
return $this->belongsToMany(Group::class)->using(GroupUser::class);
}
In app/Models/Group.php
public function users()
{
return $this->belongsToMany(User::class)->using(GroupUser::class);
}
In app/Pivots/GroupUser.php
public function user()
{
return $this->belongsTo(User::class);
}
public function group()
{
return $this->belongsTo(Group::class);
}
I'm trying to define a relationship in my User class to access all other users that are related by being in the same group. Calling it friends. So far I've tried this:
app/Models/User.php
public function friends()
{
return $this->hasManyThrough(
User::class,
GroupUser::class,
'user_id',
'id'
);
}
But it just ends up returning a collection with only the user I called the relationship from. (same as running collect($this);
I have a solution that does work but is not ideal.
app/Models/User.php
public function friends()
{
$friends = collect();
foreach($this->groups as $group) {
foreach($group->users as $user) {
if($friends->where('id', $user->id)->count() === 0) {
$friends->push($user);
}
}
}
return $friends;
}
Is there a way I can accomplish this using hasManyThrough or some other Eloquent function?
Thanks.
You can't do that using hasManyThrough because there is no foreign key on the users table to relate it to the id of the group_user table. You could try going from the user to their groups to their friends using the existing belongsToMany relations:
app/Models/User.php:
// create a custom attribute accessor
public function getFriendsAttribute()
{
$friends = $this->groups() // query to groups
->with(['users' => function($query) { // eager-load users from groups
$query->where('users.id', '!=', $this->id); // filter out current user, specify users.id to prevent ambiguity
}])->get()
->pluck('users')->flatten(); // massage the collection to get just the users
return $friends;
}
Then when you call $user->friends you will get the collection of users who are in the same groups as the current user.
Have you tried Eager Loading . Hope this help.

Eloquent relation with subquery

I am having a problem with simplyfing following eloquent relationship. It has a kind of nested query that uses a FieldRole model which I consider a bad pattern to follow. If I would have a raw query, then it should be evaluated as a one query using a join on field roles and it's name, but here it's doing two queries that probably are not cache'able by eloquent ORM.
class Team extends Model {
// ...
public function goalkeepers() {
return $this->belongsToMany(
SoccerPlayer::class,
'team_has_players',
'id_teams',
'id_soccer_players'
)->where(
'id_field_role',
FieldRole::where(
'name',
'Goalkeeper'
)
->first()
->getKey()
);
}
}
The second query is exectured there
FieldRole::where(
'name',
'Goalkeeper'
)
->first()
->getKey()
Is there a way to make it as a one query relationship?
You can use a JOIN:
public function goalkeepers() {
return $this->belongsToMany(
SoccerPlayer::class,
'team_has_players',
'id_teams',
'id_soccer_players'
)->join('field_roles', function($join) {
$join->on('team_has_players.id_field_role', 'field_roles.id')
->where('field_roles.name', 'Goalkeeper');
});
}

OrderBy working but not where

This works fine.
$q=Question::with(['users'=>function($query)
{
$query->orderBy('pivot_approved','desc');
}])->get();
This doesnot:
$q=Question::with(['users'=>function($query)
{
$query->where('pivot_approved',1);
}])->get();
Also tried with wherePivot in the relation:
public function users()
{
return $this->belongsToMany('App\User','question_user','q_id','user_id')->wherePivot('approved',1);
}
Try to add withPivot() to the relation first:
->withPivot('approved');
By default, only the model keys will be present on the pivot object. If your pivot table contains extra attributes, you must specify them when defining the relationship

Laravel: how to get count() of all relations NOT collection

I have an accessor like so
public function getRegisteredCountAttribute()
{
return $this->attendees->count();
}
However, I have noticed that this counts the attendees in my collection after the query. So if my query removes some of the attendees I don't get the proper count.
Here is my query
$programs = ScheduledProgram::where('registration_start_date', '<=', $today)
->where('end_date', '>=', $today)
/* ->with(['attendees'=>function($q) use ($user_id)
{
$q->where('user_id', $user_id);
}])
->with(['scheduledProgramSegments.attendees'=>function($q) use ($user_id)
{
$q->where('user_id', $user_id);
}])
*/
->get();
I get a different number from my accessor $program->registered_count when I uncomment the comment in query section above. I guess that the accessor is giving me the count from the collection and not doing a new query to get the count I really need.
How do I get the count of registered attendees in the program?
I should note that the models attendeesand programs have a many-to-many (belongsToMany) relation with a pivot table that also has fields for registered, waitlisted.
I saw this article but I couldn't find the next belongsToMany.
Models
class ScheduledProgram extends Eloquent {
public function scheduledProgramSegments()
{
return $this->hasMany('ScheduledProgramSegment');
}
public function attendees()
{
return $this->belongsToMany('Attendee', 'prog_bookings')->withPivot('registered','paid','waitlisted');
}
public function getRegisteredCountAttribute()
{
return $this->attendees()->count();
}
}
class ScheduledProgramSegments extends Eloquent {
public function attendees()
{
return $this->belongsToMany('Attendee', 'bookings')->withPivot('paid');
}
public function scheduledProgram()
{
return $this->belongsTo('ScheduledProgram');
}
}
class ProgBooking extends Eloquent {
public function scheduled_program()
{
return $this->belongsTo('ScheduledProgram');
}
public function attendee()
{
return $this->belongsTo('Attendee');
}
}
When you fetch the $programs and eagerly load attendees with some additional constraint, those fetched filtered attendees are saved in $program->attendees attribute. When you call count() on that collection you'll get a number of attendees in that filtered collection.
If you need to count all attendees in given program you'll need to do:
public function getRegisteredCountAttribute()
{
return $this->attendees()->count();
}
Notice the additional () - as a result you'll call count() not on the eagerly loaded collection of attendees with additional constraints applied - you'll call that on the relation itself.

How to get relationship with union in eloquent?

How can I make relationship with union in laravel eloquent? I've already tried two different approaches.
User::with(['url' => function($query) use(&$some_property) {
$favouriteUrls = \DB::table('urls')
->select('urls.*')
->join('favourite_urls', function($join) {
$join->on('favourite_urls.url_id', '=', 'urls.id');
})
->where('some_condition', '=', $some_property);
$query = $query->union($favouriteUrls);
}]);
In the first attempt there wasn't any union in the query. Then I tried to move the logic to the model.
class User extends \Eloquent {
public function urls() {
$favouriteUrls = \DB::table('urls')
->select('urls.*')
->join('favourite_urls', function($join) {
$join->on('favourite_urls.url_id', '=', 'urls.id');
})
->where('some_condition', '=', $this->some_property);
return $this->belongsTo('Url')->union($favouriteUrls);
}
}
It has executed successfully but $this->some_property was set inside the query to the null value.
I can't create two separate relationship in this case. It has to be one with union. How can I fix it?
If you call that relation as User::with('urls') you will get that $this->some_property doesn't exists, because the object itself doesn't exists. But if you call the urls() method on an object, it should work. Something like this:
$user = User::find(1);
$user->urls; // here $this->some_property should have a value
Assuming you're calling the urls() method from a User object, the $this->some_property should give you the value. If for some reason you cannot access a property directly on an Eloquent model you can always refer to the attributes[] array inside of the model. For example
// calling
$this->some_property
// should be the same as
$this->attributes['some_property']
Fetch all the users joining with the condition
Assuming users is the table for all the users, in your query you could change $this->some_property with 'users.some_property' and everything should work as expected, for each user it will query based on that property. Here is the code:
class User extends \Eloquent {
public function urls() {
$favouriteUrls = \DB::table('urls')
->select('urls.*')
->join('favourite_urls', function($join) {
$join->on('favourite_urls.url_id', '=', 'urls.id');
})
->where('some_condition', '=', 'users.some_property');
return $this->belongsTo('Url')->union($favouriteUrls);
}
}
And then just call the method like this:
User::with('urls')->get();

Resources