Octane環境下ではLaravelとLivewireのページネーションを併用しにくい


バージョン

WithPagination

Livewireのページネーションは普通に使うだけなら変わったことしてないように見えるけど詳しく見るとinitializeWithPagination()でLivewire独自の動作に変更している。

        if (class_exists(CursorPaginator::class)) {
            CursorPaginator::currentCursorResolver(function ($pageName){
                if (! isset($this->paginators[$pageName])) {
                    $this->paginators[$pageName] = request()->query($pageName, '');
                }
                return Cursor::fromEncoded($this->paginators[$pageName]);
            });
        }

        Paginator::currentPageResolver(function ($pageName) {
            if (! isset($this->paginators[$pageName])) {
                $this->paginators[$pageName] = request()->query($pageName, 1);
            }

            return (int) $this->paginators[$pageName];
        });

        Paginator::defaultView($this->paginationView());
        Paginator::defaultSimpleView($this->paginationSimpleView());

Paginator::currentPageResolverPaginator::defaultViewはここでstaticプロパティを変更している。

    /**
     * The current page resolver callback.
     *
     * @var \Closure
     */
    protected static $currentPageResolver;
    
    /**
     * Set the current page resolver callback.
     *
     * @param  \Closure  $resolver
     * @return void
     */
    public static function currentPageResolver(Closure $resolver)
    {
        static::$currentPageResolver = $resolver;
    }

Octaneだとstaticプロパティなことが困る原因。

  1. Livewireのページネーションを先に表示。この時にstaticプロパティが上書きされる。Octaneでは次のリクエストでも上書きされたまま。
  2. Laravelの通常のページネーションを表示しても上書きされたstaticプロパティを元に表示されるのでエラーになる。Octaneでなければ毎回リセットされるので影響はない。

試行錯誤

viewが違うのは

表示時に指定すればいいけど全部で指定は面倒。

{{ $posts->links('pagination::tailwind') }}

AppServiceProvider::boot()ではLaravelページネーションを表示時にstaticプロパティを再度上書きはできそうにない。

Paginator::defaultView('pagination::tailwind');
Paginator::defaultSimpleView('pagination::simple-tailwind');

links()で表示するタイミングでviewを指定する。
defaultView()はデフォルトなので指定すればデフォルトは使われない。(この時点でのデフォルトはLivewireが上書きしたviewファイル)

Resolverのリセット

PaginationState::resolveUsing()でリセットはできそう。

Laravel内部ではここで使われている。

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        PaginationState::resolveUsing($this->app);
    }

同じようにAppServiceProviderに書けばいいかと思ったけど解決しなかった。
viewと同じく表示時にコントローラーで毎回リセット。

use Illuminate\Pagination\PaginationState;

//...

    public function index(Request $request)
    {
        PaginationState::resolveUsing(app());

        $posts = Post::latest()->paginate();

        return view('post.index')->with(compact('posts'));
    }

面倒なことを除けばLaravel側のページネーションは直ったけど今度はLivewire側のページネーションでエラーが出るようになった。

OctaneのListenerでリセット(最善策)

config/octane.phpでListenerを設定してリクエストごとに初期化するのが現状での解決策っぽい。

        RequestReceived::class => [
            ...Octane::prepareApplicationForNextOperation(),
            ...Octane::prepareApplicationForNextRequest(),
            //
        ],

流れを追っていくとRequestReceivedイベント発生時に色々な初期化処理をしている。

prepareApplicationForNextOperation()にはPrepareLivewireForNextOperationがあって

PrepareLivewireForNextOperationではLivewireのflushState()を呼んでいる。

flushState()ではページネーションはないので自分で行う。

まずListenerを作成。

sail art make:listener FlushPagination

もしくは

php artisan make:listener FlushPagination

FlushPagination.phpは以下

<?php

namespace App\Listeners;

use Illuminate\Pagination\PaginationState;
use Illuminate\Pagination\Paginator;

class FlushPagination
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  object  $event
     * @return void
     */
    public function handle($event)
    {
        Paginator::useTailwind();
        PaginationState::resolveUsing($event->sandbox);
    }
}

config/octane.phpに追加。

        RequestReceived::class => [
            ...Octane::prepareApplicationForNextOperation(),
            ...Octane::prepareApplicationForNextRequest(),
            //
            \App\Listeners\FlushPagination::class,
        ],

useTailwind()は以下と同じ。Tailwindではないなら好きなように変更。

Paginator::defaultView('pagination::tailwind');
Paginator::defaultSimpleView('pagination::simple-tailwind');

$event->sandboxでもapp()でも同じはず。

これでviewの毎回指定は不要。

{{ $posts->links() }}

ServiceProviderやコントローラーでのPaginationState::resolveUsing()も不要。

LaravelのページネーションとLivewireのページネーションを使ったページを行き来してもエラーは出ない。

普通に使って何も問題はなくなった。他に問題が出て来ないかはしばらく使ってみないと確認できない。