How can I paginate a merged collection in Laravel 5?

后端 未结 7 1538
温柔的废话
温柔的废话 2020-12-02 14:31

I am creating a stream which contains two types of objects, BluePerson and RedPerson. To create the stream, I fetch all of both objects, then merge them into one collection.

7条回答
  •  生来不讨喜
    2020-12-02 15:09

    I had to deal with something like that in a project i was working on, where in one of the pages i had to display two type of publication paginated and sorted by the created_at field. In my case it was a Post model and an Event Model (hereinafter referred to as publications).

    The only difference is i didn't want to get all the publications from database then merge and sort the results, as you can imagine it would rise a performance issue if we have hundreds of publications.

    So i figure out that it would be more convenient to paginate each model and only then, merge and sort them.

    So here is what i did (based on answers and comments posted earlier)

    First of all let me show you a simplified version of "my solution", then i will try to explain the code as much as i could.

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

    As you can guess it by now, the facade Paginator is the responsible of merging and sorting my paginators ($events & $posts)

    To make this answer a little bit more clear and complete, i will show you how to create your own Facade.

    You can choose to put your own facades anywhere you like, personally, i choose to put them inside Facades folder under the app folder, just like shown in this tree.

    +---app
    |   +---Console
    |   +---Events
    |   +---Exceptions
    |   +---Exports
    |   +---Facades
    |   |   +---Paginator.php
    |   |   +---...
    |   +---Http
    |   |   +---Controllers
    .   .   +---...
    .   .   .
    

    Put this code inside 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';
        }
    }
    

    For more info, you can see How Facades Work

    Next, bind paginator to service container, open 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;
            });
        }
    }
    

    For more info, you can see The Boot Method

    My Paginator class is under app/Services/Pagination/ folder. Again, you can put your classes wherever you like.

    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(),
                ]
            );
        }
    }
    

    Definitely there is room for improvements, so please if you see something that needs to be changed, leave a comment here or reach me on twitter.

提交回复
热议问题