How to paginate and sort results in Laravel? - laravel

I need to paginate the results and sort them using sortBy(), without losing the pagination links. I also need to use a resource to return the results.
$sorted = Model::paginate(10)->sortBy('name');
$results = \App\Http\Resources\MyResource::collection($sorted);
Doing this breaks the pagination links (I get only the data part).
$paginated = Model::paginate(10);
$results = \App\Http\Resources\MyResource::collection($paginated);
return $results->sortBy('name');
This also doesn't work.
Any ideas?

Using the getCollection() and setCollection() method in the paginator class, You can update the pagination result without losing the meta data.
$result = Post::orderBy('another_key')->paginate();
$sortedResult = $result->getCollection()->sortBy('key_name')->values();
$result->setCollection($sortedResult);
return $result;

I think you can sort the results first, and then paginate
$sorted = Model::orderBy('name')->paginate(10);

I've solved the problem with this solution:
$sorted = Model::get()
->sortBy('example_function') //appended attribute
->pluck('id')
->toArray();
$orderedIds = implode(',', $sorted);
$result = DB::table('model')
->orderByRaw(\DB::raw("FIELD(id, ".$orderedIds." )"))
->paginate(10);
I've appended example_function attribute to the model, to be used by sortBy. With this solution I was able to use orderBy to order by an appended attribute of the model. And also I was able to use pagination.

Related

Laravel simplePaginate() for Grouped Data

I have the following query.
$projects = Project::orderBy('created_at', 'desc');
$data['sorted'] = $projects->groupBy(function ($project) {
return Carbon::parse($project->created_at)->format('Y-m-d');
})->simplePaginate(5);
When I try to paginate with the simplePaginate() method I get this error.
stripos() expects parameter 1 to be string, object given
How can I paginate grouped data in this case?
The created_at attribute is already casted as a Carbon Object (by default in laravel models). that's why you are getting that error. Try this:
$projects = Project::orderBy('created_at', 'desc')->get();
$data['sorted'] = $projects->groupBy(function ($project) {
return $project->created_at->format('Y-m-d');
})->simplePaginate(5);
this answer is just for the error you're getting. now if you want help with the QueryBuilder, can you provide an example of the results you're expecting to have and an example of the database structure ?
The pagination methods should be called on queries instead of collection.
You could try:
$projects = Project::orderBy('created_at', 'desc');
$data['sorted'] = $projects->groupBy('created_at');
The problem was solved. I was create custom paginator via this example:
https://stackoverflow.com/a/30014621/6405083
$page = $request->has('page') ? $request->input('page') : 1; // Use ?page=x if given, otherwise start at 1
$numPerPage = 15; // Number of results per page
$count = Project::count(); // Get the total number of entries you'll be paging through
// Get the actual items
$projects = Project::orderBy('created_at', 'desc')
->take($numPerPage)->offset(($page-1)*$numPerPage)->get()->groupBy(function($project) {
return $project->created_at->format('Y-m-d');
});
$data['sorted'] = new Paginator($projects, $count, $numPerPage, $page, ['path' => $request->url(), 'query' => $request->query()]);
simplePaginate Method is exist in the path below:
Illuminate\Database\Eloquent\Builder.php::simplePaginate()

Laravel Add Additional Rows to an Eloquent Object

I want to add an "On This Day" feature which should display records from The Previous Years. I have some Entries, all of them have a 'date' attribute. This is what I've been trying so far:
public function filterByDay($id){
$entries = Entry::where('id', $id)->get();
$currentDay = $entries[0]->date;
$oldestYear = Entry::orderBy('date','asc')->first()->date;
$previousYear = $currentDay;
while($previousYear >= $oldestYear ){
$previousYear = $currentDay->subYear();
$entries->push(Entry::where('date', $previousYear)->get());
}
return view('home')->with(compact('entries'));
}
I must send a Collection of "Entry" type from this controller method so that I can use $entry->title etc in the view. But whenever I'm using $entries->push(...) , I'm getting a Collection instance, not Entry instance. How can I convert the Collection back into Entry instance? Or what is the alternative? I'm using Laravel 5.5. Some help will be much appreciated.
You can combine whereDay, whereYear and whereMonth methods to achieve it in one liner:
$entries = Entry::where('id', $id)->get();
$today = Carbon\Carbon::now();
$oldestYear = Entry::orderBy('date','asc')->first()->date;
$allEntries = Entry::whereDay('date', $today->day)
->whereYear('date', '>=', $oldestYear)
->whereMonth('date', $today->month)
->get();
return view('home')->with(compact('allEntries'));

Laravel Query Builder in Lumen

When I use Laravel's query builder for my Lumen application with MySQL database, it does not work as expected. I used:
$itemid = DB::table('table1')->where('UserID','=',1)->pluck('ID');
This only returns one value. What is the mistake here?
Try
$itemid = DB::table('table1')->where('UserID','=',1)->get()->pluck('ID');
Here you can read more about why this happen when you use pluck on query
Pluck together with first using Query builder
Update:
I forget that DB::table returns array, so:
$items = DB::table('table1')->where('UserID','=',1)->get();
$itemsById = array_pluck($items, 'ID');
Use first instead of get as get return array.
$itemid = DB::table('table1')->where('UserID','=',1)->first()->pluck('ID');

filtering a paginated eloquent collection

