How to paginate a collection after get() in Laravel? - laravel

I have a merge on two query results which works fine:
$events1 = \App\Event::Where('valid_to','>=',$today)->orderByRaw('valid_to','ASC')->get();
$events2 = \App\Event::Where('valid_to','<',$today)>orderByRaw('valid_to','ASC')->get();
$events = $events1->merge($events2);
Now I need to paginate this new collection and as suggestted I added this piece:
$page = 1;
$perPage = 60;
$pagination = new \Illuminate\Pagination\LengthAwarePaginator(
$events->forPage($page, $perPage),
$events->count(),
$perPage,
$page
);
EDIT: For future readers, patricus's answer works great and I did that.

best way for paginate collection:
1- add this to boot function in \app\Providers\AppServiceProvider
/**
* Paginate a standard Laravel Collection.
*
* #param int $perPage
* #param int $total
* #param int $page
* #param string $pageName
* #return array
*/
Collection::macro('paginate', function($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,
]
);
});
2-From hereafter for all collection you can paginate like this
$events1 = \App\Event::Where('valid_to','>=',$today)->orderByRaw('valid_to','ASC')->get();
$events2 = \App\Event::Where('valid_to','<',$today)>orderByRaw('valid_to','ASC')->get();
$events = $events1->merge($events2);
$events->paginate(5)

You're calling links() and render() on the wrong object. You've assigned the paginator to the $pagination variable. You should be calling
$pagination->links()
or
$pagination->render()
Also, if you'd like to clean this up a little bit, you can modify your query so that you only have one query and don't need to combine two different result sets. You just need to first order on the result of the date comparison, and then order on your valid_to field.
$events = \App\Event::orderByRaw('valid_to < ?', [$today])->orderBy('valid_to')->get();
The date comparison will return a true/false result. In ASC order (default when not specified), true results will come after false results, so rows where the valid_to is less than $today (expired) will come after the rows where valid_to is greater than or equal to $today.
That result set will then be ordered by the valid_to field itself. This one query gives you the same results as the two queries you've manually merged. And, of course, you can just paginate this one query:
$events = \App\Event::orderByRaw('valid_to < ?', [$today])->orderBy('valid_to')->paginate(60);
Now, it is your $events object that is paginated, so you would want to use $events->links() and $events->render().

Related

Laravel 8 Paginate Collection (sortBy)

I try to paginate a sorted collection in Laravel 8, maybee any one have an idea?
That's my code:
$where = Business::where('name', 'LIKE', '%' . $what . '%');
$businesses = $where->get()->sortByDesc(function($business) {
return $business->totalReviews;
})->paginate(10); // <--- not working.. Collection::paginate not exists
Paginate can only be called on a builder instance (it makes no sense to call it on a collection as you already have all the data). But you are doing some logic based on the review count that requires a model method which must can only be called after fetching the data.
So you must refactor the ordering so that it gets called on the builder instance so that the ordering happens on SQL before the pagination logic happens.
withCount('relation') is perfect for this as it will append on a count of a specific relation onto your query which you can then sort by on SQL.
For example you can try this where reviews is a relation on the Business model that you have many of (likely either belongsToMany or hasMany):
Business::withCount('reviews')
->where('name', 'LIKE', '%' . $what . '%')
->orderBy('reviews_count', 'desc')
->paginate(10);
Where inside your Business model you have:
public function reviews()
{
return $this->hasMany(Review::class);
}
Remove the get:
$businesses = $where->sortByDesc(function($business) {
return $business->totalReviews;
})->paginate(10);
I fixed it on this way
$businesses = $where->get()->sortByDesc(function($business) {
return $business->getTotalReviews();
});
$businesses = ViewHelper::paginate($businesses, 10);
ViewHelper.class
<?php
namespace App\Classes;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Database\Eloquent\Collection;
class ViewHelper
{
/**
* Gera a paginação dos itens de um array ou collection.
*
* #param array|Collection $items
* #param int $perPage
* #param int $page
* #param array $options
*
* #return LengthAwarePaginator
*/
public static function paginate($items, $perPage = 15, $page = null, $options = [])
{
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
$items = $items instanceof Collection ? $items : Collection::make($items);
return new LengthAwarePaginator($items->forPage($page, $perPage), $items->count(), $perPage, $page, $options);
}
}

Laravel alternative to paginate on collection?

