如何在 Laravel 5 中对合并的集合进行分页?

Posted

技术标签:

【中文标题】如何在 Laravel 5 中对合并的集合进行分页?【英文标题】:How can I paginate a merged collection in Laravel 5? 【发布时间】:2015-08-05 21:09:43 【问题描述】:

我正在创建一个包含两种对象的流,BluePerson 和 RedPerson。为了创建流,我获取所有两个对象,然后将它们合并到一个集合中。这样做之后,我需要对它们进行分页,但是分页似乎是针对雄辩的模型和数据库查询,而不是集合。我已经看到很多关于手动创建分页器的信息,但是文档,尤其是 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);

【问题讨论】:

它可以帮助你:***.com/a/56142421/6901246 【参考方案1】:
use Illuminate\Support\Collection;

$collection = new Collection;

$collectionA = ModelA::all();
$collectionB = ModelB::all();

$merged_collection = $collectionA->merge($collectionB);

foreach ($merged_collection as $item) 

    $collection->push($item);


$paginated_collection = $collection->paginate(10);

【讨论】:

【参考方案2】:

似乎分页不再是laravel 8中集合的一部分,所以我使用了laravel的Illuminate\Pagination\Paginator类来对数据进行分页,但是出现了问题,分页相关信息通过分页更新但数据确实根本不分页!

我已经找到问题了,laravel的Paginator类没有正确分页数据,可以查看该类的原始方法。

/**
 * Set the items for the paginator.
 *
 * @param  mixed  $items
 * @return void
 */
protected function setItems($items)

    $this->items = $items instanceof Collection ? $items : Collection::make($items);

    $this->hasMore = $this->items->count() > $this->perPage;

    $this->items = $this->items->slice(0, $this->perPage);

所以,我构建了自己的 Paginator 类,并从 laravel 的 Paginator 类中扩展了它,并解决了如下所示的问题。

use Illuminate\Support\Collection;

class Paginator extends \Illuminate\Pagination\Paginator

    /**
     * Set the items for the paginator.
     *
     * @param  mixed  $items
     * @return void
     */
    protected function setItems($items)
    
        $this->items = $items instanceof Collection ? $items : Collection::make($items);

        $this->hasMore = $this->items->count() > ($this->perPage * $this->currentPage);

        $this->items = $this->items->slice(
            ($this->currentPage - 1) * $this->perPage,
            $this->perPage
        );
    

类的用法如下所示

(new Paginator(
    $items,
    $perPage = 10,
    $page = 1, [
        'path' => $request->url(),
    ]
))->toArray(),

注意:如果要使用 laravel 的 Paginatorviews,可以使用 render() 方法代替 toArray() 方法。

我的数据分页现在工作正常。

我希望这对你有用。

【讨论】:

【参考方案3】:
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Pagination\Paginator;

您可以在公共函数 boot() 内的 Providers/AppServiceProvider 中为 Collection 添加以下代码。

    // 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('');
        );
    

然后,您可以从 Collection 中调用 paginate,就像 Eloquent 模型一样。例如

$pages = collect([1, 2, 3, 4, 5, 6, 7, 8, 9])->paginate(5);

【讨论】:

这段代码太棒了。有了这个,您只需在 AppServiceProvider boot() 函数中添加下面提到的路径。使用 Illuminate\Support\Collection;使用 Illuminate\Pagination\Paginator;使用 Illuminate\Pagination\LengthAwarePaginator; 如果您不打算使用内置的 Collection::forPage(),我认为这是最“正确”的答案。它正确地使用了 Collection 数据结构,而不是简单地将所有这些放在某个独立的函数中。向@dushoda 致敬 ++ Eloquent 构建器实例上不存在属性 [id]。 Laravel 5.2 BadMethodCallException:方法 withPath 不存在。注释 ->withPath('');更改 ->setPath('') 令人惊叹的作品,正是需要的【参考方案4】:

