Laravel pagination not working with array instead of collection - laravel

I'm trying to paginate an array data set and it has proven more challenging than I thought.
I'm using Laravel 5
So I have an abstract interface/repository that all my other models extend to and I created a method inside my abstract repository call paginate.
I've included both
use Illuminate\Pagination\Paginator;
and
use Illuminate\Pagination\LengthAwarePaginator;
Here is the method
public function paginate($items,$perPage,$pageStart=1)
{
// Start displaying items from this number;
$offSet = ($pageStart * $perPage) - $perPage;
// Get only the items you need using array_slice
$itemsForCurrentPage = array_slice($items, $offSet, $perPage, true);
return new LengthAwarePaginator($itemsForCurrentPage, count($items), $perPage,Paginator::resolveCurrentPage(), array('path' => Paginator::resolveCurrentPath()));
}
So as you can imagine this function accepts an array of $items a $perPage variable that indicates how many to items to paginate and a $pageStart that indicates from which page to start.
The pagination works and I can see LengthAwarePaginator instance when I'm doing a dd() , all of it's values seem fine.
The problem starts when I'm displaying the results.
When I do {!! $instances->render() !!} The paginator links display's fine, the page parameter changes according to links but the data is not changing.
Data is the same in every page. When I'm using Eloquent for example Model::paginate(3) everything works fine, but when I dd() this LengthAwarePaginator it's identical to the LengthAwarePaginator instance of my custom paginator with the exception that it paginates an array ofcourse and not a collection.

You are not passing the current page, like you should so you also get same array. This will work
public function paginate($items,$perPage)
{
$pageStart = \Request::get('page', 1);
// Start displaying items from this number;
$offSet = ($pageStart * $perPage) - $perPage;
// Get only the items you need using array_slice
$itemsForCurrentPage = array_slice($items, $offSet, $perPage, true);
return new LengthAwarePaginator($itemsForCurrentPage, count($items), $perPage,Paginator::resolveCurrentPage(), array('path' => Paginator::resolveCurrentPath()));
}
Your function will also work if you pass correct value for $pageStart - Request::get('page', 1)

Related

Laravel 6, passing multiple arrays from a controller to a view

