5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

LaravelAdvent Calendar 2022

Day 18

Laravel+Inertia.jsでパラメータのprops化の仕組みを調べた

Last updated at Posted at 2022-12-19

これはLaravel Advent Calendar 2022の18日目の記事です。

Laravel Jetstreamのパッケージを利用すると、LivewireやInertiaのパッケージを一緒に導入することが出来ます。
今回はInertia+Vueの使い方を見ていきます。

inertia

inertia.js自体はLaravel独自のJavaScriptアダプターというわけではなく、サーバーサイドはLaravel,Rails、フロントはVue,React、Sveltなどの選択肢が存在しています。MVCフレームワークでなるべく簡単にモダンJSを導入出来ることに価値があるようです。

Jetstreamでの導入

使い方

Laravelに導入した場合、bladeを呼ぶようにルートのURLとパラメータをInertia::renderに渡します

use Illuminate\Http\Request;
use Inertia\Inertia;

/**
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Inertia\Response
 */
public function show(Request $request)
{
    return Inertia::render('index', [
        'message' => 'test',
    ]);
}

Vueファイル側ではpropsでパラメータを受けることが出来ます。

index.vue
<script setup >
const props = defineProps<{
    message: string;
}>();
</script>

<template>
{{ props.message }}
</template>

この仕組みを使って、認証やルーティングをサーバーサイドのままに、フロントはVueやReactファイルを使って実装出来ます。

props化の仕組みは?

さてパラメータのprops化はどこでやってるのでしょうか...?

inertia.jsとLaravelの間に入るのはinertia-laravelというアダプター用のコードが用意されています。

何か結構大変なことやってそう...?

Laravel側で呼んでるInertia::renderはFacade定義されたInertiaを使っています。

Inertia.php

<?php

namespace Inertia;

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 $props = [])
 * @method static \Illuminate\Http\Response location(string $url)
 *
 * @see \Inertia\ResponseFactory
 */
class Inertia extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return ResponseFactory::class;
    }
}

実態はResponseFactoryクラスのようです。

ResponseFactoryのrender

ResponseFactory.php
    /**
     * @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()
        );
    }

引数で受ける$componentがURLの文字列、$propsがパラメータ配列ですね。
それを利用してResponseクラスのインスタンスを生成しています。

ここのResponseクラスはinertia-laravelの中にあるクラスです。

このクラスはIlluminate\Contracts\Support\Responsableというinterfaceを継承することでtoResponseメソッドを持つHTTPレスポンスクラスとして使えます。

Response.php
    /**
     * @param  string  $component
     * @param  array|Arrayable  $props
     * @param  string  $rootView
     * @param  string  $version
     */
    public function __construct(string $component, $props, string $rootView = 'app', string $version = '')
    {
        $this->component = $component;
        $this->props = $props instanceof Arrayable ? $props->toArray() : $props;
        $this->rootView = $rootView;
        $this->version = $version;
    }

コンストラクタでclass変数に引数を保存して、元のControllerreturnに返ります。

そこからLaravelのコード内でvendor/laravel/framework/src/Illuminate/Routing/Router.phptoResponseメソッドでレスポンスクラスのtoResponseが呼ばれます

Router.php
    /**
     * Static version of prepareResponse.
     *
     * @param  \Symfony\Component\HttpFoundation\Request  $request
     * @param  mixed  $response
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public static function toResponse($request, $response)
    {
        if ($response instanceof Responsable) {
            $response = $response->toResponse($request);
        }

        if ($response instanceof PsrResponseInterface) {
            $response = (new HttpFoundationFactory)->createResponse($response);
        } elseif ($response instanceof Model && $response->wasRecentlyCreated) {
            $response = new JsonResponse($response, 201);
        } elseif ($response instanceof Stringable) {
            $response = new Response($response->__toString(), 200, ['Content-Type' => 'text/html']);
        } elseif (! $response instanceof SymfonyResponse &&
                   ($response instanceof Arrayable ||
                    $response instanceof Jsonable ||
                    $response instanceof ArrayObject ||
                    $response instanceof JsonSerializable ||
                    $response instanceof stdClass ||
                    is_array($response))) {
            $response = new JsonResponse($response);
        } elseif (! $response instanceof SymfonyResponse) {
            $response = new Response($response, 200, ['Content-Type' => 'text/html']);
        }

        if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
            $response->setNotModified();
        }

        return $response->prepare($request);
    }

ResponsableのinstanceであればtoResponseメソッドが呼ばれています。

inertia-laravelの中にあるResponseクラスです。

Response.php

...

use Illuminate\Support\Facades\Response as ResponseFactory;

...

    /**
     * Create an HTTP response that represents the object.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function toResponse($request)
    {
        $only = array_filter(explode(',', $request->header('X-Inertia-Partial-Data', '')));

        $props = ($only && $request->header('X-Inertia-Partial-Component') === $this->component)
            ? Arr::only($this->props, $only)
            : array_filter($this->props, static function ($prop) {
                return ! ($prop instanceof LazyProp);
            });

        $props = $this->resolvePropertyInstances($props, $request);

        $page = [
            'component' => $this->component,
            'props' => $props,
            'url' => $request->getBaseUrl().$request->getRequestUri(),
            'version' => $this->version,
        ];

        if ($request->header('X-Inertia')) {
            return new JsonResponse($page, 200, ['X-Inertia' => 'true']);
        }

        return ResponseFactory::view($this->rootView, $this->viewData + ['page' => $page]);
    }

最終的には$pageに情報を詰めて既にX-Inertiaがtrueの場合JsonResponseを返す。
そうでない場合FacadeResponseviewメソッドを呼びます。

X-Inertiaにまつわる挙動に関してはこちらの記事がとても詳しく参考になります。

FacadeResponseviewに行った後、makeメソッドでviewを通したresponseを作っています。

routing/Response.php

    /**
     * Create a new response for a given view.
     *
     * @param  string|array  $view
     * @param  array  $data
     * @param  int  $status
     * @param  array  $headers
     * @return \Illuminate\Http\Response
     */
    public function view($view, $data = [], $status = 200, array $headers = [])
    {
        if (is_array($view)) {
            return $this->make($this->view->first($view, $data), $status, $headers);
        }

        return $this->make($this->view->make($view, $data), $status, $headers);
    }

という感じでこの先は通常のviewの処理になっていきます。
渡したパラメータはアダプター内の変換でpropskey名にしてテンプレート側へ渡っていきました。

さらにfront側でどう受け取って扱っていくかは今度はjs側のアダプターコードを読んでいくことで分かりそうです。
これはまた別の機会に読んでみようと思います。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?