你可以像下面这样改变:

public function index()

    $bluePerson = BluePerson::paginate();
    $redPerson = RedPerson::all();

    $people = $bluePerson->merge($redPerson)->sortByDesc('created_at');


    return view('stream.index')->with('people', $people);

【讨论】:

【参考方案5】:

我必须在我正在处理的项目中处理类似的事情,在其中一个页面中我必须显示两种类型的出版物分页created_at 字段排序。在我的例子中,它是一个 Post 模型和一个 Event 模型(以下简称 publications)。 p>

唯一的区别是我不想从数据库中获取所有出版物然后对结果进行合并和排序,因为您可以想象如果我们有数百个出版物会引发性能问题。

所以我发现对每个模型进行分页然后才对它们进行合并和排序会更方便。

这就是我所做的(基于之前发布的答案和 cmets)

首先让我向您展示一个简化版本的“我的解决方案”,然后我会尽可能多地解释代码。

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'));
    

你现在已经猜到了,facade Paginator负责合并和排序我的分页器($events & $posts

为了让这个答案更加清晰和完整,我将向您展示如何创建自己的 Facade。

您可以选择将自己的外观放在您喜欢的任何位置,我个人选择将它们放在应用文件夹下的 Facades 文件夹中,就像这棵树中显示的那样。

+---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';
    

更多信息,您可以查看How Facades Work

接下来,绑定分页器到服务容器,打开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(),
            ]
        );
    


当然还有改进的余地,所以如果您发现需要更改的地方,请在此处发表评论或通过twitter与我联系。

【讨论】:

【参考方案6】:

分页收集的最佳方式:

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)

【讨论】:

【参考方案7】:

如果您想使用 LengthAwarePaginator,只需实例化一个。如先前答案的 cmets 中所述,您必须为此设置路径。您还需要确保在实例化分页器之前解析“currentPage”并设置要返回的项目。这都可以在实例化之前/实例化时完成。所以一个函数可能看起来像:

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;

【讨论】:

这解决了我使用 laravel 5.3 的问题。我合并了 2 个集合,因为联合导致了问题,现在分页工作 +1 那怎么用呢?【参考方案8】:

您可以尝试对这两个集合进行分页并合并它们。您可以在docs 和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;
    

【讨论】:

这种方法有问题。它会重置分页器中的 URL,因此您需要使用 $paginator->setPath(request()->getPathInfo()); 将其重新设置 你也可以使用$paginator->setPath($collection1->resolveCurrentPath())的第一个集合的URL【参考方案9】:

但是 paginate 似乎是针对雄辩的模型和数据库查询,而不是集合。

你是对的。但是集合需要一个分页器功能。 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);

【讨论】:

如果您想要与 Eloquent Collection paginate() 方法类似的语法,为什么不尝试其中一种解决方案呢? gist.github.com/simonhamp/549e8821946e2c40a617c85d2cf5af5e @simonhamp 很棒的链接正是我所需要的。 虽然简单,但这种方法希望您拥有所有数据,因此它会通过获取过多的行来使数据库查询过载,而这些行目前不会显示。使用默认的paginate() 方法有一些性能更好的方法,因为它只从数据库中获取将显示在当前页面上的行(通过SKIPTAKE)。【参考方案10】:

尝试关注。

$arr = $pets->toArray();
$paginator->make($arr, count($arr), $perPage);

【讨论】:

如何使用分页器对象来获取所选页面的正确项目?

以上是关于如何在 Laravel 5 中对合并的集合进行分页?的主要内容,如果未能解决你的问题,请参考以下文章

如何在laravel中对相关模型进行分页

如何在 laravel&Vue 中对数据进行分页?

如何合并两个模型集合并使其成为一个分页然后将其发送到 Laravel 中的视图

在 Laravel 中对分页变量进行排序

分页 Laravel 集合

Laravel 5.3 对 sortBy 集合进行分页