How to paginate a collection and query builder in laravel - laravel

I have +30K items in my requests table so i decided to back-end paginate the page showing the requests to user. The problem is that every user does not have permission to see all the requests and it's based on a lot of factors which i made a function that returns whether the user have permission to see a request or not.
The problem is in requests in process i have to pass every request in process to the function to get the permission ( processing request is a small set of items ). So i used the filter function on processing requests and it gives me a collection of 10 items. Now i want to merge the big set of items which is closed requests that can be +10K items with the processing ones and use the paginate feature.
How can i do that ?
This function will explain more what am trying to say :
public function getRequests(){
$closedRequests = request::join('request_logs', 'request_logs.request_id', '=', 'requests.id')
->select("requests.id", "requests.user_id", "requests.form_type", "requests.created_at", "requests.request_status")
->whereNotIn('request_status', [-2, 0])
->where('request_logs.user_id', Auth::user()->id);
$processingRequests = request::select("requests.id", "requests.user_id", "requests.form_type", "requests.created_at", "requests.request_status")
->where('request_status', 0)
->get()
->filter(function ($request) {
return FormsController::checkUserPermissionToConsultForm($request, true);
});
$closedRequests = $closedRequests->union($processingRequests)
->orderBy('created_at', 'desc')
->paginate(5);
return $closedRequests;
}
The function above is what i tried to do but it generate an error saying
Call to a member function getBindings() on array

You can create a collection and push the items to it, then use then create a class
<?php
namespace App\Support;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection as BaseCollection;
class Collection extends BaseCollection
{
public function paginate($perPage, $total = null, $page = null, $pageName = 'page')
{
$page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName);
return new LengthAwarePaginator(
$this->forPage($page, $perPage),
$total ?: $this->count(),
$perPage,
$page,
[
'path' => LengthAwarePaginator::resolveCurrentPath(),
'pageName' => $pageName,
]
);
}
}
Then you can call it like this
use App\Support\Collection;
$items = [];
$collection = (new Collection($items))->paginate(20);
This solution is from a gist I found on github. you can see it here

Related

Laravel 9 hasMany relationship not working

I have a model with a relationship with another model, when calling the relationship in the controller it gives me
Exception: Property [products] does not exist on the Eloquent builder instance.
model:
public function products(): HasMany
{
return $this->hasMany(CartProduct::class,'cart_id','id');
}
controller
public function showCartOfAuth()
{
$id =auth()->guard('customers')->user()->id;
$cart = Cart::where('customer_id',$id)->get();
$products = Cart::where('customer_id',$id)->products->get();
$response = [
'cart' => $cart,
'items' => $products
];
return response($response,200);
}
Can you try this, please: Cart::with('products')->where('customer_id',$id)->get();
The way you have implemented requires atleast two database queries.
One: $cart = Cart::where('customer_id',$id)->get();
Two: Cart::where('customer_id',$id)->first()->products; //maybe a third query to fetch the products
For better performance you should
public function showCartOfAuth()
{
$id =auth()->guard('customers')->user()->id;
// Either Option 1
// Possibly 2 database queries, one to fetch the Cart and another to fetch Products for the Cart
$cart = Cart::where('customer_id',$id)->first();
$products = $cart->products;
$response = [
'cart' => $cart,
'items' => $products
];
// OR Option 2
// One database query using eager loading
$cart = Cart::with('products')->where('customer_id', $id)->first();
$response = ['cart' => $cart];
return response($response,200);
}
If you use option 2 from above then instead of $items you can use $cart->products

laravel more than one result from single query