On my website, I have Submissions, and submissions can have comments.
Comments can have upvotes and downvotes, leading to a total "score" for the comment.
In this example, before passing the comments to the view, I sort them by score.
$comments = Comment::where('submission_id', $submission->id)->where('parent_id', NULL)->get();
$comments = $comments->sortByDesc(function($comment){
return count($comment['upvotes']) - count($comment['downvotes']);
});
This works fine. The higher the score of a comment, the higher it is sorted.
However, I want to paginate these results.
If I do ->paginate(10) instead get(), the following sortByDesc will only sort those 10 results.
So logically I would want to add the paginator after the sortByDesc like so:
$comments = $comments->sortByDesc(function($comment){
return count($comment['upvotes']) - count($comment['downvotes']);
})->paginate(10);
However this will return the error:
Method Illuminate\Database\Eloquent\Collection::paginate does not
exist.
as expected.
My question is, what is the alternative to using paginate in this situation?
EDIT:
When trying the response of #party-ring (and switching the double quotes and single quotes) I get the following error:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an
error in your SQL syntax; check the manual that corresponds to your
MariaDB server version for the right syntax to use near '["upvotes"])
- count($comment["downvotes"]) desc limit 10 offset 0' at line 1 (SQL: select * from comments where submission_id = 1 and parent_id is
null order by count($comment["upvotes"]) -
count($comment["downvotes"]) desc limit 10 offset 0)
You are trying to paginate after the get, the solution i try on my website is this and it works
$users = User::where('votes', '>', 100)->get();
$page = Input::get('page', 1); // Get the ?page=1 from the url
$perPage = 15; // Number of items per page
$offset = ($page * $perPage) - $perPage;
return new LengthAwarePaginator(
array_slice($users->toArray(), $offset, $perPage, true), // Only grab the items we need
count($users), // Total items
$perPage, // Items per page
$page, // Current page
['path' => $request->url(), 'query' => $request->query()] // We need this so we can keep all old query parameters from the url
);
You could add a macro:
if (!Collection::hasMacro('paginate')) {
Collection::macro('paginate', function ($perPage = 25, $page = null, $options = []) {
$options['path'] = $options['path'] ?? request()->path();
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
return new LengthAwarePaginator(
$this->forPage($page, $perPage)->values(),
$this->count(),
$perPage,
$page,
$options
);
});
}
Then you can use a collection to paginate your items:
collect([1,2,3,4,5,6,7,8,9,10])->paginate(5);
See Extending Collections under Introduction
Give this a try:
$comments = Comment::where('submission_id', $submission->id)
->where('parent_id', NULL)
->orderBy(DB::raw("count($comment['upvotes']) - count($comment['downvotes'])"), 'desc')
->paginate(10);`
SortBy returns a Collection, whereas you can only call paginate on an instance of QueryBuilder. OrderBy should return an instance of QueryBuilder, and you should be able to do the subtraction using a DB::raw statement.
** edit
I have just read about orderByRaw, which might be useful in this scenario:
$comments = Comment::where('submission_id', $submission->id)
->where('parent_id', NULL)
->orderByRaw('(upvotes - downvotes) desc')
->paginate(10);`
You might have to play around a bit with your subtraction above as I don't know the structure of your comments table.
A couple of links which might be useful:
laravel orderByRaw() on the query builder
https://laraveldaily.com/know-orderbyraw-eloquent/

How to use Paginator with pluck method in Laravel?

I have several columns that are plucked out of database and displayed in a table.I have been trying to paginate the table.Is there a way where I could paginate each and every column values that are plucked out of database?
return $this-> orderByDesc('date_added')->whereNull('deleted_at')->pluck('program');
Create a Paginator instance with the query results and return that to your view with the table:
$page = 1;
$perPage = 10;
$options = [];
$items = $this-> orderByDesc('date_added')->whereNull('deleted_at')->pluck('program');
return view('some.table', [
'items' => new LengthAwarePaginator($items->take($perPage), $items->count(), $perPage, $page, $options)
]);
$page_no = 1;
$no_of_data = 10;
$offset = ($page_no * $no_of_data) - $no_of_data;
return $this-> orderByDesc('date_added')->whereNull('deleted_at')->offset($offset)->limit($no_of_data)->pluck('program');

Laravel Method paginate does not exist

I am trying to paginate Model result, but I am getting "Method paginate does not exist.". Here is my code:
$user_dispatches = Dispatch::all()->where('user_id', Auth::id())->paginate(10);
I need to get all records where users id equals current authenticated users id. Works well without paginate() method.
Extending a bit Alexey's perfect answer :
Dispatch::all() => Returns a Collection
Dispatch::all()->where() => Returns a Collection
Dispatch::where() => Returns a Query
Dispatch::where()->get() => Returns a Collection
Dispatch::where()->get()->where() => Returns a Collection
You can only invoke "paginate" on a Query, not on a Collection.
And yes, it is totally confusing to have a where function for both Queries and Collections, working as close as they do, but it is what it is.
You need to remove all():
Dispatch::where('user_id', Auth::id())->paginate(10);
When you're using all() you get all the rows from the table and get a collection. Then you're using collection method where() (and not Query Builder method where()) and then you're trying to use paginate() method on the collection and it doesn't exist.
for use all recorde and pagination , you need use below code :
$user_dispatches = Disspath::paginate(8);
You need remove method all() :
$user_dispatches = Dispatch::where('user_id', Auth::id())->paginate(10);
Because all() return a Collection while paginate() used a Builder
The method toQuery() changes a collection to query:
$pacientes = Paciente::get()->toQuery()->paginate(20);
Dispatch::where('user_id', auth()->user()->id)->paginate(10);
You can create own custom class:
<?php
namespace App\CustomClasses;
use Illuminate\Container\Container;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
class ColectionPaginate
{
public static function paginate(Collection $results, $pageSize)
{
$page = Paginator::resolveCurrentPage('page');
$total = $results->count();
return self::paginator($results->forPage($page, $pageSize), $total, $pageSize, $page, [
'path' => Paginator::resolveCurrentPath(),
'pageName' => 'page',
]);
}
/**
* Create a new length-aware paginator instance.
*
* #param \Illuminate\Support\Collection $items
* #param int $total
* #param int $perPage
* #param int $currentPage
* #param array $options
* #return \Illuminate\Pagination\LengthAwarePaginator
*/
protected static function paginator($items, $total, $perPage, $currentPage, $options)
{
return Container::getInstance()->makeWith(LengthAwarePaginator::class, compact(
'items', 'total', 'perPage', 'currentPage', 'options'
));
}
}
and then use it:
use App\CustomClasses\ColectionPaginate;
...
$result = $query->limit(100)->get();
$paginatedResult = ColectionPaginate::paginate($result, 10);
**Solved
To Clarify the solution from above
Change...
$user_dispatches = Dispatch::all()->where('user_id', Auth::id())->paginate(10);
to
$user_dispatches = Dispatch::where('user_id', Auth::id())->paginate(10)
In another project I was attempting to return a view with my posts array and I was also able to paginate like this...In PostController
public function index()
{
$posts = Post::where('user_id', Auth::id());
return view('admin.posts.index', ['posts'=>$posts->paginate(5)]);
}

How to use WHERE in yii2 joinWith() that is doing eager loading

I have tables: document and document_content. One document can have many contents.
I am using joinWith() method to get data from document_content table together with document using model relations.
The queries executed are these :
SELECT document.* FROM document INNER JOIN document_content ON document.id = document_content.document_id WHERE (lang='1') ORDER BY id DESC LIMIT 10
SELECT * FROM document_content WHERE document_id IN (665566, 665034, 664961, 664918, 664910, 664898, 664896, 664893, 664882, 664880)
I have a problem with this second query. I want it to include this WHERE clause from the first one: WHERE (lang='1')
So I want yii to generate this query:
SELECT * FROM document_content WHERE (lang='1') AND document_id IN (665566, 665034, 664961, 664918, 664910, 664898, 664896, 664893, 664882, 664880)
I have managed somehow to achieve this, but I have code repetition and I do not like it. There must be some better way to do this. This is my code that works, but it's not that good I think:
/**
* Returns documents by params.
*
* #param array $params the query params.
* #return ActiveDataProvider
*/
public function findDocuments($params)
{
/** #var $query ActiveQuery */
$query = Document::find();
// store params to use in other class methods.
self::$_params = $params;
// build dynamic conditions for document table
$this->buildDocumentQuery($query);
// build dynamic conditions for document_content table
$this->buildDocumentContentQuery($query);
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
'sort' => ['defaultOrder' => ['id' => SORT_DESC]],
'pagination' => [
'pageSize' => 10,
],
]);
return $dataProvider;
}
/**
* Relation with document_content table.
*
* #return DocumentContent
*/
public function getDocumentContent()
{
$query = $this->hasMany(DocumentContent::className(), ['document_id' => 'id']);
if (isset(self::$_params['lang'])) {
$query->andFilterWhere([
'lang' => self::$_params['lang'],
]);
}
}
/**
* Method that is responsible for building query conditions for document_content table.
*
* #param object $query ActiveQuery instance.
* #return ActiveQuery
*/
public function buildDocumentContentQuery($query)
{
if (isset(self::$_params['lang'])) {
$query->innerJoinWith('documentContent');
}
return $query;
}
As you can see I am checking for params['lang'] on two places. In my relation method and in buildDocumentContentQuery() method. So I am repeating same code on two places, and lang param is not going to be the only one that I want to test, there can be 10 or more.
Basically, I had to do all of this because I could not send any params through yii2 joinWith() method. I do not know what is the best way to add WHERE to query that is generated by eager loading of joinWith(). I made it work somehow, but I think this is dirty.
Does anyone have any idea for better/cleaner solution to this problem ?
Model#Document
public function getDocuments($params)
{
/** #var $query ActiveQuery */
$query = Document::find();
$query->getDocumentContentsByLanguage($params['lang']);
}
public function getDocumentContentsByLanguage($lang = null)
{
return $this->hasMany(DocumentContent::className(), ['document_id' => 'id'])->where('lang = :lang', [':lang'=>$lang]);
}
Try this:
$query = $this
->hasMany(DocumentContent::className(), ['document_id' => 'id']);
if (isset(self::$_params['lang']) && self::$_params['lang']==1) {
$query
->joinWith('document')
->andWhere([
Document::tablename().'.lang' => self::$_params['lang']
]);
}

Resources