例えば、User::paginate(5)で返されるデータは、@foreachでループできるゆえにコレクションと思いきや、そうでありません。

ページネーション

ページネーションは、以下の画面のように複数のレコードをページごと閲覧できるために使われます。

上の表示に使われるコントローラは、以下のように簡単なコードです。

namespace App\Http\Controllers;

use App\Models\User;

class UserController extends Controller
{
    public function index()
    {
        $users = User::paginate(2); //1ページ2個表示の設定
        return view('users.index', compact('users'));
    }
}

ブレードも簡単です。

@extends('layouts.app')

@section('content')
    <div class="main py-4">
        <div class="card card-body border-0 shadow table-wrapper table-responsive">
            <h2 class="mb-4 h5">会員名</h2>

            <table class="table table-hover">
                <thead>
                    <tr>
                        <th class="border-gray-200">名前</th>
                        <th class="border-gray-200">メールアドレス</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach ($users as $user)
                        <tr>
                            <td><span class="fw-normal">{{ $user->name }}</span></td>
                            <td><span class="fw-normal">{{ $user->email }}</span></td>
                        </tr>
                    @endforeach
                </tbody>
            </table>
            <div
                class="card-footer px-3 border-0 d-flex flex-column flex-lg-row align-items-center justify-content-between">
                {{ $users->links() }}
            </div>
        </div>
    </div>
@endsection

表示するレコードを変える

さて、画面1で表示されるコードに会員IDを追加して、以下のように表示したいです。

ブレードを以下のようにすれば簡単に表示を変更できます。

...
<td><span class="fw-normal">{{ $user->id }} : {{ $user->name }}</span></td>
...

しかし、ブレードを変更せずに、$user->nameに会員ID + 名前の値としたいなら、どうしましょうか?
先のコントローラの定義において、

...
   $users = User::paginate(2);

   $users->each(function($user) {
       $user->name = $user->id.' : '.$user->name;
   });
...

と変更すればよいです。transform()を使用してもよいです。

...
   $users = User::paginate(2);

   $users->transform(function($user) {
       $user->name = $user->id.' : '.$user->name;
       return $user;
   });
...

され、これらをみて、誰しも思うのは、以下のようにメソッドをチェーンすることです。わかりやすいし、Laravelらしい。

...
   $users = User::paginate(2)->each(function($user) {
       $user->name = $user->id.' : '.$user->name;
   });
...

しかし、そうするとエラーとなってしまいます。

そこで気が付くのは、User::paginate(2)が返すのはCollectionではない、ということです。

通常は、以下のようにCollectionを返します。

>>> get_class(User::all());
=> "Illuminate\Database\Eloquent\Collection"

しかし、ページネーションだと、

>>> get_class(User::paginate(2));
=> "Illuminate\Pagination\LengthAwarePaginator"

返すのは、Collectionではなく、LengthAwarePaginatorです。これには、ページに表示するレコードだけでなく、何番目のページとか、他のページに移動するためのリンクとか、など他にもたくさんデータが詰まっています。

>>> User::paginate(2)->toArray();
[!] Aliasing 'User' to 'App\Models\User' for this Tinker session.
=> [
     "current_page" => 1,
     "data" => [
       [
         "id" => 1,
         "name" => "村山 涼平",
         "email" => "kenichi.yoshida@example.org",
         "email_verified_at" => "2022-02-11T02:11:12.000000Z",
         "created_at" => "2022-02-11T02:11:12.000000Z",
         "updated_at" => "2022-02-11T02:11:12.000000Z",
       ],
       [
         "id" => 2,
         "name" => "笹田 裕樹",
         "email" => "shuhei99@example.com",
         "email_verified_at" => "2022-02-11T02:11:12.000000Z",
         "created_at" => "2022-02-11T02:11:12.000000Z",
         "updated_at" => "2022-02-11T02:11:12.000000Z",
       ],
     ],
     "first_page_url" => "http://localhost?page=1",
     "from" => 1,
     "last_page" => 2,
     "last_page_url" => "http://localhost?page=2",
     "links" => [
       [
         "url" => null,
         "label" => "&laquo; Previous",
         "active" => false,
       ],
       [
         "url" => "http://localhost?page=1",
         "label" => "1",
         "active" => true,
       ],
       [
         "url" => "http://localhost?page=2",
         "label" => "2",
         "active" => false,
       ],
       [
         "url" => "http://localhost?page=2",
         "label" => "Next &raquo;",
         "active" => false,
       ],
     ],
     "next_page_url" => "http://localhost?page=2",
     "path" => "http://localhost",
     "per_page" => 2,
     "prev_page_url" => null,
     "to" => 2,
     "total" => 3,
   ]

それゆえに、先ほどのようにメソッドのチェーンをしてデータの変更をすると、これらのデータが失われエラーとなってしまうのです。それでは、どのようにメソッドをチェーンできるのでしょう?

paginate()にチェーン

そのためのメソッドがあるのです。

...
    $users = User::paginate(2)->through(function($user) {
        $user->name = $user->id.' : '.$user->name;
        return $user;
    });
...

through()は、Collectionのtransform()のようなもので、ページネーションのオブジェクトのdataの部分だけの編集が可能です。

ちなみに、以下のようにtap()を使用しても同様なことができます。
(参照:https://stackoverflow.com/questions/37102841/laravel-change-pagination-data

...
  $users = tap(User::paginate(2), function($page) {
        return $page->each( function($user) {
            $user->name = $user->id.' : '.$user->name;
        });
  });
...

なるほど、tap()はこのようなときに使うのですね。paginate()が返すLengthAwarePaginatorのオブジェクトはキープして、その中身を操作することが可能なのです。

By khino