Get filtered child from parent - php

I have a one to many relationship. So I can use this code to display all posts.
$tag = Tag::where('slug', $slug)->first();
$posts = $tag->posts;
It works correctly but I want to filter child to display. For example:
$posts = $tag::whereHas('posts', function($query){
$query->where('accept', 1)
})->get();
But it gets tags not posts. Any idea how I can solve my problem?

In Post model you have to define relation to tag like this
public function tags(){
return $this->hasMany(Tag::class);
}
and this is how you can get posts from specific tag
$slug = "my-slug";
$posts = Post::whereHas('tags', function($query) use ($slug){
$query->where('slug', $slug)
})->where('accept', 1)->get();

As mentioned by the documentation:
Since all relationships also serve as query builders, you can add further constraints to which comments are retrieved by calling the comments method and continuing to chain conditions onto the query:
$comment = App\Post::find(1)->comments()->where('title', 'foo')->first();
You can change your code to:
$posts = $tag->posts()->where('accept', 1)->get()
You can directly query the relationship.

You can use
$tag = Tag::where('slug', $slug)
->with(['posts' => function($q) {
$q->where('accept', 1);
}])->first();
$posts = $tag->posts;

Related

What to do when paginator is not allowed on a collection?

I have two entities: Post (posts) and Tag (tags). They both are in many-to-many relationship. So I have a pivot table called PostTag (post_tag). I want to list all the tags [including a) pivot table and b) post title] which belong to those posts whose author is the logged in user. So I did something like this:
$tags = collect();
$posts = Post::where('user_id', auth()->id())->with('tags')->get();
$posts->each(function($post, $key) use ($tags){
$post->tags->each(function($tag, $key) use ($tags, $post) {
$tag->post_title = $post->title;
$tags->push($tag);
});
});
return $tags;
However, I also need to paginate the result. So I attempted to return this instead:
return $tags->paginate(10);
But paginate is not a method of Collection (Maybe of Builder)
The relationship methods are:
// Post.php
public function tags() {
return $this->belongsToMany(Tag::class)->withPivot('updated_at');
}
// Tag.php
public function posts(){
return $this->belongsToMany(Post::class);
}
I have a feeling that there must be some easier way of doing it which I may not know:
PostTag::someQueryThatFetchesThoseTagsWithPostTitle();
// If I could do something like this, paginate() would have been available
Tags::query()->where('posts.user_id', auth()->id())
->join('post_tag', 'post_tag.tag_id', '=', 'tags.id')
->join('posts', 'post_tag.post_id', '=', 'posts.id')
->selectRaw('tags.*, posts.title as post_title')
->paginate(10);
You can just optimize your query in order to return what you want selecting what you need.
This should be even faster.
You can create your own pagination with LengthAwarePaginator with this piece of code I'm using in my projects sometimes.
//Get current page form url e.g. &page=6
$currentPage = LengthAwarePaginator::resolveCurrentPage();
//Number of results in pagination
$paginate = 10;
//Slice the collection to get the items to display in current page
$currentPageSearchResults = $tags->slice(($currentPage - 1) * $paginate, $paginate)->all();
//Create our paginator and pass it to the view
$paginatedSearchResults = new LengthAwarePaginator($currentPageSearchResults, $tags->count(), $paginate);
Where $paginatedSearchResults returns pagination object.

Paginating posts according to tags

When a user clicks on a tag say "mobile", I'm trying to get all the posts that are associated with that tag through this method:
public function getRelevantPostsFromTag($tag)
{
$posts = [];
$tag = Tag::where('name', '=', $tag)->first();
foreach ($tag->posts as $post) {
array_push($posts, $post);
}
return collect($posts);
}
I'm then trying to paginate the returned collection by trying to do so:
$posts = $this->postRepository->getRelevantPostsFromTag($tag);
$posts = $posts->paginate(8);
But I'm getting a method paginate does not exist error. Is the application of paginate on a custom collection disallowed?
You could do something like this to get all posts by tag name and paginate.
$posts = Post::whereHas('tags', function ($q) use ($name) {
$q->where('name', $name);
})->paginate(...);
Illuminate\Support\Collection does not have a paginate method.
If you already have a collection you can manually create a paginator and use something like Collection#forPage to help slice it for you to pass into the paginator.

Selecting and ordering data from a query using Laravel Eloquent