I'm brand new to Laravel, I need to display around 8 different drop down menus on a page all populated from tables in my Db, I am using blades.
In my controller I can create various types of arraysin one function (using eloquent) and I can dd(); them out correctly one at a time, my issue appears to be that you can only pass one array through a controller to a view. I have tried various options I found here but without success, including ->with and compact(). I have tried defining the arrays in the controller one at a time and passing them using compact() all result in errors either the variable not defined or trying to get an non-object. I am obviously going about this all wrong any help would be great.
This is not a code issue (hence no code posted) I think it more of a Laravel issue that I don't yet understand, thanks in advance.
Try like this
class YourController extends Controller{
public function yourMethod(){
$arr1 = [];
$arr2 = [];
return view('view.name', ['arr1' => $arr1, 'arr2' => $arr2]);
}
}
If you have:
$array1 = [...];
$array2 = [...];
Then you can:
return view('path.to.view', compact('array1', 'array2');
This is my route from web.php and my controller from ReservationContoller any help as to my the arrays wont pass would be great, many thanks.
Route::get('/client/{client}/reservation/{reservation}', 'ReservationController#getReservation');
public function getReservation($client, $reservation)
{
$client = Client::findOrFail($client);
$reservation = Reservation::where('client_id', $client->id)->get();
$company = Company::where('type', 'staghen')
->where('status', 'Active')
->orderBy('comp_name')
->pluck('comp_name', 'id');
$cl = array(['client' => $client]);
$res = array(['reservation' => $reservation]);
$comp = array(['company' => $company]);
return view('admin.reservations.reservation', compact('$cl', '$res', '$comp'));
}

How I can paginate DB::select(...) result and get links() method?

I have a big and difficult SQL query. It works fine for me. And I have the following code in the controller:
public function index(OpenRegDepDataReportInterface $openRegDepDataReport, Request $request): Renderable
{
$applications = $openRegDepDataReport->getReport($advertisers, $category);
return view('applications.index', compact('applications'));
}
So, the method getReport gives me a result of DB::select('<here is my big diffecult SQL>'), and, as well known it's an array.
But as you can see I'm trying to pass the result on a view. And, of course, I would like to call $applications->links() in the view, like for eloquent collection. Which is proper and faster way to do that?
doc
To display pagination at the table, you must call the select and then the pagination method.
in Controller:
$test = DB::table('users')->select('id')->paginate(10);
in View:
$test->links();
So based on the documentation if your $applications returns a Query Builder result, then just append ->paginate(10); to it.
https://laravel.com/docs/master/pagination#paginating-query-builder-results
$applications = $openRegDepDataReport->getReport($advertisers, $category)->paginate(10);
Simple answer, use paginate() method:
$basicQuery = DB::select(DB::raw("<here is the big diffcult SQL query>"));
However, paginate() works only on collections, and since you have an array of objects, you need to turn it into a collection first, using the forPage() method:
The forPage method returns a new collection containing the items that would be present on a given page number. The method accepts the page number as its first argument and the number of items to show per page as its second argument:
$collection = collect($basicQuery);
$chunk = $collection->forPage(2, 3);
$chunk->all();
Complicated answer: build a paginator instance yourself:
$perPage = 10;
$page = $request->input("page", 1);
$skip = $page * $perPage;
if($take < 1) { $take = 1; }
if($skip < 0) { $skip = 0; }
$basicQuery = DB::select(DB::raw("<here is the big diffcult SQL query>"));
$totalCount = $basicQuery->count();
$results = $basicQuery
->take($perPage)
->skip($skip)
->get();
$paginator = new \Illuminate\Pagination\LengthAwarePaginator($results, $totalCount, $take, $page);
return $paginator;
I would recommend using the paginate() method.
You can read more about Laravel's pagination.

save collection in global variable in laravel 5.6

so basically i have an index page where i have to go to controller to fetch data from database
route:-
route::get('/index/{$x}', 'indexController#index');
index controller:-
$data = Data::where('name', $x)->get();
return View::make('index', compact('data');
then inside index page i have link that goes to second page
with same data, i d not want to query the same data as it may affect performance
route:-
route::get('/second', indexController#second);});
Second Controller:-
$data = $data->sortBy('id');
return View::make('second', compact('data');
i thought of saving data in global variable in controller
so i added private variable inside controller and try to access it through $this->data
but it did not work cause based on my search the controller will be closed after it returns view
so if i try access $this->data inside second function it will be empty
is it possible to save queried data (collection) in global variable
as i do not want to query same data for every page
your help will be appreciated
If you having same data on different pages that will not be solved using any kind of global variables. You need to cache query results. Please check cache in laravel docs https://laravel.com/docs/5.6/cache
Here is how that is usually done:
$data = Cache::get('some_cache_key');
if (empty($data)) {
$data = Data::where('name', $x)->get();
Cache::put('some_cache_key', $data, 60); //60 minutes
}
return View::make('index', compact('data');
There are multiple ways to achieve this. Either you can store the data in the Session and access all across the application
$data = Data::where('name', $x)->get();
session()->put('data', $data);
You can also use View::share('key', $data)
In your AppServiceProvider inside boot() method
public function boot()
{
View::share('data', Cache::get('data'));
}
In your controller.
$data = Data::where('name', $x)->get();
Cache::put('data', $data);
By this way you can access the data all across the application using {{ $data }} in your views.
hope this helps.

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 paginate collection

I cant create a proper pagination system using laravel 4. I have the following models and function that return collections:
Model Restaurant:
public function fooditem()
{
return $this->hasMany('Fooditem','rest_id');
}
public function get_rest_foods($id){
return Restaurant::find($id)->fooditem->toArray();
}
The second function returns all food items for a certain restaurant as an array. I also use this in an API call.
in my controller i have this:
$food = $food->get_rest_foods($id);
$paginator = Paginator::make($food, 10, 5);
I pass the paginator to the view and it shows the links ok but also shows all my item from the food array.
I tried using
public function get_rest_foods($id){
return Restaurant::find($id)->fooditem->paginate(5);
}
but i get an error:
FatalErrorException: Error: Call to undefined method Illuminate\Database\Eloquent\Collection::paginate()
I searched this and many other sites but cant understant how to paginate a collection.
Thanks for your help
The paginator must get the items that it would normally get from a database query with an offset/limit statement.
So when you have a collection with all items, you should do the offset/limit yourself.
$food = $food->get_rest_foods($id);
$page = 1;
if( !empty(Input::get['page']) ) {
$page = Input::get['page'];
}
$perPage = 15;
$offset = (($page - 1) * $perPage);
$food = Paginator::make($food->slice($offset,$perPage, true)->all(), $food->count(), $perPage);
I created a subclass of Collection and implemented my own paginate method
public function paginate($perPage) {
$pagination = App::make('paginator');
$count = $this->count();
$page = $pagination->getCurrentPage($count);
$items = $this->slice(($page - 1) * $perPage, $perPage)->all();
$pagination = $pagination->make($items, $count, $perPage);
return $pagination;
}
The Laravel paginator does not do any of the splicing for you. Typically the paginator should be used hand in hand with Eloquent/Fluent. In your second example you should be doing it like this.
return Restaurant::find($id)->fooditem()->paginate(5);
Without calling the actual method you'll just be getting a collection of results and not the query builder instance.
If you want to use the paginator manually you need to pass in the spliced array (the correct items for the current page). This usually involves determining the current page, the total results and total pages. That's why, where possibly, it's best to use it with Eloquent or Fluent.

Resources