Laravel 10.x Precognition

イントロダクション

Laravel Precognition(プリコグニション:予知)により、将来のHTTPリクエストの結果を予測できます。Precognitionの主な使用例の1つは、アプリケーションのバックエンドのバリデーションルールを複製せずとも、フロントエンドのJavaScriptアプリケーションの「ライブ」バリデーションを提供する能力です。Precognitionは、LaravelのInertiaベースのスターターキットと特に相性がよいです。

Laravelが「事前認識型リクエスト」を受け取ると、ルートのミドルウェアをすべて実行し、フォームリクエストのバリデーションを含む、ルートのコントローラの依存解決を行いますが、実際にはルートのコントローラメソッドを実行しません。

ライブバリデーション

Vueの使用

Laravel Precognitionを使用すると、フロントエンドのVueアプリケーションでバリデーションルールを複製することなく、ユーザーにライブバリデーション体験を提供できます。その仕組みを説明するため、アプリケーション内で新規ユーザーを作成するためのフォームを作成してみましょう。

最初に、ルートに対するPrecognitionを有効にするには、ルート定義へHandlePrecognitiveRequestsミドルウェアを追加する必要があります。さらに、ルートのバリデーションルールを格納するため、フォームリクエストを作成する必要もあります。

use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;

Route::post('/users', function (StoreUserRequest $request) {
    // ...
})->middleware([HandlePrecognitiveRequests::class]);

次に、Vue用のLaravel PrecognitionフロントエンドヘルパをNPMにより、インストールします。

npm install laravel-precognition-vue

LaravelのPrecognitionパッケージをインストールすれば、PrecognitionのuseForm関数を使用して、HTTPメソッド(post)、ターゲットURL(/users)、初期フォームデータを指定して、フォームオブジェクトを作成できるようになります。

次に、ライブバリデーションを有効にするには、各入力の change イベントでフォームのvalidateメソッドを起動し、入力名を指定します。

<script setup>
import { useForm } from 'laravel-precognition-vue';

const form = useForm('post', '/users', {
    name: '',
    email: '',
});

const submit = () => form.submit();
</script>

<template>
    <form @submit.prevent="submit">
        <label for="name">Name</label>
        <input
            id="name"
            v-model="form.name"
            @change="form.validate('name')"
        />
        <div v-if="form.invalid('name')">
            {{ form.errors.name }}
        </div>

        <label for="email">Email</label>
        <input
            id="email"
            type="email"
            v-model="form.email"
            @change="form.validate('email')"
        />
        <div v-if="form.invalid('email')">
            {{ form.errors.email }}
        </div>

        <button :disabled="form.processing">
            Create User
        </button>
    </form>
</template>

これで、フォームがユーザーにより入力されると、Precognitionはルートのフォームリクエストのバリデーションルールに従い、ライブバリデーション出力を提供します。フォームの入力が変更されると、デバウンスされた「事前認識型」バリデーションリクエストをLaravelアプリケーションへ送信します。デバウンスのタイムアウトは、フォームの setValidationTimeout 関数を呼び出すことで設定できます。

form.setValidationTimeout(3000);

バリデーション要求がやり取り中の場合、フォームのvalidatingプロパティはtrue になります。

<div v-if="form.validating">
    Validating...
</div>

バリデーションリクエストやフォーム送信時に返される全てのバリデーションエラーは、自動的にフォームのerrorsオブジェクトへ格納します。

<div v-if="form.invalid('email')">
    {{ form.errors.email }}
</div>

フォームにエラーがあるかは、フォームのhasErrorsプロパティで判断できます。

<div v-if="form.hasErrors">
    <!-- ... -->
</div>

また、入力がバリデーションに合格したか失敗したかを判断するには、入力名をフォームのvalid関数かinvalid関数へ渡してください。

<span v-if="form.valid('email')">
    ✅
</span>

<span v-else-if="form.invalid('email')">
    ❌
</span>

Warning!! フォーム入力が変更され、バリデーションレスポンスを受信した時点で、初めて有効または無効として表示されます。

Precognitionでフォームの入力のサブセットをバリデートしている場合、エラーを手作業でクリアできると便利です。それには、フォームのforgetError関数を使用します。

<input
    id="avatar"
    type="file"
    @change="(e) => {
        form.avatar = e.target.files[0]

        form.forgetError('avatar')
    }"
>