I am trying to use Laravel and eloquent to return results based on the following query.
$blogPosts = BlogPosts::with('blog_user', 'blog_categories', 'blog_comments', 'tags')
->orderBy('created_at', 'desc')
->paginate(5);
Ok, so that is fine, it return exactly what it should, all the blog posts with associated relations to other tables.
What I now want to do is return only the $blogPosts where a tag is clicked by the user. So let's say there is a tag "PHP", so I pass in that value as $tag to the method. I then have something like this.
public function tag_search($tag)
{
$blogPosts = BlogPosts::with('blog_user', 'blog_categories', 'blog_comments', 'tags')
->where('tags', $tag)
->orderBy('created_at', 'desc')
->paginate(5);
$categories = BlogCategories::with('blog_posts')->get();
$data = array('blogPosts' => $blogPosts, 'categories' => $categories,
);
return view('blog.index')->with($data);
}
Now my issue is actually relatively simple I guess, if the where clause was a column in the BlogPosts table it would work, I know this because I tried that.
However the above won't work as is, I can only use;
->where('x', y)
Where x is a field in the BlogPosts table. I want to return a set of values where the submitted $tag is the same as one associated to the tags attached to the blog posts.
Make sense? I think I am over thinking it the point I am just not thinking now :)
Add the column field 'tags' to your table and then these queries:
$blogPosts = BlogPosts::with('blog_user', 'blog_categories', 'blog_comments', 'tags')
->where('tags', '=', $tag)
->orderBy('created_at', 'desc')
->paginate(5);
$categories = BlogCategories::with('blog_posts')->get();
return view('blog.index', compact ('categories', 'blogposts'));
Ok so the answer was to use whereHas like this.
$blogPosts = BlogPosts::whereHas('tags', function ($query) use ($tag) {
$query->where('name', $tag);
})->orderBy('created_at', 'desc')
->paginate(5);
That returns a search based on the associated elements.

How to do pagination in Many to Many in Eloquent?

I have two tables Posts and Tags with many to many relation. I need to retrieve all posts by some tag and paginate it. Is it possible to do with paginate method or I need to create a new instance of Paginator and do everything by hand?
P.S.
Need something like:
$tags = Tags::where('name', '=', $tag)->with('posts')->first();
$posts = $tags->posts->paginate(5);
return view('blog/posts')->with('posts', $posts);
You should probably use:
$posts = $tags->posts()->paginate(5);
and you should also rename $tags to $tag if you are getting only one record to not make variable name misleading.
You have to use a join statement. It should look something like this:
DB::table('tags')
->join('posts', 'tags.id', '=', 'posts.tag_id')
->where('tags.name', '=', $tag)
->paginate(5);

Query relationship Eloquent

I have News model, and News has many comments, so I did this in News model:
public function comments(){
$this->hasMany('Comment', 'news_id');
}
But I also have field trashed in comments table, and I only want to select comments that are not trashed. So trashed <> 1. So I wonder is there a way to do something like this:
$news = News::find(123);
$news->comments->where('trashed', '<>', 1); //some sort of pseudo-code
Is there a way to use above method or should I just write something like this:
$comments = Comment::where('trashed', '<>', 1)
->where('news_id', '=', $news->id)
->get();
Any of these should work for you, pick the one you like the most:
Eager-loading.
$comments = News::find(123)->with(['comments' => function ($query) {
$query->where('trashed', '<>', 1);
}])->get();
You can inject the parameter to query function by use($param) method, that allows you to use dynemic query value at runtime.
Lazy-loading
$news = News::find(123);
$comments = $news->comments()->where('trashed', '<>', 1)->get();
I couldn't help but notice, though, that what you're probably trying to do is handle soft deleting, and that Laravel has built-in functionality to help you with that: http://laravel.com/docs/eloquent#soft-deleting
You can do simply in your eloquent model file.
do like this :
public function comments_with_deleted()
{
return $this->belongsTo('Comments', 'id')->where('deleted', 1);
}
public function comments()
{
return $this->belongsTo('Comments', 'id');
}
call like this :
// for show comments with deleted
$comments = News::find(123)->with('comments_with_deleted');
// for show comments without deleted
$comments = News::find(123)->with('comments');
rmobis's answer was what I needed, but it throws an error in current Laravel 5. You have to use it as an associatve array now:
$comments = News::find(123)->with(
['comments' => function ($query) {$query->where('trashed', '<>', 1);}]
);
Took me some time to figure it out, hope this will help others.
Read more in Laravel's Docs (5.6): https://laravel.com/docs/5.6/eloquent-relationships#querying-relations

Categories