Querying a related model within a Laravel query scope - laravel

I have a query scope method that filters results on a Foo model by the author of the model. The complication is that the author is not directly related.
Foo belongs to Bar and Bar belongs to User. If Foo belonged to User, I could just do this:
public function scopeAuthorOnly($query, User $user)
{
return $query->whereuser_id($user->id);
}
This obviously won't work as the Foo model has no user_id column and instead has a bar_id column. However I'm not sure how I can build the query in a way that would filter the user_id.
Any pointers?

You can use whereHas() in scopes to query relationships.
Let's assume you have a users() relation function in this model. Then it'd be something like this:
public function scopeAuthorOnly($query, User $user)
{
return $query->whereHas('users', function($q) use($user){
$q->where('id', $user->id);
});
}
Update: You can also nest whereHas statements: If the current Foo has a relation named bar, and bar has a relation called users, it'd be like this:
public function scopeAuthorOnly($query, User $user)
{
return $query->whereHas('bar', function($q) use($user){
return $q->whereHas('users', function($q2) use($user){
$q2->where('id', $user->id);
});
});
}
More info: here

You can also do this using relations (without resorting to manual joins):
public function scopeAuthorOnly($query, User $user)
{
$query->whereHas(array('Bar' => function($query) use($user){
$query->where('user_id', '=', $user->id);
}));
}
Edit: now using whereHas instead of with (Laravel >= 4.1 only). Thanks for the correction Arda.
Edit: in 4.2 the syntax of whereHas has changed, so that the relationship name is parameter 1 and the closure is parameter 2 (rather than being passed in as an array):
public function scopeAuthorOnly($query, User $user)
{
$query->whereHas('Bar', function($query) use($user){
$query->where('user_id', '=', $user->id);
});
}

Found a solution:
public function scopeAuthorOnly($query, User $user)
{
$query
->join('bars', 'foos.bar_id', '=', 'bars.id')
->join('users', 'bars.user_id', '=', 'users.id')
->where('users.id', '=', $user->id);
return $query;
}

Related

orderBy created_at on many-to-many relationship laravel and eloquent

I would like to know how can I use an orderby on created_at on pivot table.
Currently I have this :
users
events
subscriptions [user_id, event_id, created_at, updated_at]
I would like to have all users subscriptions on specific event id :
return $this->user
->with('subscriptions')
->whereHas('subscriptions', function ($query) use ($eventId) {
$query->where('events.id', $eventId)
->orderBy('subscriptions.created_at', 'ASC');
})
->paginate(5);
I have all users attach on a specific event but the orderBy doesn't work.
I would like to use the whereHas and no the join method.
I believe you should order on the with clause, like so:
return $this->user
->with([
'subscriptions' => function ($query) {
$query->orderBy('created_at', 'ASC');
}
])
->whereHas('subscriptions', function ($query) use ($eventId) {
$query->where('events.id', $eventId);
})
->paginate(5);
Here's another way to get what you want:
join()
return $this->user
->with('subscriptions')
->has('subscriptions')
->join('subscriptions', function ($join) use ($request) {
$join->on('subscriptions.user_id', '=', 'users.id');
})
->select('users.*', 'subscriptions.created_at as subscriptions_created_at')
->orderBy('subscriptions_created_at', 'ASC')
->paginate(5);
with() uses eager loading, which turns query into multiple queries. You can try this.
return $this->user->join('subscriptions', function ($join) use ($eventId) {
$join->on('subscriptions.user_id', '=', 'users.id');
$join->where('events.id', $eventId)
})
->select('users.*', 'subscriptions.*')
->orderBy('subscriptions.created_at', 'ASC')
->paginate(25);
I'm hoping this helps you my friend :
return $this->user
->with(['subscriptions' => function ($query) use ($eventId) {
$query->where('event_id', $eventId)
->orderBy('created_at', 'ASC');
}])
->paginate(5);
Try to use the hasManyThrough option. This will find all events that are intermediate of the subscriptions pertaining to the user.
User extends Model{
public function ScopeEvents()
{
return $this->hasManyThrough(Event::class, Subscription::class);
}
}
UserController extends Controller{
public function listEvents(Request $request, User $user){
$event_id = $request->get('event_id');
return $user->events()->where('event_id',$event_id)
->orderBy('subscriptions.created_at','ASC')->paginate(5);
}
}