もちろん、フォーム送信に対するレスポンスに反応してコードを実行することもできます。フォームのsubmit関数は、AxiosのリクエストPromiseを返します。これは、レスポンスペイロードへのアクセス、送信成功時のフォーム入力のリセット、または失敗したリクエストの処理に便利な方法を提供します。

const submit = () => form.submit()
    .then(response => {
        form.reset();

        alert('User created.');
    })
    .catch(error => {
        alert('An error occurred.');
    });

フォームのprocessingプロパティを調べれば、フォーム送信リクエストが処理中か判断できます。

<button :disabled="form.processing">
    Submit
</button>

VueとInertiaの使用

Note: VueとInertiaを使ってLaravelアプリケーションを開発するとき、有利に開始したい場合は、スターターキットの一つを使うことを検討してください。Laravelのスターターキットは、新しいLaravelアプリケーションにバックエンドとフロントエンドの認証へスカフォールドを提供します。

VueとInertiaと一緒にPrecognitionを使用する前に、より一般的なVueでPrecognitionを使用するドキュメントを必ず確認してください。VueをInertiaで使用する場合、NPM経由でInertia互換のPrecognitionライブラリーをインストールする必要があります。

npm install laravel-precognition-vue-inertia

一度インストールしたら、PrecognitionのuseForm関数は、上で説明したバリデーション機能で強化した、Inertiaフォームヘルパを返します。

フォームヘルパのsubmitメソッドが効率化され、HTTPメソッドやURLを指定する必要がなくなりました。その代わりに、Inertiaのvisitオプションを最初で唯一の引数として渡してください。また、submitメソッドは、上記のVueの例で見られるように、Promiseを返すことはありません。代わりに、Inertiaがサポートしているイベントコールバックsubmitメソッドへ指定するvisitオプションに指定します。

<script setup>
import { useForm } from 'laravel-precognition-vue-inertia';

const form = useForm('post', '/users', {
    name: '',
    email: '',
});

const submit = () => form.submit({
    preserveScroll: true,
    onSuccess: () => form.reset(),
});
</script>

Reactの使用

Laravel Precognitionを使用すると、フロントエンドのReactアプリケーションへバリデーションルールを複製することなく、ユーザーにライブバリデーション体験を提供できます。その仕組みを説明するため、アプリケーション内で新規ユーザーを作成するためのフォームを作成してみましょう。

最初に、ルートでPrecognitionを有効にするには、ルート定義でHandlePrecognitiveRequestsミドルウェアを追加する必要があります。また、ルートのバリデーションルールを格納するために、フォームrequestを作成する必要があります。

use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;

Route::post('/users', function (StoreUserRequest $request) {
    // ...
})->middleware([HandlePrecognitiveRequests::class]);

次に、React用のLaravel PrecognitionフロントエンドヘルパをNPMによりインストールします。

npm install laravel-precognition-react

LaravelのPrecognitionパッケージをインストールすれば、PrecognitionのuseForm関数を使用して、HTTPメソッド(post)、ターゲットURL(/users)、初期フォームデータを指定し、フォームオブジェクトを作成できます。

ライブバリデーションを有効にするには、各入力のchangeイベントとblurイベントをリッスンする必要があります。changeイベントハンドラでは、setData関数でフォームのデータをセットし、入力名と新しい値を渡します。次に、blurイベントハンドラで、入力名を指定し、フォームのvalidateメソッドを呼び出します。

import { useForm } from 'laravel-precognition-react';

export default function Form() {
    const form = useForm('post', '/users', {
        name: '',
        email: '',
    });

    const submit = (e) => {
        e.preventDefault();

        form.submit();
    };

    return (
        <form onSubmit={submit}>
            <label for="name">Name</label>
            <input
                id="name"
                value={form.data.name}
                onChange={(e) => form.setData('name', e.target.value)}
                onBlur={() => form.validate('name')}
            />
            {form.invalid('name') && <div>{form.errors.name}</div>}

            <label for="email">Email</label>
            <input
                id="email"
                value={form.data.email}
                onChange={(e) => form.setData('email', e.target.value)}
                onBlur={() => form.validate('email')}
            />
            {form.invalid('email') && <div>{form.errors.email}</div>}

            <button disabled={form.processing}>
                Create User
            </button>
        </form>
    );
};

