Consultation through relationships in laravel - laravel

I searched endlessly for a question that answered my question here and I didn't find it. My question is as follows, I have 3 models: User, Post and Comments. Where user has a relationship with one to many posting, and Post has a relationship with one to many comments as well. How can I get all of the user's comments on all posts?
Currently my solution looks like this:
Models Users:
public function comments(){
$comments = array();
foreach ($this->posts()->get() as $el) {
foreach ($el->posts()->get() as $nEl) {
array_push($comments, $nEl);
}
}
return collect($comments);
}
I would like a less expensive and native solution for laravel, if any.

On your User model, something like:
public function getComments()
{
return Comment::whereHas('posts', function ($query) {
$query->where('user_id', $this->id);
})->get();
}
Also see:
https://laravel.com/docs/6.x/eloquent-relationships#has-many-through

Related

Laravel retrieve relationship data via another relationship

I have three tables
Question table:
id
title
description
Answer table:
id
answerbody
question_id
Likes table:
id
answer_id
I want to retrieve single question details with all the answers and answer_count and their respective likes (only like_count)
I tried this
public function show($id)
{
return question::with(['answers','answers.likes'])->withCount(['answers'])->where('id',$id)-
>get();
}
How do I return likes with count?
If you need all answers for a question and along with their likes count you can call a closure inside your with() clause and load their related likes count as
Question::with(['answers'=> function ($query) {
$query->withCount('likes')
->orderBy('likes_count', 'desc');
}])
->withCount(['answers'])
->where('id',$id)
->get();
This way you can also sort the answers based on their likes count
Each question has many answers. So in the Question model add the below code :
public function answer()
{
return $this->hasMany(Answer::class);
}
Each answer has many likes. So in the Answer model add the below code :
public function likes()
{
return $this->hasMany(Like::class);
}
Now you have to define a mutator "getDataAttribute" as below in the Question model:
public function getDataAttribute()
{
return [
'question' => $this,
'answers' => $this->answers()->get(),
'answer_count' => $this->answers()->get()->count(),
'likes' => $this->answers()->likes()->get(),
];
}
Finally, anywhere you needed the question information you just need to invoke :
$question->data
to retrieve question, likes and question count.

Making a query including model's method

I've been making a query to return all those people who have answered all the questions in a certain block. I've been successfull in getting all the users who answered like this:
public function getUsers()
{
$question_ids = $this->questions->pluck('id');
return User::query()->whereHas('question_answers', function ($query) use ($question_ids) {
$query->whereIn('question_id', $question_ids);
})->get();
}
And I have a function to check if a certain user has answered all the questions in a certain block.
public function hasAnsweredAll(User $user)
{
return (boolean) ($this->questions->count() == $user->question_answers()->whereHas('question', function($query){
$query->where('question_block_id', $this->id);
})->count());
}
Now I get the users I want like the following:
$users=[];
foreach($question_block->getUsers() as $user){
if($question_block->hasAnsweredAll($user)){
$users[] = $user;
}
};
Now my question is, instead of foreach, can I involve my hasAnsweredAll() method in a whereHas() or can I avoid somehow using foreach? I've searched the net for hours now, but no luck, so any help is appreciated.
You can take a look to this article from Jonathan Reinink, he uses subqueries to solve a problem like the one you mentioned
I hope this works for you
I've managed to work out a solution for my problem. It is so easy, and I've missed this so far when reading the documentations. whereHas() can take an operation and an int as parameters, so it will return instances according to that.
So like this:
public function getUsers()
{
$question_ids = $this->questions->pluck('id');
return User::query()->whereHas('question_answers', function ($query) use ($question_ids) {
$query->whereIn('question_id', $question_ids);
}, '=', $this->questions->count())->get();
}
I don't even need the hasAnsweredAll method anymore, because only users who answered all are returned. So instead the foreach it will look like this:
$users = $question_block->getUsers();

Laravel : Get all models that their last relationship have some condition