orderBy on hasMany relationship laravel

I made a "hasMany" relationship from Category model to Product model using ProductCatRel model.
I am trying to ordering my products form Category model. The "where" condition is fine, But "orderBy" is not working. Here is my code:
public function Products(){
return $this->hasMany(ProductCatRel::class,'category')
->with('Product')
->whereHas('Product', function($q){
$q->where('status', 1)->orderBy('position');
});
}
Use the following snippet may works
public function products(){
return $this->hasMany(ProductCatRel::class,'category')
->with('Product')
->whereHas('Product', function($q){
$q->where('status', 1)
});
}
$products = App\Category::find(1)->products()->orderBy('position')->get();
whereHas() only check existence and don't affect on retrieved relation data.
You should apply orderBy() in with() method. Also you need to duplicate status checking in with() method.
public function Products(){
return $this
->hasMany(ProductCatRel::class, 'category')
->with(['Product' => function ($q) {
$q->where('status', 1)->orderBy('position');
}])
->whereHas('Product', function($q) {
$q->where('status', 1);
});
}

Order on second degree relationship in Eloquent

I have two models in a many-to-many relationship: Fixture and Event.
Fixture:
public function events()
{
return $this->belongsToMany(Event::class, 'fixture_events')->withPivot('player_id');
}
Event:
public function fixtures()
{
return $this->belongsToMany(Fixture::class, 'fixture_events')->withPivot('player_id');
}
You will notice that the pivot table has an additional field player_id. This is because FixtureEvent also had a relationship to a model called Player.
FixtureEvent:
public function fixture()
{
return $this->hasOne(Fixture::class, 'id', 'fixture_id');
}
public function event()
{
return $this->hasOne(Event::class, 'id', 'event_id');
}
public function player()
{
return $this->belongsTo(Player::class, 'id', 'player_id');
}
And Player has:
public function events()
{
return $this->hasMany(FixtureEvent::class);
}
My problem arises when I want to get all the fixture_events for a player and sort them by a field in the events table. This field is named sequence.
However, whatever I do, the events always come out ordered by ID.
This is the query that I would like to order by events.sequence, whether by using some type of join or whatever works (this is inside the Player model so $this is a player object):
$events = $this->events()->whereHas('fixture', function ($query) use ($round, $competition_id) {
$query->where('fixtures.round', '=', $round)->where('competition_id', $competition_id);
})->get();
I've tried adding a join query here on fixture_events.event_id = events.id and then ordering by events.sequence but this doesn't work.
I've also tried adding orderBy directly in the model relationship, i.e. in the Fixture model:
public function events()
{
return $this->belongsToMany(Event::class, 'fixture_events')->orderBy('sequence')->withPivot('player_id');
}
But this does nothing for my problem.
How do I make this happen?
Update
At first I misread the relations, can you try with the below query?
$events = $this->events()->whereHas('fixture', function ($query) use ($round, $competition_id) {
$query->where('fixtures.round', '=', $round)->where('competition_id', $competition_id);
})->with(['events.event' => function ($query) {
$query->orderBy('sequence');
}])->get();
You have a couple of alternatives, but first I suggest you to edit your relationship to include the sequence field you are trying to load.
Then proceed with one of the following:
Order by on the relationship definition, but I think you have to load that field from the pivot table, otherwise you won't have its value, and prefix the relations table on the orderby field.
public function events() {
return $this->belongsToMany(Event::class, 'fixture_events')
->withPivot(['player_id', 'sequence'])
->orderBy('fixture_events.sequence');
}
or with:
public function events() {
return $this->belongsToMany(Event::class, 'fixture_events')
->withPivot(['player_id', 'sequence'])
->orderBy('pivot_sequence');
}
Order by a pivot field outside the relation can be done like this:
$events = $this->events()->whereHas('fixture', function ($query) use ($round, $competition_id) {
$query->where('fixtures.round', '=', $round)->where('competition_id', $competition_id);
})->with(['fixture' => function ($query) {
$query->orderBy('sequence');
}])->get();
or with:
$events = $this->events()->whereHas('fixture', function ($query) use ($round, $competition_id) {
$query->where('fixtures.round', '=', $round)->where('competition_id', $competition_id);
})
->orderBy('pivot_sequence')
->get();
Let me know if any of these methods works!

