There was question about old L3 eager loaded paginations, not using eloquent. But i want to use eloquent to get eager loaded relationship with pagination.
Main model: Topic that has one to many relationship with Posts,
So that one Topic has many Posts. I get all data with this function:
public function findById($id)
{
return $this->topic->with('posts', 'posts.user', 'posts.user.profile')
->find($id);
}
And later I make loop to display all results, but they are not paginated:
#foreach($topic->posts as $post)
... unpaginated content ...
#endforeach
So, i could make a workaround and select separately all posts that has $id of topic and use ->paginate() instead of ->get() and would got paginated $pots,
but is there possibility to use eager loaded relationship posts that is paginated ? And how can it be done ?
To clarify something: paginating eager-loaded relationships is somewhat of a misconception. The point of eager loading is to retrieve all relationships in as few queries as you can. If you want to retrieve 10 topics, all of which have 35 posts, you will only need two queries. Sweet!
That said, paginating an eager-loaded relationship is not going to work. Consider two scenarios when eager loading happens:
You want to retrieve and list topics, and maybe list the first five posts for each topic. Great! Eager loading is perfect. Now, you wouldn't want to paginate the eager-loaded posts on a page like this, so it doesn't matter.
You load a single topic, and you want to paginate the posts for that topic. Great! Relatively easy to do. However, if you've already eager-loaded all posts belonging to this topic, you've just potentially retrieved a lot of extra resources that you don't need. Therefore eager loading is actually hurting you.
That said, there are two potential solutions:
Option 1: Create a custom accessor that paginates the Eloquent relationship.
/**
* Paginated posts accessor. Access via $topic->posts_paginated
*
* #return \Illuminate\Pagination\Paginator
*/
public function getPostsPaginatedAttribute()
{
return $this->posts()->paginate(10);
}
Pros: Paginates very easily; does not interfere with normal posts relationship.
Cons: Eager loading posts will not affect this accessor; running it will create two additional queries on top of the eager loaded query.
Option 2: Paginate the posts Collection returned by the eager-loaded relationship.
/**
* Paginated posts accessor. Access via $topic->posts_paginated
*
* #return \Illuminate\Pagination\Paginator
*/
public function getPostsPaginatedAttribute()
{
$posts = $this->posts;
// Note that you will need to slice the correct array elements yourself.
// The paginator class will not do that for you.
return \Paginator::make($posts, $posts->count(), 10);
}
Pros: Uses the eager-loaded relationship; creates no additional queries.
Cons: Must retrieve ALL elements regardless of current page (slow); have to build the current-page elements manually.
You may want to take a look at following:
$category = 'Laravel';
$posts = Post::with('user', 'category')->whereHas('category', function($query) use ($category) {
$query->where('name', '=', $category);
})->paginate();
By Zenry :
Source:http://laravel.io/forum/02-25-2014-eager-loading-with-constrains-load-post-based-on-category-name
Hope this helps
Related
In my case, I have a user, who has many posts that each have comments and my goal is to retrieve only a collection of comments through the use of eloquent relationships (without creating a custom collection and looping, if possible).
Current I have this:
$comments = $user->posts()->with('comments')->get();
This returns a collection of posts with related comments in each of them, however I wish just a collection of comments.
I hope this makes sense - thanks!
Consider a Many-to-Many relationship and a Form that is showing the relationship as a Collection.
I want to only render entities of that relation that match a given criteria.
Example:
Category and Products are in Many-to-Many.
When editing the Category I only want to provide Products that are currently available.
When I add another Product I need to update all Products. I have the updated list of Products with available Products only and I have a list of all Products, now I have to merge these Products and I don't know how to do this efficient.
Dirty Solution
I have a possible solution in mind, however it is not a nice solution.
Add two methods getAvailableProducts() and setAvailableProducts(). However, the setAvailableProducts() method involves a lot of computation power, since you need to compare it to the $availableProducts.
Isn't there a better solution?
You can filter doctrine collections by using Criteria. This is explained in detail in the documentation chapter 8.8. Filtering Collections.
You use the class Doctrine\Common\Collections\Criteria to define the conditions of your filtering and then you can do like this:
$filteredCollection = $collection->matching($criteria);
Another option would be to left-join with conditions in your entity repository. In that case the collection will already be hydrated with only the members according to the conditions in your query.
Update
You could also simply make a special method getAvailableProductsForCategory in your ProductRepository where you pass a category as a parameter to the method.
class ProductRepository extends EntityRepository
{
public function getAvailableProductsForCategory(Category $category)
{
$products = $this->createQueryBuilder('p')
->leftJoin('p.categories', 'c')
->where('p.available = true')
->andWhere('c = :category_id')
->setParameters(
array(
'category_id' => $category->getId(),
)
)
->getQuery()
->getResult();
return $products;
}
}
I'm going through my laravel application and trying to fix any n+ issues I can find. I have come across one scenario which isn't really an n+ but not sure what to call it.
I have 2 models Post, Comment. A post has many comments and a comment belongs to a post
When I loop through all my posts I would like to display a count of how many comments they contain. I've been able to do this fine. But the problem it is 2 queries.
How do I update the following Eloquent query to add a column for comments count.
Post::where('status', 1)->get();
Thanks
Update
As of Laravel 5.2.32, a new method was added to the query builder to help with this. When you add the withCount($relation) method to your query, it will add a {relation}_count field to the results, which contains the count of the supplied relation.
So, your query would be:
$posts = Post::where('status', 1)->withCount('comments')->get();
foreach($posts as $post) {
echo $post->comments_count;
}
You can read more in the documentation here.
Original
#JarekTkaczyk has a good blog post that does what you're looking for. Check out the article here.
Basically, you'll be creating a relationship that contains the count of comments for the post, and you'll eager load the relationship (thus avoiding the n+1). He also has some syntactic sugar in there for accessing the count through an attribute accessor.
Either just use count on the relationship, or if you think it's necessary, you could add a 'num_comments' to the Post model and increment it on the creation of a comment:
$post->comments()->count();
or in the comments model:
public function create( $commentData ){
$result = $this->fill( $commentData );
$this->post()->increment('num_comments');
return $result;
}
I need to be able to specify conditions on a relationship (one-to-many).
Example being; We have a Post which has many Comments, but I only want the Posts with Comments made by User A and then return the Post object(s).
The only way I can currently think of doing this is with a Fluent query, which wouldn't return the Post object I desire!
EDIT:
The comment has to of been made by User A. The relationship of the User who made the Comment to Post isn't a direct one. It would go through the Comment table.
RE EDIT:
Would it also be possible to say have distinct Posts? Rather than return 3 of the same Post Object?
You can query relationships. You would end up with something like this:
$postsWithComments = $user->posts()->has('comments', '>=', 1)->get();
Here is an extract from documentation: http://laravel.com/docs/eloquent
Querying Relations
When accessing the records for a model, you may wish to limit your results based on the existence of a relationship. For example, you wish to pull all blog posts that have at least one comment. To do so, you may use the has method:
Checking Relations When Selecting
$posts = Post::has('comments')->get();
You may also specify an operator and a count:
$posts = Post::has('comments', '>=', 3)->get();
With Laravel and the eloquent ORM, I want to create an array or object of all posts and corresponding comments that belong to a specific user (the logged in one). The result will then be used with Response::eloquent(); to return JSON.
Basically in pseudo-code:
All Posts by user ::with('comments').
or
Posts by Auth::user()->id ::with('comments').
I have my database setup per the usual with a user's table, comments table and posts table. The comments table has a post_id and the posts table has a user_id.
The long way of doing this without Laravel would be something like:
SELECT * FROM posts WHERE user_id = 'user_id'
foreach($result as $post) {
SELECT * FROM comments WHERE posts_id = $post->id
foreach($query as $comment) {
$result[$i]->comments[$n] = $comment
}
}
But I want to accomplish it with Laravel's Eloquent ORM.
It looks like you don't even need a nested eager load, you just need to modify the query that with returns, so:
$posts = Post::with('comments')->where('user_id', '=', 1)->get();
You can daisy chain most of the methods in the Eloquent system, generally they're just returning a Fluent query object.
(I haven't tested it but I'm fairly certain that'll work. Also, you can't do it on ::all() because that calls ->get() for you. You have to dig in the source code to find this, I don't think the Eloquent documentation mentions that's what it's doing.)
Also the Eager Loading Documentation covers nested eager loading, so you could load all users, with their posts, with the comments:
You may even eager load nested relationships. For example, let's
assume our Author model has a "contacts" relationship. We can eager
load both of the relationships from our Book model like so:
$books = Book::with(array('author', 'author.contacts'))->get();