I have two models Post and Comment, i'd like to get all posts that their last comment is active:
// Model Post
public function comments()
{
return $this->hasMany('comments');
}
//Model Comment
public function post()
{
return $this->belongsTo('post');
}
i tried this solution :
public function lastComment()
{
return $this->hasOne('comment')->latest()
}
and in my controller :
$postsWithLastActiveComment = Post::whereHas('lastComment', function($q){
$q->where('active',1);
})->all();
but in this solution if the last comment is not active Previous comment will be taken
I am not sure if there's another simpler way of doing this, but maybe you can try it with a sub-query?
$lastComment = Comment::select('active')
->whereColumn('post_id', 'posts.id')
->latest()
->limit(1)
->getQuery();
$posts = Post::select('posts.*')
->selectSub($lastComment, 'last_comment_is_active')
->having('last_comment_is_active', 1)
->get();
->latest() only orders the posts by created_at so to get only the latest comment you need ->latest()->first()
I think the code below should work !
public function comments()
{
return $this->hasMany('comments');
}
public function lastComment()
{
return $this->comments()->latest()->first();
}
Shouldn't this
$postsWithLastActiveComment = Post::whereHas('lastComment', function($q){
$q->where('active',1);
})->all();
be
$postsWithLastActiveComment = Post::whereHas('lastComment', function($q){
$q->where('active',1);
})->get();
According to your question, the Post model has many Comments. And you want to get the comment from the post where active is one and must be the lastest id.
Get the last comment like the following
public function lastComment()
{
return $this->hasOne('comment')->latest()->take(1);
}
Get all posts which had the lastComment like the following
$latestCommentPosts = Post::whereHas('lastComment')->get()
And filter the latestCommentPosts like the following
$latestCommentPosts->where('active', 1)->get()
Or, you can archive by one query like the following as well.
Post::whereHas('comments', function($q) {
$q->where('active', 1);
})->get()
Like that, you got all the latest comment with active is 1.

One To Many associate

I have a model Shop and a model Comment.
on the Comment model:
public function shop()
{
return $this->belongsTo('App\Shop', 'commentable_id');
}
The Shop model is:
public function comments() {
return $this->hasMany('App\Comment', 'commentable_id');
}
So the relationship is One To Many
I want to copy all the comments of a Shop and attach them to another Shop:
$shop = Shop::find(1);
$comments = $shop->comments()->pluck('id')->toArray(); // it works, i have all the ids of the comments
$othershop = Shop::find(2);
$othershop->comments()->associate($comments); // doesn't work
$othershop->comments()->attach($comments); // doesn't work
Anyone knows how to proceed with a One to Many situation?
You can utilize createMany method of relationship:
$othershop->comments()->createMany($shop->comments->toArray());
You can also utilize chunking if one shop can have many comments (split it into more queries to make it more memory efficient):
$shop->comments()->chunk(500, function ($comments) use ($othershop) {
$othershop->comments()->createMany($comments->toArray());
});
edit: changed my answer based on comments.
To copy comments, you first need to replicate them. Then you can associate them with their owning model:
$shop = Shop::find(1);
$otherShop = Shop::find(2);
$comments = $shop->comments()->get();
foreach ($comments as $comment) {
$tmp = $comment->replicate();
$tmp->shop()->associate($otherShop);
$tmp->save();
}

Laravel query with hasmany relationship

I'm new to laravel so forgive me for the possible noobish question. Also, I did look into all the other 'similar' questions but either they're don't reproduce the right solution or i'm honestly having a hard time wrapping my head around it.
The scenario:
I have a Post model and a Subject model. This is what they look like at the moment.
in Post.php
public function subjects() {
return $this->belongsToMany('Subject', 'posts_subjects');
}
in Subject.php
public function posts() {
return $this->belongsToMany('Post', 'posts_subjects');
}
Now, what i need to achieve is:
In case an query parameter is passed to the request(ie q=Food) i want to return only the posts that contain the subject food in their subjects relationship and none of the others. if nothing is passed, then i just show everything...
This is what my PostsController's index action looks like atm.
public function index() {
$q = Input::get('q');
$posts = Post::all();
return View::make('posts.index', ['posts' => $posts]);
}
How would i go about doing that? would greatly appreciate some help.
Thanks a lot
PS. I'm able to get all the posts with this code.
This is untested code, but it probably will work for you, if you have only one Subject returned by that query:
public function index()
{
if($q = Input::get('q'))
{
if($subject = Subject::with('posts')->where('name', $q)->first())
{
$posts = $subject->posts;
}
else
{
$posts = array();
}
}
else
{
$posts = Post::all();
}
return View::make('posts.index', ['posts' => $posts]);
}

Resources