フォームがユーザーにより入力されると、Precognitionはルートのフォームリクエストのバリデーションルールにより、ライブバリデーション出力を提供します。フォームの入力が変更されると、デバウンスされた「事前認識型」バリデーションリクエストをLaravelアプリケーションへ送信します。デバウンスのタイムアウトは、フォームのsetValidationTimeout関数を呼び出し設定します。

form.setValidationTimeout(3000);

バリデーション要求がやり取り中の場合、フォームのvalidatingプロパティはtrue になります。

{form.validating && <div>Validating...</div>}

バリデーションリクエストやフォーム送信時に返される全てのバリデーションエラーは、自動的にフォームのerrorsオブジェクトへ格納します。

{form.invalid('email') && <div>{form.errors.email}</div>}

フォームにエラーがあるかは、フォームのhasErrorsプロパティで判断できます。

{form.hasErrors && <div><!-- ... --></div>}

また、入力がバリデーションに合格したか失敗したかを判断するには、入力名をフォームのvalid関数かinvalid関数へ渡してください。

{form.valid('email') && <span>✅</span>}

{form.invalid('email') && <span>❌</span>}

Warning!! フォーム入力が変更され、バリデーションレスポンスを受信した時点で、初めて有効または無効として表示されます。

Precognitionでフォーム入力のサブセットをバリデートしている場合、エラーを手作業でクリアできると便利です。それには、フォームのforgetError関数を使用します。

<input
    id="avatar"
    type="file"
    onChange={(e) =>
        form.setData('avatar', e.target.value);

        form.forgetError('avatar');
    }
>

もちろん、フォーム送信に対するレスポンスに反応してコードを実行することもできます。フォームのsubmit関数は、AxiosのリクエストPromiseを返します。これは、レスポンスペイロードへのアクセス、送信成功時のフォーム入力のリセット、または失敗したリクエストの処理に便利な方法を提供します。

const submit = (e) => {
    e.preventDefault();

    form.submit()
        .then(response => {
            form.reset();

            alert('User created.');
        })
        .catch(error => {
            alert('An error occurred.');
        });
};

フォームのprocessingプロパティを調べれば、フォーム送信リクエストが処理中か判断できます。

<button disabled={form.processing}>
    Submit
</button>

ReactとInertiaの使用

Note: ReactとInertiaを使ってLaravelアプリケーションを開発するとき、有利に開始したい場合は、スターターキットの一つを使うことを検討してください。Laravelのスターターキットは、新しいLaravelアプリケーションにバックエンドとフロントエンドの認証へスカフォールドを提供します。

ReactとInertiaと一緒にPrecognitionを使用する前に、より一般的なVueでPrecognitionを使用するドキュメントを必ず確認してください。VueをInertiaで使用する場合、NPM経由でInertia互換のPrecognitionライブラリーをインストールする必要があります。

npm install laravel-precognition-react-inertia

一度インストールしたら、PrecognitionのuseForm関数は、上で説明したバリデーション機能で強化した、Inertiaフォームヘルパを返します。

フォームヘルパのsubmitメソッドが効率化され、HTTPメソッドやURLを指定する必要がなくなりました。その代わりに、Inertiaのvisitオプションを最初で唯一の引数として渡してください。また、submitメソッドは、上記のReactの例で見られるように、Promiseを返すことはありません。代わりに、Inertiaがサポートしているイベントコールバックsubmitメソッドへ指定するvisitオプションに指定します。

import { useForm } from 'laravel-precognition-react-inertia';

const form = useForm('post', '/users', {
    name: '',
    email: '',
});

const submit = (e) => {
    e.preventDefault();

    form.submit({
        preserveScroll: true,
        onSuccess: () => form.reset(),
    });
};

AlpineとBladeの使用

Laravel Precognitionを使用し、フロントエンドのAlpineアプリケーションでバリデーションルールを複製しなくても、ユーザーへライブバリデーション体験を提供することができます。その仕組みを説明するために、アプリケーション内で新規ユーザーを作成するフォームを作成してみましょう。

まず、ルートのPrecognitionを有効にするには、ルート定義へHandlePrecognitiveRequestsミドルウェアを追加する必要があります。また、ルートのバリデーションルールを格納するため、フォームリクエストも作成する必要があります。

use App\Http\Requests\CreateUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;

Route::post('/users', function (CreateUserRequest $request) {
    // ...
})->middleware([HandlePrecognitiveRequests::class]);

