Order query by relationship count laravel 4.2 - php

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');
}

Related

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

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');
}

Laravel 5.4 - Return single row from joined table

I have model Album (table albums) and model Photo (table photos). When I'm fetching all albums to page, I would like to display first photo from each album as album image.
Album model:
public function photos()
{
return $this->hasMany('App\Photo');
}
Photo model:
public function album()
{
return $this->belongsTo('App\Album');
}
Query:
public function getAlbums()
{
return DB::table('albums')
->join('photos', 'albums.id', '=', 'photos.album_id')
->select('photos.title', 'albums.title AS album_title', 'albums.slug AS album_slug')
->get();
}
It returns all photos with album title. How to modify the query to return only first photo from each album? How to use LIMIT 1 in joined table? Or is there another way to do this? Thanx.
Reslut
Why not do things the Laravel way instead of raw DB queries? There's a trick to doing exactly this. First in your Album model, add a method (I use latest() but maybe you want to do an oldest(), or some other limit described in the docs
):
public function firstPhoto() {
return $this->hasOne('App\Photo')->latest();
}
Now you can do:
public function getAlbums() {
return Albums::with('firstPhoto')->get();
}
Replace get() with first() since get returns an object instance, first will return a single instance;
public function getAlbums()
{
return DB::table('albums')
->join('photos', 'albums.id', '=', 'photos.album_id')
->select('photos.title', 'albums.title AS album_title', 'albums.slug AS album_slug')
->first()
}

Laravel Eloquent Eager loading confusion

In Laravel I have a model that looks like this:
class Recipient extends Model
{
public $table = 'recipients';
public function location()
{
return $this->belongsTo('App\Location');
}
public function teams()
{
return $this->belongsToMany('App\Team');
}
public function company()
{
return $this->belongsTo('App\Company');
}
}
To query that model I do this:
$recipients = Recipient::with('location')
->with('teams')
->where('company_id',Auth::user()->company_id)
->where('teams.id', 10)
->get();
On doing so, I get an error saying that laravel can't find teams.id, as it is only querying the parent recipient table. Wondering what I'm doing wrong, I thought the with method was to eager load / inner join records? Do I need to use a DB: inner join instead? Or am I missing something?
Use the whereHas method for this:
Recipient::with('location')
->where('company_id', auth()->user()->company_id)
->whereHas('teams', function($q){
return $q->where('id', 10);
})
->get();
try being explicit and add a select statement. Sometimes a relationship does not show up when not selected. Include the IDs else it won't work

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.)

Creating a query in Laravel by using `with` and `where`

I'm wondering it would be possible to add a where condition to a with.
Such as:
Comment::with('Users')->where('allowed', 'Y')->get();
I was trying to find a more simple way to make queries avoiding the whereHas method which looks quite verbose:
$users = Comment::whereHas('users', function($q)
{
$q->where('allowed', 'Y');
})->get();
The raw query I want internally to generate should be like so:
select * from comments, users
where users.id = comments.user_id and
users.allowed = 'Y'
I'm used to work with CakePHP in which this queries look very simple:
$this->Comments->find('all', array('Users.allowed' => 'Y'));
The relationships I have defined are:
//Comments.php
public function Users()
{
return $this->belongsTo('Users');
}
//Users.php
public function Comments(){
return $this->hasMany('Comments');
}
You may try this
$users = User::with(array('comments' => function($q)
{
$q->where('attachment', 1);
}))->get();
Update : Alternatively you may use a where clause in your relationship in your User model
// Relation for comments with attachment value 1
// and if hasMany relation is used
public function commentsWithAttachment()
{
return $this->hasMany('Comment')->where('attachment', 1);
}
// Relation for all comments
// and if hasMany relation is used
public function comments()
{
return $this->hasMany('Comment');
}
So, you can just use
// Comments with attachment value 1
User::with('commentsWithAttachment')->get();
// All comments
User::with('comments')->get();
Update : I think you want all comments with users where attachment is 1, if this what you want then it should be Comment not User
Comment::with('user')->where('attachment', 1)->get();
In this case your relation should be
public function user()
{
return $this->belongsTo('User'); // if model name is User
}
Because one comment belongs to only one user.

Categories