Laravel - How to get data from a table related with pivot table

I have this model for notifications table:
class Notification extends Model
{
public function users()
{
return $this->belongsToMany(User::class, 'notification_user', 'notification_id', 'user_id');
}
}
And this method in controller for getting data from notifications where id of notifications is related to a pivot table named notification_user:
$myNotifications = DB::table('notification_user')
->join('notifications', 'notifications.id', 'notification_user.notification_id')
->where('notification_user.user_id', $userId)
->where('notification_user.seen', 0)
->get();
the result of $myNotifications is correct but I want to use Model and its relationship instead of DB.
How can I get all records in notifications where each notification related to a specific user which is not seen by the user.
You need to add ->withPivot('seen') to the relationship:
public function users()
{
return $this
->belongsToMany(User::class, 'notification_user', 'notification_id', 'user_id')
->withPivot('seen');
}
Then you can do:
Notification::whereHas('users', function ($q) use ($userId) {
$q->where('id', $userId)->where('seen', 0);
})->get();
To avoid joining users, your other option is whereExists:
Notification::whereExists(function ($q) use ($userId) {
$q
->selectRaw(1)
->table('notification_user')
->whereRaw('notifications.id = notification_user.notification_id')
->where('user_id', $userId)
->where('seen', 0);
})->get();
Should still be more performant, but not much more elegant.
You will have to define the same relation in User model as notifications and then:
$notifications = User::where('id', $user_id)->notifications()->where('seen', 0)->get();
You can use with keyword for eager loading inside your controller.
like if you have any relation defined inside your model, just add a with('modelRelation') before your get() statement in eloquent.
Happy Coding.

Eloquent: hasNot with parameter

I have the following eloquent models:
User | id
Post | id
Comment | id | post_id | user_id
Using eloquent, how can I fetch all Posts which a specific User hasn't commented yet?
I tried so far:
In Model Post:
public function noCommentOf(User $user) {
$this->hasNot('App\Comment')->commentOf($user);
}
In Model Comment:
public function commentOf($query, User $user) {
return $query->where('user_id', '=', $user->id);
}
The way I would do this is by querying the Post model with a whereDoesnthave relationship. In your controller:
public function getPostsWithoutCommenter(){
$userId = 1; // Could be `$user`, `use($user)` and `$user->id`.
$posts = \App\Post::whereDoesntHave("comments", function($subQuery) use($userId){
$subQuery->where("user_id", "=", $userId);
})->get();
}
This would assume that comments is defined on the Post model as:
public function comments(){
return $this->hasMany(Comment::class);
}
Basically, if the comments relationship with the check for that $userId returns a Collection, it would be ignored from the result set.
Post model
public function comments()
{
return $this->hasMany(Comment::class)
}
Then get posts
$posts = Post:: whereDoesntHave('comments', function ($query) use ($userId) {
$query->where('user_id', $userId);
});
To get posts with no comments
$posts = Post::has('comments', '=', 0)->get();
I think:
$user->post()->leftJoin('comments', 'posts.id', '=', 'comments.post_id')->whereNull('comments.id');

Resources