How to use transform in paginated collection in laravel - 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;
}));

Related

How to paginate a collection and query builder in 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

Laravel filter of multiple variables from multiple models

Goodmorning
I'm trying to make a filter with multiple variables for example I want to filter my products on category (for example 'fruit') and then I want to filter on tag (for example 'sale') so as a result I get all my fruits that are on sale. I managed to write seperate filters in laravel for both category and tag, but if I leave them both active in my productsController they go against eachother. I think I have to write one function with if/else-statement but I don't know where to start. Can somebody help me with this please?
These are my functions in my productsController:
public function productsPerTag($id){
$tags = Tag::all();
$products = Product::with(['category','tag','photo'])->where(['tag_id','category_id'] ,'=', $id)->get();
return view('admin.products.index',compact('products','tags'));
}
public function productsPerCategory($id){
$categories = Category::all(); //om het speciefieke id op te vangen heb ik alle categories nodig
$products = Product::with(['category','tag','photo'])->where('category_id', '=', $id)->get();
return view('admin.products.index',compact('products','categories'));
}
These are my routes in web.php. I guess this will also have to change:
Route::get('admin/products/tag/{id}','AdminProductsController#productsPerTag')->name('admin.productsPerTag');
Route::get('admin/products/category/{id}','AdminProductsController#productsPerCategory')->name('admin.productsPerCategory');
For filter both
change your URL like
Route::get('admin/products/tag/{tag_id?}/{category_id?}','AdminProductsController#productsPerTag')->name('admin.productsPerTag');
Make your function into the controller like
public function productsPerTag($tagId = null, $categoryId = null){
$tags = Tag::all();
$categories = Category::all();
$query = Product::with(['category','tag','photo']);
if ($tagId) {
$query->where(['tag_id'] ,'=', $tagId);
}
if ($tagId) {
$query->where(['category_id'] ,'=', $categoryId);
}
$products = $query->get();
return view('admin.products.index',compact('products','tags', 'categories'));
}
You are trying to filter in your query but you pass only 1 parameter to your controller, which is not working.
1) You need to add your filters as query params in the URL, so your url will look like:
admin/products/tag/1?category_id=2
Query parameters are NOT to be put in the web.php. You use them like above when you use the URL and are optional.
2) Change your controller to accept filters:
public function productsPerTag(Request $request)
{
$categoryId = $request->input('category_id', '');
$tags = Tag::all();
$products = Product::with(['category', 'tag', 'photo'])
->where('tag_id', '=', $request->route()->parameter('id'))
->when((! empty($categoryId)), function (Builder $q) use ($categoryId) {
return $q->where('category_id', '=', $categoryId);
})
->get();
return view('admin.products.index', compact('products', 'tags'));
}
Keep in mind that while {id} is a $request->route()->parameter('id')
the query parameters are handled as $request->input('category_id') to retrieve them in controller.
Hope It will give you all you expected outcome if any modification needed let me know:
public function productList($tag_id = null , $category_id = null){
$tags = Tag::all();
$categories = Category::all();
if($tag_id && $category_id) {
$products = Product::with(['category','tag','photo'])
->where('tag_id' , $tag_id)
->where('category_id' , $category_id)
->get();
} elseif($tag_id && !$category_id) {
$products = Product::with(['category','tag','photo'])
->where('tag_id' , $tag_id)
->get();
} elseif($category_id && !$tag_id) {
$products = Product::with(['category','tag','photo'])
->where('category_id' , $category_id)
->get();
} elseif(!$category_id && !$tag_id) {
$products = Product::with(['category','tag','photo'])
->get();
}
return view('admin.products.index',compact(['products','tags','products']));
}
Route:
Route::get('admin/products/tag/{tag_id?}/{category_id?}','AdminProductsController#productsPerTag')->name('admin.productsPerTag');

Laravel Eloquent / keyBy doesn't work with related entity

Code:
public function getPosts(){
$posts = Post::with('meta','taxonomies.terms.termMeta')
->where('post_type','service')
->orderBy('post_name')
->get()
->keyBy('ID')
->toArray();
return $posts;
}
Question :
KeyBy() works when you want to change the entity(ex: Post) key but how to change the related entity key ex: I Want to key post related meta with meta_key.
screenshot
You may need to transform() each post and call the keyBy() method.
public function getPosts(){
$posts = Post::with('meta','taxonomies.terms.termMeta')
->where('post_type','service')
->orderBy('post_name')
->get()
->transform(function ($post) {
$meta = $post->meta;
$post->meta = $meta->keyBy('meta_key');
return $post;
})
->keyBy('ID')
->toArray();
return $posts;
}
Code 1 : doesn't work ,I have noticed that you can alter the $post but not the related entities (ex:meta) :
->transform(function ($post) {
$meta = $post->meta;
$post->meta = $meta->keyBy('meta_key');
return $post;
})
Code 2 : Works but it return only the meta array without the post information
->transform(function ($post) {
$meta = $post->meta;
$post->meta = $meta->keyBy('meta_key');
return $post->meta;
})

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 Eloquent get() indexed by primary key id

I often find it very useful to index my results by the primary key id.
Example:
$out = [];
$users = User::where('created_at', '>=', '2015-01-01')->get();
foreach ($users as $user) {
$out[$user->id] = $user;
}
return $out;
Is there anyway to do this in one shot with Eloquent? It's not useful to use the 0...n index.
You can accomplish this by using getDictionary() on your collection.
Like so:
$users = User::where('created_at', '>=', '2015-01-01')->get()->getDictionary();
Note: in newer version of Laravel (5.2+), getDictionary() was removed; keyBy() can be used instead:
$users = User::where('created_at', '>=', '2015-01-01')->get()->keyBy('id');
I created my own solution by having a super Model that extends Eloquent.
Full solution:
https://gist.github.com/yadakhov/741173ae893c1042973b
/**
* Where In Hashed by primary key
*
* #param array $ids
* #return array
*/
public static function whereInHash(array $ids, $column = 'primaryKey')
{
$modelName = get_called_class();
$primaryKey = static::getPrimaryKey();
if ($column === 'primaryKey') {
$column = $primaryKey;
}
$rows = $modelName::whereIn($column, $ids)->get();
$out = [];
foreach ($rows as $row) {
$out[$row->$primaryKey] = $row;
}
return $out;
}
Not with eloquent but this is potentially nicer option than looping through all the results.
$users = Users::all();
return array_combine($users->modelKeys(), $users);
You can use keyBy()
$users = User::where('created_at', '>=', '2015-01-01')->get()->keyBy('id')->toArray();

Resources