How to constraint scope of hasMany relationship in a query in Laravel - php

I am working on a laravel based project comprising of Authors, Posts and Comments as models.
The relationship between the models is as follows:
Author -> hasMany Post
Post -> hasMany Comment.
The problem is as follows:
Authors made multiple posts, and all of them have multiple comments. I want to get the count of the Authors with comments from internal members only on their latest post.
My Models are shown below-
Author.php
public function post() {
return $this->hasMany('App\Post');
}
public function latestPost() {
return $this->hasOne('App\Post', 'latest_post_id', 'id')-
>orderBy('created_at', 'desc');
}
Post.php
public function comment() {
return $this->hasMany('App\Comment', 'post_id', 'id');
}
The query I am using to get the data are as follow:
$author_count = Author::whereHas('latestPost.comment', function ($query) {
$query->createdByInternal();
})->count();
When I execute this query, it considers all the Post models instead of only the latest Post and gives the authors who have a comment by an Internal member on any of their post.
However, on eager loading the latestPost query using "with" function, I get only the comments on the latest post.
$author = Author::with('latestPost.comment')->get();
Thanks in advance.

If you want to count authors who have posts with no created_by_internal
= null comments, use whereDoesntHave():
Author::whereDoesntHave('latestPost.comment', function ($query) {
$query->notCreatedByInternal();
})
->count();
In the relationship definition put foreign key before the id:
public function latestPost()
{
return $this->hasOne('App\Post', 'latest_post_id', 'id')->latest();
}
Also, change to scope to:
public scopeNotCreatedByInternal($query)
{
return $query->whereNull('created_by_internal');
}

Related

Laravel 5.5 How to get latest N record When using Many to Many releationships?

I want to get latest ten record from breaking news category. I searched here and web but any solutions doesn't work..
I have categories, posts and pivot tables.
Category Model:
public function posts(){
return $this->belongsToMany('App\Post');
}
Posts Model
public function categories(){
return $this->belongsToMany('App\Category');
}
Controller
$breaking_news = Category::where('id',6)->with('posts')->orderBy('id','desc')->take(2)->get();
return view('frontend.home',compact('breaking_news'));
dd($breaking_news) screenshot: https://screenshots.firefox.com/rK9hbBq4pIru1o1s/localhost
Any advice ?
You need to limit the posts to N instead of the categories. There's 2 ways to do that:
$breaking_news = Category::where('id',6)->with('posts')->first()->posts->sortBy('id')->take(2); //$breaking_news will be a collection of posts
Or
$breaking_news = Post::whereHas('categories', function ($query) {
$query->where("id", 6);
})->orderBy('id','desc')->take(2)->get(); // Also a collection of posts but each post will have a ->categories collection as well.

Order query by relationship count laravel 4.2

So i got an section people can make comments and give like to those comments.
i want to organizate the comments based on how many likes does it have.
so i use something like this
$art = Article::with('category')
->with(array('comments' => function($comments){
//i get the comments related to the article and the count of likes it has
$comments->with('likesCount');
}))
->find($id);
this is the model
<?php
class Comment extends Eloquent {
public function likes()
{
return $this->hasMany('CommentsLike','comment_id');
}
//here i take the count of likes, if i use ->count() it throw
public function likesCount()
{
return $this->likes()
->groupBy('comment_id')
->selectRaw('comment_id,count(*) as comment_likes');
}
}
how can i sort my comment based on what i got in likesCount
Use orderBy() to likesCount() function.
public function likesCount()
{
return $this->likes()
->groupBy('comment_id')
->selectRaw('comment_id,count(*) as comment_likes')
->orderBy('comment_likes', 'desc');
}

laravel eloquent sort by relationship