I am trying to get all rows and distinct column from single query. but paginate method is only giving result but not pagination option like total prev next etc..
$offers = Offer::whereHas('users', function ($q) use ($authUser) {
$q->where('user_id', $authUser->parent_id);
$q->where('publisher_id', '=', $authUser->id);
});
and distinct column
$websites = $offers->distinct()->get(['website']);
with pivot columns (just wanted to show my full query)
$offers->orderBy($sortBy, $orderBy)->paginate($perPage)->map(function ($offer) {
if (!empty($offer->users)) {
$manager = $publisher = '';
foreach ($offer->users as $user) {
$manager = $user->pivot->user_id;
$publisher = $user->pivot->publisher_id;
}
$offer->manager = $manager;
$offer->publisher = $publisher;
}
return $offer;
});
Return
return response()->json([
'offers' => $offers,
'websites' => $websites
], 200);
hope my question will make sense.
Thanks.
You should run getCollection() before mapping to get the paginator's underlying collection.
(https://laravel.com/api/7.x/Illuminate/Pagination/LengthAwarePaginator.html#method_getCollection)
$offers->orderBy($sortBy, $orderBy)->paginate($perPage)
->getCollection()
->map(function ($offer) {
// ...
return $offer;
});
I'm answering based on it being $offers:
Your usage of map() is copying the modified results of your paginate() call to a new collection and that collection does not include the pagination information. That's why you no longer have pagination information.
Since there result of paginate() is already a usable collection, you can use each() instead of map() which will alter the objects in-place.

How to use transform in paginated collection in laravel

I want to use map or transform in paginated collection in laravel 5.5 but I am struggling it work
This is what I was trying to do but getCollection is not available in LengthAwarePaginator as what we used to do in previous laravel versions see: How to transform paginated collection
$query = User::filter($request->all()
->with('applications');
$users = $query->paginate(config('app.defaults.pageSize'))
->transform(function ($user, $key) {
$user['picture'] = $user->avatar;
return $user;
});
This is what I receive but there is no pagination details in my result
How can I return transformed collection with pagination details?
For Laraval >= 8.x: if you want to perform the transform() on the collection of the paginated query builder result instead of doing the pagination on the full collection, one can use the method through():
User::filter($request->all()
->with('applications')
->paginate(config('app.defaults.pageSize'))
// through() will call transform() on the $items in the pagination object
->through(function ($user, $key) {
$user['picture'] = $user->avatar;
return $user;
});
I have ended up building custom paginate function in AppServiceProvider
use Illuminate\Support\Collection;
In register of AppServiceProvider
Collection::macro('paginate', function ($perPage, $total = null, $page = null, $pageName = 'page') {
$page = $page ?: \Illuminate\Pagination\LengthAwarePaginator::resolveCurrentPage($pageName);
return new \Illuminate\Pagination\LengthAwarePaginator(
$this->forPage($page, $perPage),
$total ?: $this->count(),
$perPage,
$page,
[
'path' => \Illuminate\Pagination\LengthAwarePaginator::resolveCurrentPath(),
'pageName' => $pageName,
]
);
});
You should paginate before retrieving the collection and transforming as follows:
$query = User::filter($request->all())->with('applications')->paginate(50);
$users = $query->getCollection()->transform(function ($user, $key) {
//your code here
});
dd($users);
It should give you your desired result.
Your problem is that you are printing the $users variable that hold the array of the users in the current page. To get the paginated list try to return/print $query instead.
So your code should be as following:
$query = User::filter($request->all()
->with('applications');
$users = $query->paginate(config('app.defaults.pageSize'))
->transform(function ($user, $key) {
$user['picture'] = $user->avatar;
return $user;
});
return response()->json($query);
Happy Coding!
You can use method setCollection from Paginator class:
https://laravel.com/api/8.x/Illuminate/Pagination/Paginator.html#method_setCollection
$messages = $chat->messages()->paginate(15);
$messages->setCollection($messages->getCollection()->transform(function($item){
$item->created = $item->created_at->formatLocalized('%d %B %Y %H:%M');
return $item;
}));

Property [***] does not exist on this collection instance Laravel eloquent relationship

In my Post Model
public function user()
{
return $this->belongsTo('App\User');
}
And in the User Model
public function posts()
{
return $this->hasMany('App\Post');
}
Now I am trying to get the comments of a specific user
$user= User::where('name', 'like', '%Mat%')->first();
return $user->posts->comment;
But it shows
Property [comment] does not exist on this collection instance.
The user has many posts which therefore returns a collection, you will need to loop over this to get your comments out. I.e.
$user = User::where('name', 'like', '%Mat%')->first();
$user->posts->each(function($post) {
echo $post->comment;
});
See the documentation on Laravel Collections
I think you can try this :
$user= User::with('post')->where('name', 'like', '%Mat%')->get();
$postComment = array();
foreach($user->post as $post){
$postComment = $post->comment;
}
return $postComment;
Hope this help for you !!!
If you want to have all comments you can use the following code:
$comments = [];
$user = User::where('name', 'like', '%Mat%')->with(['post.comment' => function($query) use (&$comments) {
$comments = $query->get();
}])->first();
return $comments;
Property [comment] does not exist on this collection instance.
The above error occurs because the Posts function returns a collection. Now you will have to traverse through each element of the collection.
Since, you are returning $user->posts()->comment, I am assuming you need it in the form of an array and don't have to simply echo them out, one by one. So you can store them all in an array & then process it whatever whay you like.
$comments = array();
$user->posts()->each(function $post){
$comments = $post->comment;
}
return $comments;
For greater insight, into this collection function read:
https://laravel.com/docs/5.4/collections#method-each

Laravel cache::remember is returing object as an array

Laravel Cache::remember is returning a LengthAwarePaginator object as an array.
function getNotifications( $userID ) {
$values = Cache::remember('cache-key', 10, function() {
$query = DB::table( 'user_notifications' )
->leftJoin( 'notifications', 'user_notifications.notification_id', '=', 'notifications.id' )
->where( 'user_notifications.user_id', $userID )
->select( 'notifications.*' )
->orderBy('created_at', 'DESC')
->paginate(5);
return $query;
});
return $values;
}
If I dd($query) before returning from Cache closure, it's returning the following object, that accepts $value->links() to display pagination.
But whenever the Cache is storing $query into $values it's returning values as an array:
I tried commenting out the unserialize-block:
/*foreach( $values as $key => $value ) :
$values[$key]->meta = self::maybeUnserialize($value->meta);
endforeach;*/
and confirmed that, that's not the cause.
I also tried, but failed:
$values = collect($values);
With multiple check and cross-check I am confirming that, the issue is the Cache::remember.
How can I force the Cache::remember return things as it is? So that I can let $object->links() work for me.
The actual code can be found here.
The issue is, Cache is made to store data, not the instance. So there are two ways to do this:
When caching, cache information per page, or
Get all the data while fetching, but make your own pagination
Solution 1:
We went for the second. But in case you need to go for the first solution, here's the code, I got from Laracasts, provided by chrisml:
$statuses = Cache::remember("statuses_{$id}_page_{$page}", 3, function() use ($event, $sort) {
return $event->statuses()
->with('comments')
->latest()
->paginate(10);
});
On the above code, the cache key is changing on every page, so the cache is storing per page.
Solution 2:
But for my case, we thought we should go for the second, and that would be wise for us, in our case. So, we've to make our own pagination. My luck that psampaz did the base for us on their blog:
Custom data pagination with Laravel 5 — by psampaz
So, instead of using ->paginate() we're fetching all the data first, and caching them as previous.
$values = Cache::remember('cache-key', 10, function() {
$query = DB::table( 'user_notifications' )
->leftJoin( 'notifications', 'user_notifications.notification_id', '=', 'notifications.id' )
->where( 'user_notifications.user_id', $userID )
->select( 'notifications.*' )
->orderBy('created_at', 'DESC')
->get(); // <----------- here
return $query;
});
But before returning the $values, we're making our own pagination. We made some fixes to the psampaz's code:
use Illuminate\Support\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
function getNotifications( $userID ) {
// Collapsed the cached values here :)
$values = Cache::remember('cache-key', 10, function() {...});
// Get current page form url e.g. &page=6.
$currentPage = LengthAwarePaginator::resolveCurrentPage();
// Get current path.
$currentPath = LengthAwarePaginator::resolveCurrentPath();
// Create a new Laravel collection from the array data.
$collection = new Collection($values);
// Define how many items we want to be visible on each page.
$perPage = 5;
// Slice the collection to get the items to display on the current page.
$results = $collection->slice(($currentPage - 1) * $perPage, $perPage)->all();
// Create our paginator and pass it to the view.
$values = new LengthAwarePaginator($results, count($collection), $perPage, $currentPage, ['path' => $currentPath]);
return $values;
}
And finally, we can easily use the $object->links() for pagination, and it's awesome! :)

Resources