次に、Alpine用のLaravel PrecognitionフロントエンドヘルパをNPMでインストールします。

npm install laravel-precognition-alpine

それから、PrecognitionプラグインをAlpineへ登録するために、resources/js/app.jsファイルへ登録します。

import Alpine from 'alpinejs';
import Precognition from 'laravel-precognition-alpine';

window.Alpine = Alpine;

Alpine.plugin(Precognition);
Alpine.start();

Laravel Precognitionパッケージをインストール、登録した状態で、Precognitionの$formの「マジック」を使い、HTTPメソッド(post)、ターゲットURL(/users)、初期フォームデータを指定してフォームオブジェクトを作成できるようになりました。

ライブバリデーションを有効にするため、フォームのデータを関連する入力と結合し、各入力のchangeイベントをリッスンする必要があります。changeイベントハンドラは、フォームのvalidateメソッドを呼び出し、入力名を指定する必要があります。

<form x-data="{
    form: $form('post', '/register', {
        name: '',
        email: '',
    }),
}">
    @csrf
    <label for="name">Name</label>
    <input
        id="name"
        name="name"
        x-model="form.name"
        @change="form.validate('name')"
    />
    <template x-if="form.invalid('name')">
        <div x-text="form.errors.name"></div>
    </template>

    <label for="email">Email</label>
    <input
        id="email"
        name="email"
        x-model="form.email"
        @change="form.validate('email')"
    />
    <template x-if="form.invalid('email')">
        <div x-text="form.errors.email"></div>
    </template>

    <button :disabled="form.processing">
        Create User
    </button>
</form>

ユーザーがフォームを入力すると、Precognitionはルートのフォームリクエストの中にあるバリデーションルールにより、ライブバリデーション出力を提供します。フォーム入力が変更されると、デバウンスした「事前認識型」バリデーションリクエストをLaravelアプリケーションへ送信します。デバウンスのタイムアウトは、フォームのsetValidationTimeout関数を呼び出し設定します。

form.setValidationTimeout(3000);

バリデーションリクエストがやり取り中の間、フォームのvalidatingプロパティはtrueになります。

<template x-if="form.validating">
    <div>Validating...</div>
</template>

バリデーションリクエストやフォーム送信の全バリデーションエラーは、自動的にフォームのerrorsオブジェクトへ格納します。

<template x-if="form.invalid('email')">
    <div x-text="form.errors.email"></div>
</template>

フォームのhasErrorsプロパティで、フォームにエラーがあるかを判定できます。

<template x-if="form.hasErrors">
    <div><!-- ... --></div>
</template>

また、入力がバリデーションに合格したか、失敗したかを判定するには、入力名をフォームのvalid 関数とinvalid関数へ渡してください。

<template x-if="form.valid('email')">
    <span>✅</span>
</template>

<template x-if="form.invalid('email')">
    <span>❌</span>
</template>

Warning!! フォーム入力が変更され、バリデーションレスポンスを受信した時点で、初めて有効または無効として表示されます。

フォームのprocessingプロパティを調べれば、フォーム送信リクエストが処理中か判断できます。

<button :disabled="form.processing">
    Submit
</button>

直前のフォームデータの再取得

前述のユーザー作成の例では、Precognitionを使用してライブバリデーションを実行しています。しかし、フォームを送信するために従来のサーバサイドフォーム送信を実行しています。そのため、フォームにはサーバサイドのフォーム送信から返された「古い」入力やバリデーションエラーを保持しているでしょう。

<form x-data="{
    form: $form('post', '/register', {
        name: '{{ old('name') }}',
        email: '{{ old('email') }}',
    }).setErrors({{ Js::from($errors->messages()) }}),
}">

他の方法として、XHRでフォームを送信したい場合は、Axiosリクエストプロミスを返す、フォームのsubmit関数を使用します。

<form
    x-data="{
        form: $form('post', '/register', {
            name: '',
            email: '',
        }),
        submit() {
            this.form.submit()
                .then(response => {
                    form.reset();

                    alert('User created.')
                })
                .catch(error => {
                    alert('An error occurred.');
                });
        },
    }"
    @submit.prevent="submit"
>

Axiosの設定

