Как я могу разбивать на страницы объединенную коллекцию в Laravel 5?
Я создаю поток, который содержит два типа объектов: BluePerson и RedPerson. Чтобы создать поток, я извлекаю все оба объекта, а затем объединим их в одну коллекцию. После этого мне нужно их разбивать на страницы, но, как представляется, для создания красноречивых моделей и запросов БД, а не для коллекций. Я много видел о создании paginator вручную, но документация, особенно в API, разрежена (я даже не могу найти аргументы, которые принимает класс Paginator.)
Как я могу разбивать на страницы результаты слияния моих коллекций?
public function index()
{
$bluePerson = BluePerson::all();
$redPerson = RedPerson::all();
$people = $bluePerson->merge($redPerson)->sortByDesc('created_at');
return view('stream.index')->with('people', $people);
}
Ответы
Ответ 1
однако paginate для красноречивых моделей и запросов БД, а не коллекций, кажется.
Вы правы. но есть функция paginator для коллекций. forPage
Синтаксис:
Collection forPage(int $page, int $perPage)
Пример:
Отдых прост.
public function foo()
{
$collection = collect([1,2,3,4,5,6,7,8,9,0]);
$items = $collection->forPage($_GET['page'], 5); //Filter the page var
dd($items);
}
Ответ 2
Если вы хотите использовать LengthAwarePaginator, просто создайте экземпляр. Как упоминалось в комментариях к предыдущему ответу, вам нужно будет установить путь для этого. Вам также необходимо убедиться, что вы разрешите "currentPage" и установите элементы, которые будут возвращены, прежде чем создавать экземпляр paginator. Все это можно сделать до/после создания экземпляра. Таким образом, функция может выглядеть примерно так:
function paginateCollection($collection, $perPage, $pageName = 'page', $fragment = null)
{
$currentPage = \Illuminate\Pagination\LengthAwarePaginator::resolveCurrentPage($pageName);
$currentPageItems = $collection->slice(($currentPage - 1) * $perPage, $perPage);
parse_str(request()->getQueryString(), $query);
unset($query[$pageName]);
$paginator = new \Illuminate\Pagination\LengthAwarePaginator(
$currentPageItems,
$collection->count(),
$perPage,
$currentPage,
[
'pageName' => $pageName,
'path' => \Illuminate\Pagination\LengthAwarePaginator::resolveCurrentPath(),
'query' => $query,
'fragment' => $fragment
]
);
return $paginator;
}
Ответ 3
Вы можете попробовать разбить оба сета на группы и объединить их. Вы можете найти больше информации о нумерации страниц в документации и API. Вот пример того, как вручную создать свой собственный пагинатор...
$perPage = 20;
$blue = BluePerson::paginate($perPage / 2);
$red = RedPerson::paginate($perPage - count($blue));
$people = PaginationMerger::merge($blue, $red);
Я включил класс PaginationMerger ниже.
use Illuminate\Pagination\LengthAwarePaginator;
class PaginationMerger
{
/**
* Merges two pagination instances
*
* @param Illuminate\Pagination\LengthAwarePaginator $collection1
* @param Illuminate\Pagination\LengthAwarePaginator $collection2
* @return Illuminate\Pagination\LengthAwarePaginator
*/
static public function merge(LengthAwarePaginator $collection1, LengthAwarePaginator $collection2)
{
$total = $collection1->total() + $collection2->total();
$perPage = $collection1->perPage() + $collection2->perPage();
$items = array_merge($collection1->items(), $collection2->items());
$paginator = new LengthAwarePaginator($items, $total, $perPage);
return $paginator;
}
}
Ответ 4
Вы можете добавить следующий код для Collection в Providers/AppServiceProvider.
// Enable pagination
if (!Collection::hasMacro('paginate')) {
Collection::macro('paginate',
function ($perPage = 15, $page = null, $options = []) {
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
return (new LengthAwarePaginator(
$this->forPage($page, $perPage)->values()->all(), $this->count(), $perPage, $page, $options))
->withPath('');
});
}
Затем вы можете вызывать paginate из коллекции, как модель Eloquent. Например
$pages = collect([1, 2, 3, 4, 5, 6, 7, 8, 9])->paginate(5);
Ответ 5
лучший способ для сбора страниц:
1- добавьте это в загрузочную функцию в \app\Providers\AppServiceProvider
/*
* use Illuminate\Support\Collection;
* use Illuminate\Pagination\LengthAwarePaginator;
*
* 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-Отныне для всей коллекции вы можете разбить на страницы как ваш код
$people->paginate(5)
Ответ 6
Попробуйте следующее.
$arr = $pets->toArray();
$paginator->make($arr, count($arr), $perPage);
Ответ 7
Мне приходилось иметь дело с чем-то подобным в проекте, над которым я работал, где на одной из страниц мне приходилось отображать публикации двух типов: с разбивкой по страницам и , отсортированные по по полю create_at. В моем случае это была модель Post и Event (далее именуемые публикациями).
Единственное отличие состоит в том, что я не хотел получать все публикации из базы данных, а затем объединять и сортировать результаты, так как вы можете себе представить, что это повысит производительность, если у нас будут сотни публикаций.
Поэтому я понял, что было бы удобнее разбивать каждую модель на страницы и только затем объединять и сортировать их.
Итак, вот что я сделал (основываясь на ответах и комментариях, опубликованных ранее)
Прежде всего позвольте мне показать вам упрощенную версию "моего решения", а затем я постараюсь объяснить код как можно больше.
use App\Models\Post;
use App\Models\Event;
use App\Facades\Paginator;
class PublicationsController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$events = Event::latest()->paginate(5);
$posts = Post::latest()->paginate(5);
$publications = Paginator::merge($events, $posts)->sortByDesc('created_at')->get();
return view('publications.index', compact('publications'));
}
}
Как вы уже можете догадаться, фасадная страница отвечает за слияние и сортировку моих страниц ($events
и $posts
)
Чтобы сделать этот ответ немного более четким и полным, я покажу вам, как создать свой собственный фасад.
Вы можете разместить собственные фасады где угодно, лично я выберу их в папке "Фасады" в папке приложения, как показано в этом дереве.
+---app
| +---Console
| +---Events
| +---Exceptions
| +---Exports
| +---Facades
| | +---Paginator.php
| | +---...
| +---Http
| | +---Controllers
. . +---...
. . .
Поместите этот код внутрь app/Facades/Paginator.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class Paginator extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'paginator';
}
}
Для получения дополнительной информации вы можете посмотреть как работают фасады
Затем, привяжите paginator к служебному контейнеру, откройте app\Providers\AppServiceProvider.php
namespace App\Providers;
use App\Services\Pagination\Paginator;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
$this->app->bind('paginator', function ($app) {
return new Paginator;
});
}
}
Для получения дополнительной информации см. The Boot Method
.Мой класс Paginator находится в папке app/Services/Pagination/
. Опять же, вы можете размещать свои классы где угодно.
namespace App\Services\Pagination;
use Illuminate\Support\Arr;
use InvalidArgumentException;
use Illuminate\Support\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
class Paginator
{
/**
* All of the items being paginated.
*
* @var \Illuminate\Support\Collection
*/
protected $items;
/**
* The number of items to be shown per page.
*
* @var int
*/
protected $perPage;
/**
* The total number of items before slicing.
*
* @var int
*/
protected $total;
/**
* The base path to assign to all URLs.
*
* @var string
*/
protected $path = '/';
/**
* Merge paginator instances
*
* @param mixed $paginators
* @param bool $descending
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
function merge($paginators)
{
$paginators = is_array($paginators) ? $paginators : func_get_args();
foreach ($paginators as $paginator) {
if (!$paginator instanceof LengthAwarePaginator) {
throw new InvalidArgumentException("Only LengthAwarePaginator may be merged.");
}
}
$total = array_reduce($paginators, function($carry, $paginator) {
return $paginator->total();
}, 0);
$perPage = array_reduce($paginators, function($carry, $paginator) {
return $paginator->perPage();
}, 0);
$items = array_map(function($paginator) {
return $paginator->items();
}, $paginators);
$items = Arr::flatten($items);
$items = Collection::make($items);
$this->items = $items;
$this->perPage = $perPage;
$this->total = $total;
return $this;
}
/**
* Sort the collection using the given callback.
*
* @param callable|string $callback
* @param int $options
* @param bool $descending
* @return static
*/
public function sortBy($callback, $options = SORT_REGULAR, $descending = false)
{
$this->items = $this->items->sortBy($callback, $options, $descending);
return $this;
}
/**
* Sort the collection in descending order using the given callback.
*
* @param callable|string $callback
* @param int $options
* @return static
*/
public function sortByDesc($callback, $options = SORT_REGULAR)
{
return $this->sortBy($callback, $options, true);
}
/**
* Get paginator
*
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
public function get()
{
return new LengthAwarePaginator(
$this->items,
$this->total,
$this->perPage,
LengthAwarePaginator::resolveCurrentPage(),
[
'path' => LengthAwarePaginator::resolveCurrentPath(),
]
);
}
}
Определенно, есть место для улучшений, поэтому, если вы видите что-то, что нужно изменить, оставьте комментарий здесь или свяжитесь со мной в твиттере.