I am trying to filter a paginated eloquent collection, but whenever I use any of the collection methods, I lose the pagination.
$models = User::orderBy('first_name','asc')->paginate(20);
$models = $models->each(function($model) use ($filters) {
if(!is_null($filters['type'])) {
if($model->type == $filters['type'])
return $model;
}
if(!is_null($filters['state_id'])) {
if($model->profile->state_id == $filters['state_id'])
return $model;
}
if(!is_null($filters['city_id'])) {
if($model->profile->city_id == $filters['city_id'])
return $model;
}
});
return $models;
I am working with Laravel 4.2, is there any way to persist the pagination?
An answer to the titular question, which is possible in Laravel 5.2+:
How to filter the underlying collection of a Paginator without losing the Paginator object?
You can eject, modify and inject the collection as follows:
$someFilter = 5;
$collection = $paginator->getCollection();
$filteredCollection = $collection->filter(function($model) use ($someFilter) {
return $model->id == $someFilter;
});
$paginator->setCollection($filteredCollection);
The Paginator is built on an underlying collection, but indeed when you use any of the inherited Collection methods they return the underlying collection and not the full Paginator object: collection methods return collections for chaining together collection calls.
Expanding on mininoz's answer with your specific case:
//Start with creating your object, which will be used to query the database
$queryUser = User::query();
//Add sorting
$queryUser->orderBy('first_name','asc');
//Add Conditions
if(!is_null($filters['type'])) {
$queryUser->where('type','=',$filters['type']);
}
if(!is_null($filters['state_id'])) {
$queryUser->whereHas('profile',function($q) use ($filters){
return $q->where('state_id','=',$filters['state_id']);
});
}
if(!is_null($filters['city_id'])) {
$queryUser->whereHas('profile',function($q) use ($filters){
return $q->where('city_id','=',$filters['city_id']);
});
}
//Fetch list of results
$result = $queryUser->paginate(20);
By applying the proper conditions to your SQL query, you are limiting the amount of information that comes back to your PHP script, and hence speeding up the process.
Source: http://laravel.com/docs/4.2/eloquent#querying-relations
I have a scenario that requires that I filter on the collection, I cannot rely on the Query Builder.
My solution was to instantiate my own Paginator instance:
$records = Model::where(...)->get()->filter(...);
$page = Paginator::resolveCurrentPage() ?: 1;
$perPage = 30;
$records = new LengthAwarePaginator(
$records->forPage($page, $perPage), $records->count(), $perPage, $page, ['path' => Paginator::resolveCurrentPath()]
);
return view('index', compact('records'));
Then in my blade template:
{{ $records->links() }}
paginate() is function of Builder. If you already have Collection object then it does not have the paginate() function thus you cannot have it back easily.
One way to resolve is to have different builder where you build query so you do not need to filter it later. Eloquent query builder is quite powerful, maybe you can pull it off.
Other option is to build your own custom paginator yourself.
You can do some query on your model before do paginate.
I would like to give you some idea. I will get all users by type, sort them and do paginate at the end. The code will look like this.
$users = User::where('type', $filters['type'])->orderBy('first_name','asc')->paginate(20);
source: http://laravel.com/docs/4.2/pagination#usage
This was suitable for me;
$users = User::where('type', $filters['type'])->orderBy('first_name','asc')->paginate(20);
if($users->count() < 1){
return redirec($users->url(1));
}
A workaround when mixing search parameters and pagination, since default pagination won't keep the search parameters, using GET:
$urlSinPaginado = url()->full();
$pos=strrpos(url()->full(), 'page=');
if ($pos) {
$urlSinPaginado = substr(url()->full(), 0, $pos-1);
}
[...]
->paginate(5)
->withPath($urlSinPaginado);
sample generated pagination link: http://myhost/context/list?filtro_1=5&filtro_2=&filtro_3=&filtro_4=&filtro_5=&filtro_6&page=8
You can simply use this format in views
{!! str_replace('/?', '?', $data->appends(Input::except('page'))->render()) !!}
For newer versions of Laravel, you can use this:
$data->paginate(15)->withQueryString();

Laravel-4 Pagination in Query Builder with form inputs

Got a question if anyone can help out.
I have a query that has a collection of parameters and displays some results.
DB::table()
->join()
->where()
->orderby()
->select()
->get();
But the wheres are generated by a form input in the view. Basically this is a bunch of filters to get a table of results. I want to paginate that. if I change the get() to paginate(), and call $result->links() in the template, it does indeeed paginate and generates a bunch of results for me, however the problem is that when you move away from page 1, the links are just a _GET parameter and all the filter input does not get applied.
Is there a way I can have the pagination AND filters going at the same time? What is the laravel way of handling that? Or would I have to build some way of handling filters and pages? Any tips on that?
Cheers!
* Solution *
The solution was to make the form use GET method and persist the old filters to the Pagination using ->appends() method. Below is the modified code to do that if anyone else is looking.
$results = $query->paginate($this->report->inputs->per_page);
$query = array_except( Input::query(), Paginator::getPageName() );
$results->appends($query);
Yes, here's how I do it:
$orderBy = Input::get('orderBy', 'created_at');
$order = Input::get('order', 'DESC');
$limit = Input::get('limit', 100);
$name = Input::get('name', '');
$result = DB::table()
->where('name', 'LIKE', '%' . $name . '%')
->orderBy($orderBy, $order)
->paginate($limit);

Resources