Precognitionのバリデーションライブラリは、Axios HTTPクライアントを使用して、アプリケーションのバックエンドにリクエストを送信します。使いやすいように、アプリケーションで必要であれば、Axiosインスタンスをカスタマイズできます。例えば、laravel-precognition-vueライブラリを使用する場合、アプリケーションのresources/js/app.jsファイル内の各送信リクエストへ、リクエストヘッダを追加できます。

import { client } from 'laravel-precognition-vue';

client.axios().defaults.headers.common['Authorization'] = authToken;

もしくは、すでにアプリケーション用に設定したAxiosインスタンスがある場合は、代わりにそのインスタンスを使用するようにPrecognitionへ指示することもできます。

import Axios from 'axios';
import { client } from 'laravel-precognition-vue';

window.axios = Axios.create()
window.axios.defaults.headers.common['Authorization'] = authToken;

client.use(window.axios)

Warning!! Inertia的なPrecognitionライブラリでは、設定したAxiosインスタンスのみをバリデーションリクエストに使用します。フォーム送信は常にInertiaが送信します。

バリデーションルールのカスタマイズ

リクエストのisPrecognitiveメソッドを使用すれば、事前認識型リクエスト時に実行するバリデーションルールをカスタマイズできます。

例えば、ユーザー作成フォームにおいて、最終的なフォーム送信時にのみ、パスワードの「データ漏洩」をバリデートしたい場合があります。事前認識型のバリデーションリクエストでは、パスワードが必須であることと、最低8文字であることをバリデートします。isPrecognitiveメソッドを使用すると、フォームリクエストで定義されたルールをカスタマイズできます。

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Password;

class StoreUserRequest extends FormRequest
{
    /**
     * リクエストへ適用するバリデーションルールの取得
     *
     * @return array
     */
    protected function rules()
    {
        return [
            'password' => [
                'required',
                $this->isPrecognitive()
                    ? Password::min(8)
                    : Password::min(8)->uncompromised(),
            ],
            // ...
        ];
    }
}

ファイルアップロードの処理

Laravel Precognitionは、事前認識型バリデーションリクエスト中にファイルのアップロードや検証をデフォルトでは行いません。これにより、大きなファイルが不必要に何度もアップロードされないようにしています。

この振る舞いのため、アプリケーションで対応するフォームリクエストのバリデーションルールをカスタマイズすることで、完全なフォーム送信にのみ、このフィールドが必要であることを指定する必要があります。

/**
 * このリクエストに適用するバリデーションルールの取得
 *
 * @return array
 */
protected function rules()
{
    return [
        'avatar' => [
            ...$this->isPrecognitive() ? [] : ['required'],
            'image',
            'mimes:jpg,png'
            'dimensions:ratio=3/2',
        ],
        // ...
    ];
}

すべてのバリデーションリクエストへファイルを含めたい場合は、クライアント側のフォームインスタンスでvalidateFiles関数を呼び出します。

form.validateFiles();

副作用の管理

HandlePrecognitiveRequestsミドルウェアをルートに追加する場合、他のミドルウェアで事前認識型リクエスト時にスキップすべき副作用があるかどうかを検討する必要があります。

例えば、各ユーザーがアプリケーションとやり取りした「操作」の総数を増加させるミドルウェアがあり、事前認識型リクエストは操作としてカウントしたくない場合です。これを実現するには、操作数を増やす前にリクエストのisPrecognitiveメソッドをチェックする必要があるでしょう。

<?php

namespace App\Http\Middleware;

use App\Facades\Interaction;
use Closure;
use Illuminate\Http\Request;

class InteractionMiddleware
{
    /**
     * 受信リクエストの処理
     */
    public function handle(Request $request, Closure $next): mixed
    {
        if (! $request->isPrecognitive()) {
            Interaction::incrementFor($request->user());
        }

        return $next($request);
    }
}

テスト

もしテスト内で事前認識型リクエストを行いたい場合、LaravelのTestCasePrecognitionリクエストヘッダを追加するwithPrecognitionヘルパがあります。

さらに、事前認識型リクエストが成功したこと、例えばバリデーションエラーを返さないことを宣言したい場合は、レスポンスのassertSuccessfulPrecognitionメソッドを使用します。

public function test_it_validates_registration_form_with_precognition()
{
    $response = $this->withPrecognition()
        ->post('/register', [
            'name' => 'Taylor Otwell',
        ]);

    $response->assertSuccessfulPrecognition();
    $this->assertSame(0, User::count());
}

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる