🥯

【Laravel+Breeze+Inertia+React】のpropsのauthはどこからでてくるのか

2023/01/11に公開

基本的なことなのかもしれませんが、初心者の私にとって忘れないようにするために記事にします。
環境はLaravel:9.44.0, php:8.0.9です。

ホーム画面のpropsについて

LalavelとBreeze、Inertia、Reactをインストールするとホーム画面は以下のような画面だと思います。インストール方法はこちらこちらを参考にしました。

こちらの画面でchromeの拡張機能であるReact DevToolsを使ってデベロッパーツールからComponents選択すると受け取るpropsが以下のように確認できます。

ここで「/routes/web.php」を見てみると

/routes/web.php
<?php

use App\Http\Controllers\ProfileController;
use App\Http\Controllers\InHouseFileController;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;

/*
省略
*/

Route::get('/', function () {
    return Inertia::render('Welcome', [
        'canLogin' => Route::has('login'),
        'canRegister' => Route::has('register'),
        'laravelVersion' => Application::VERSION,
        'phpVersion' => PHP_VERSION,
    ]);
});

/*
省略
*/

ホームにgetでアクセスした際には、Inertia::renderが返され、WelcomeというコンポーネントにcanLogin, canRegister, laravelVersion, phpVersionを渡してそうだなというのがわかります。しかし、autherrorsziggyについては記載がありません。
ではこれらはどこから出てきたのでしょうか。初心者の私は頭を抱えていました。

Inertia::renderを探す

Inertia::renderというファサード(Facade)は上記からuse Inertia\Inertia;からきていることがわかります。これはどこかというと、「/vendor/inertiajs/inertia-laravel/src/Inertia.php」にありました。

/vendor/inertiajs/inertia-laravel/src/Inertia.php
<?php

namespace Inertia;

use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Facades\Facade;

/**
 * @method static void setRootView(string $name)
 * @method static void share($key, $value = null)
 * @method static array getShared(string $key = null, $default = null)
 * @method static array flushShared()
 * @method static void version($version)
 * @method static int|string getVersion()
 * @method static LazyProp lazy(callable $callback)
 * @method static Response render($component, array|Arrayable $props = [])
 * @method static \Symfony\Component\HttpFoundation\Response location(string $url)
 *
 * @see \Inertia\ResponseFactory
 */
class Inertia extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return ResponseFactory::class;
    }
}

ここからInertia::renderResponseFactory::classにあることがわかります。
そこでResponseFactory::classを見てみると、

/vendor/inertiajs/inertia-laravel/src/ResponseFactory.php
<?php

namespace Inertia;

use Closure;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Response as BaseResponse;
use Illuminate\Support\Traits\Macroable;

class ResponseFactory
{
    use Macroable;

    /** @var string */
    protected $rootView = 'app';

    /** @var array */
    protected $sharedProps = [];

    /** @var Closure|string|null */
    protected $version;

    public function setRootView(string $name): void
    {
        $this->rootView = $name;
    }

    /**
     * @param  string|array|Arrayable  $key
     * @param  mixed|null  $value
     */
    public function share($key, $value = null): void
    {
        
        if (is_array($key)) {
            $this->sharedProps = array_merge($this->sharedProps, $key);
        } elseif ($key instanceof Arrayable) {
            $this->sharedProps = array_merge($this->sharedProps, $key->toArray());
        } else {
            Arr::set($this->sharedProps, $key, $value);
        }
        
    }
 /*
 省略
 */
     /**
     * @param  string  $component
     * @param  array|Arrayable  $props
     * @return Response
     */
    public function render(string $component, $props = []): Response
    {
        if ($props instanceof Arrayable) {
            $props = $props->toArray();
        }
        
        return new Response(
            $component,
            array_merge($this->sharedProps, $props),
            $this->rootView,
            $this->getVersion()
        );
    }
 /*
 省略
 */

render関数がありました。「/routes/web.php」ではこれを呼んでいたわけですね。第1引数がWelcome、第2引数がpropsに渡っていた変数です。returnでResponseインスタンスを返して画面を表示しているんですね。

authはどこから

ただrender関数を見てもauth等の記述はありません。怪しいのは、array_merge($this->sharedProps, $props)の部分で、$propsは引数でしたので、$this->sharedPropsの方ですね。$this->sharedPropsResponseFactory::classshare関数で値が取得されているみたいです。
ではこのshare関数はどこで実行されているのでしょうか。
今度はMiddlewareの方を確認してみます。Middlewareは「/app/Http/Kernel.php」にまとめられています。

/app/Http/Kernel.php
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /*
    省略
     */