I have 3 models
User
Channel
Reply
model relations
user have belongsToMany('App\Channel');
channel have hasMany('App\Reply', 'channel_id', 'id')->oldest();
let's say i have 2 channels
- channel-1
- channel-2
channel-2 has latest replies than channel-1
now, i want to order the user's channel by its channel's current reply.
just like some chat application.
how can i order the user's channel just like this?
channel-2
channel-1
i already tried some codes. but nothing happen
// User Model
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved')
->with(['replies'])
->orderBy('replies.created_at'); // error
}
// also
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved')
->with(['replies' => function($qry) {
$qry->latest();
}]);
}
// but i did not get the expected result
EDIT
also, i tried this. yes i did get the expected result but it would not load all channel if there's no reply.
public function channels()
{
return $this->belongsToMany('App\Channel')
->withPivot('is_approved')
->join('replies', 'replies.channel_id', '=', 'channels.id')
->groupBy('replies.channel_id')
->orderBy('replies.created_at', 'ASC');
}
EDIT:
According to my knowledge, eager load with method run 2nd query. That's why you can't achieve what you want with eager loading with method.
I think use join method in combination with relationship method is the solution. The following solution is fully tested and work well.
// In User Model
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved');
}
public function sortedChannels($orderBy)
{
return $this->channels()
->join('replies', 'replies.channel_id', '=', 'channel.id')
->orderBy('replies.created_at', $orderBy)
->get();
}
Then you can call $user->sortedChannels('desc') to get the list of channels order by replies created_at attribute.
For condition like channels (which may or may not have replies), just use leftJoin method.
public function sortedChannels($orderBy)
{
return $this->channels()
->leftJoin('replies', 'channel.id', '=', 'replies.channel_id')
->orderBy('replies.created_at', $orderBy)
->get();
}
Edit:
If you want to add groupBy method to the query, you have to pay special attention to your orderBy clause. Because in Sql nature, Group By clause run first before Order By clause. See detail this problem at this stackoverflow question.
So if you add groupBy method, you have to use orderByRaw method and should be implemented like the following.
return $this->channels()
->leftJoin('replies', 'channels.id', '=', 'replies.channel_id')
->groupBy(['channels.id'])
->orderByRaw('max(replies.created_at) desc')
->get();
Inside your channel class you need to create this hasOne relation (you channel hasMany replies, but it hasOne latest reply):
public function latestReply()
{
return $this->hasOne(\App\Reply)->latest();
}
You can now get all channels ordered by latest reply like this:
Channel::with('latestReply')->get()->sortByDesc('latestReply.created_at');
To get all channels from the user ordered by latest reply you would need that method:
public function getChannelsOrderdByLatestReply()
{
return $this->channels()->with('latestReply')->get()->sortByDesc('latestReply.created_at');
}
where channels() is given by:
public function channels()
{
return $this->belongsToMany('App\Channel');
}
Firstly, you don't have to specify the name of the pivot table if you follow Laravel's naming convention so your code looks a bit cleaner:
public function channels()
{
return $this->belongsToMany('App\Channel') ...
Secondly, you'd have to call join explicitly to achieve the result in one query:
public function channels()
{
return $this->belongsToMany(Channel::class) // a bit more clean
->withPivot('is_approved')
->leftJoin('replies', 'replies.channel_id', '=', 'channels.id') // channels.id
->groupBy('replies.channel_id')
->orderBy('replies.created_at', 'desc');
}
If you have a hasOne() relationship, you can sort all the records by doing:
$results = Channel::with('reply')
->join('replies', 'channels.replay_id', '=', 'replies.id')
->orderBy('replies.created_at', 'desc')
->paginate(10);
This sorts all the channels records by the newest replies (assuming you have only one reply per channel.) This is not your case, but someone may be looking for something like this (as I was.)

Displaying posts of users the user follows through Laravel relationships

I would like to display the posts of everyone the current user follows, ordered by date desc.
I have a many to many relationship supplying all the people the user is following.
$users = User::find(Auth::user()->id)->follow()->get();
I have a one to many relationship displaying the posts for any user.
$updates = App\User::find(?????)->updates()->orderBy('created_at', 'desc')->get();
The question mark's shows where the followers ID's need to be placed.
I can put the above query inside the for each loop but that obviously works its way through each follower rather than all posts in date order.
I suspect I may need to set a new relationship and work from the beginning. Can anyone advise.
User Model
public function updates()
{
return $this->hasMany('App\update');
}
/**
* User following relationship
*/
// Get all users we are following
public function follow()
{
return $this->belongsToMany('App\User', 'user_follows', 'user_id', 'follow_id')->withTimestamps()->withPivot('id');;;
}
// This function allows us to get a list of users following us
public function followers()
{
return $this->belongsToMany('App\User', 'user_follows', 'follow_id', 'user_id')->withTimestamps();;
}
}
Update Model
public function user_update()
{
return $this->belongsTo('App\User');
}
Thank you.
Since you want the posts, it is probably going to be easier starting a query on the Post model, and then filter the posts based on their relationships.
Assuming your Post model has an author relationship to the User that created the post, and the User has a follower relationship to all the Users that are following it, you could do:
$userId = Auth::user()->id;
$posts = \App\Post::whereHas('author.follower', function ($q) use ($userId) {
return $q->where('id', $userId);
})
->latest() // built in helper method for orderBy('created_at', 'desc')
->get();
Now, $posts will be a collection of your Post models that were authored by a user that is being followed by your authenticated user.

Laravel / Eloquent : hasManyThrough WHERE

In the documentation of Eloquent it is said that I can pass the keys of a desired relationship to hasManyThrough.
Lets say I have Models named Country, User, Post. A Country model might have many Posts through a Users model. That said I simply could call:
$this->hasManyThrough('Post', 'User', 'country_id', 'user_id');
This is fine so far! But how can I get these posts only for the user with the id of 3 ?
Can anybody help here?
So here it goes:
models: Country has many User has many Post
This allows us to use hasManyThrough like in your question:
// Country model
public function posts()
{
return $this->hasManyThrough('Post', 'User', 'country_id', 'user_id');
}
You want to get posts of a given user for this relation, so:
$country = Country::first();
$country->load(['posts' => function ($q) {
$q->where('user_id', '=', 3);
}]);
// or
$country->load(['posts' => function ($q) {
$q->has('user', function ($q) {
$q->where('users.id', '=', 3);
});
})
$country->posts; // collection of posts related to user with id 3
BUT it will be easier, more readable and more eloquent if you use this instead:
(since it has nothing to do with country when you are looking for the posts of user with id 3)
// User model
public function posts()
{
return $this->hasMany('Post');
}
// then
$user = User::find(3);
// lazy load
$user->load('posts');
// or use dynamic property
$user->posts; // it will load the posts automatically
// or eager load
$user = User::with('posts')->find(3);
$user->posts; // collection of posts for given user
To sum up: hasManyThrough is a way to get nested relation directly, ie. all the posts for given country, but rather not to search for specific through model.
$user_id = 3;
$country = Country::find($country_id);
$country->posts()->where('users.id', '=', $user_id)->get();
$this->hasManyThrough('Post', 'User', 'country_id', 'user_id')->where(column,x);
What happen here is you get the collection in return you can put any condition you want at the end.

Categories