    /**
     * The application's route middleware groups.
     *
     * @var array<string, array<int, class-string|string>>
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
            \App\Http\Middleware\HandleInertiaRequests::class,  // ←これ
            \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
            //\App\Http\Middleware\SetLocale::class,
        ],

        'api' => [
            // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];
    
    /*
    省略
     */

$middlewareGroups\App\Http\Middleware\HandleInertiaRequests::class,があります。これを見てみましょう。

/app/Http/Middleware/HandleInertiaRequests.php
<?php

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Inertia\Middleware;
use Tightenco\Ziggy\Ziggy;

class HandleInertiaRequests extends Middleware
{
    /**
     * The root template that is loaded on the first page visit.
     *
     * @var string
     */
    protected $rootView = 'app';

    /**
     * Determine the current asset version.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string|null
     */
    public function version(Request $request)
    {
        return parent::version($request);
    }

    /**
     * Define the props that are shared by default.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return mixed[]
     */
    public function share(Request $request)
    {
        return array_merge(parent::share($request), [
            'auth' => [
                'user' => $request->user(),
            ],
            'ziggy' => function () use ($request) {
                return array_merge((new Ziggy)->toArray(), [
                    'location' => $request->url(),
                ]);
            },
        ]);
    }
}

なんかそれっぽいshare関数がありました。ばっちりauthziggyの記載があります。そこでこのshare関数を実行している部分を探します。
Middlewareは実行するされる際にhandle関数を実行するという決まりがあります。そこでこのHandleInertiaRequests::classclass HandleInertiaRequests extends Middlewareで継承しているMiddlewareを見てみます。これはuse Inertia\Middleware;からわかる通り「/vendor/inertiajs/inertia-laravel/src/Middleware.php」にあります。

/vendor/inertiajs/inertia-laravel/src/Middleware.php
<?php

namespace Inertia;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;
use Symfony\Component\HttpFoundation\Response;

class Middleware
{
/*
省略
*/
    /**
     * Defines the props that are shared by default.
     *
     * @see https://inertiajs.com/shared-data
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function share(Request $request)
    {
        return [
            'errors' => function () use ($request) {
                return $this->resolveValidationErrors($request);
            },
        ];
    }
/*
省略
*/
    /**
     * Handle the incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  Closure  $next
     * @return Response
     */
    public function handle(Request $request, Closure $next)
    {
        Inertia::version(function () use ($request) {
            return $this->version($request);
        });

        Inertia::share($this->share($request)); // ←これ
        Inertia::setRootView($this->rootView($request));

        $response = $next($request);
        $response->headers->set('Vary', 'X-Inertia');

        if (! $request->header('X-Inertia')) {
            return $response;
        }

        if ($request->method() === 'GET' && $request->header('X-Inertia-Version', '') !== Inertia::getVersion()) {
            $response = $this->onVersionChange($request, $response);
        }

        if ($response->isOk() && empty($response->getContent())) {
            $response = $this->onEmptyResponse($request, $response);
        }

        if ($response->getStatusCode() === 302 && in_array($request->method(), ['PUT', 'PATCH', 'DELETE'])) {
            $response->setStatusCode(303);
        }

        return $response;
    }
/*
省略
*/

handle関数がありましたね。そのなかにInertia::share($this->share($request));があると思います。このInertia::shareの部分が最初の方に確認したResponseFactory::classのshare関数で、Welcomeコンポーネントに渡す$sharedPropsを取得しています。
引数である$this->share($request)HandleInertiaRequestsshare関数でここでauthziggyが取得されています。
ついでにこのHandleInertiaRequestsshare関数の1行目の

/app/Http/Middleware/HandleInertiaRequests.php
return array_merge(parent::share($request), [

parent::share($request)が継承元のMiddleware::classshare関数errorsを取得していることがわかります。
これでようやく全部取得しているルートがわかりました。めでたしめでたし。

まとめ

これまでの流れをまとめるとホーム画面にauth等が渡る流れとしては、

  • Middlewareでauth情報の取得
  1. /vendor/inertiajs/inertia-laravel/src/Middleware.php -> handle()
  2. /app/Http/Middleware/HandleInertiaRequests.php -> share() #auth,ziggy
  3. /vendor/inertiajs/inertia-laravel/src/Middleware.php -> share() #errors
  • Inertia::render()でレンダリング
  1. /vendor/inertiajs/inertia-laravel/src/ResponseFactory.php -> render()

本来は突き詰めるなら$request->user()の部分の取得までやるべきかもしれませんが、元気がありません。なかなか初心者には難しいですね。でかziggyってなに。

Discussion