0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

僕なりの Laravel 備忘録

Last updated at Posted at 2024-06-16

Blade

Introduction

Route::get('/', function () {
    return view('greeting', ['name' => 'Finn']);
});

Additional Attributes

<input type="checkbox"
        name="active"
        value="active"
        @checked(old('active', $user->active)) />
  • 与えられた条件を評価して、trueの場合にcheckedを出力する。
@checked((old('active') === "active") || old('active') === null)
  • radioボタンの場合はこんな感じで使えるよ。
<select name="version">
    @foreach ($product->versions as $version)
        <option value="{{ $version }}" @selected(old('version') == $version)>
            {{ $version }}
        </option>
    @endforeach
</select>

ここから下はなんかHTMLディレクティブに直接関係あるやつ。

<button type="submit" @disabled($errors->isNotEmpty())>Submit</button>
<input type="email"
        name="email"
        value="email@laravel.com"
        @readonly($user->isNotAdmin()) />
<input type="text"
        name="title"
        value="title"
        @required($user->isAdmin()) />

Displaying Data

Route::get('/', function () {
    return view('welcome', ['name' => 'Samantha']);
});
Route::get('/', function () {
    return view('welcome', ['name' => 'Samantha']);
    return view('admin.index', ['name' => 'Samantha']);
});
  • ディレクトリを掘る時はこんな感じにする。
Hello, {{ $name }}.
The current UNIX timestamp is {{ time() }}.
  • blade echo statement 内にPHP関数も使える。

Displaying Unescaped Data

Hello, {!! $name !!}.
  • blade echo statement はデフォルトでhtmlspecialchars()を実行している。XSS対策にね。
  • エスケープ無しで出力したい場合に使う。

Forms

CSRF Field

<form method="POST" action="/profile">
    @csrf
 
    ...
</form>

Method Field

<form action="/foo/bar" method="POST">
    @method('PUT')
 
    ...
</form>
  • HTMLフォームはPUTPATHCDELETEリクエストを作成できないので、Bladeで変わりにやって上げられる。

Validation Errors

<!-- /resources/views/post/create.blade.php -->
 
<label for="title">Post Title</label>
 
<input id="title"
    type="text"
    class="@error('title') is-invalid @enderror">
 
@error('title')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror
<!-- /resources/views/auth.blade.php -->
 
<label for="email">Email address</label>
 
<input id="email"
    type="email"
    class="@error('email') is-invalid @else is-valid @enderror">
<!-- /resources/views/auth.blade.php -->
 
<label for="email">Email address</label>
 
<input id="email"
    type="email"
    class="@error('email', 'login') is-invalid @enderror">
 
@error('email', 'login')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror

print debug

@dd() # こっちはexitも含まれている感じ
@dump() # こっちは出力してその後も続く

Collections

Introduction

$collection = collect(['taylor', 'abigail', null])
    ->map(function (?string $name) {
    	return strtoupper($name);
	})
    ->reject(function (string $name) {
    	return empty($name);
	});
  • immutable
  • collection()メソッドは実行される度に、新しいインスタンスを作る

Creating Collections

$collection = collect([1, 2, 3]);
  • Eloquetクエリの返値はいつも Collectionになっているらしい。つまり、Model経由で取ってきたデータは全てCollectionになっている。

Extending Collections

use Illuminate\Support\Collection;
use Illuminate\Support\Str;
 
Collection::macro('toUpper', function () {
    return $this->map(function (string $value) {
        return Str::upper($value);
    });
});
 
$collection = collect(['first', 'second']);
 
$upper = $collection->toUpper();
 
// ['FIRST', 'SECOND']
  • macro()メソッドを使って、カスタムの関数を追加できる。上記は全て大文字にする関数を追加している例。
  • 通常、Service Provider の boot()メソッドに追加する。
  • Collection Macroと呼ばれている?

Available Methods

Constants

https://qiita.com/chin-zabro/items/11b4d92f5eacbaa1fbb6

Controller

# 作成
$ php artisan make:controller ApiProductController

# モデルに紐づけながら作成
$ php artisan make:controller ApiProductController --model=Product
$ php artisan route:list
$ php artisan route:list -help
namespace App\Http\Controllers;
 
use App\Models\User;
use Illuminate\View\View;
 
class UserController extends Controller
{
    public function show(string $id): View
    {
        return view('user.profile', [
            'user' => User::findOrFail($id)
        ]);
    }
}

resource

https://laravel.com/docs/11.x/controllers#resource-controllers

https://laravel.com/docs/11.x/controllers#specifying-the-resource-model

一般的なCRUDに必要なメソッドを含ませながらコントローラーを作成する

$ php artisan make:controller PhotoController --resource
$ php artisan make:controller PhotoController --model=Photo --resource
class PhotoController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index() {}

    /**
     * Show the form for creating a new resource.
     */
    public function create() {}

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request) {}

    /**
     * Display the specified resource.
     */
    public function show(News $news) {}

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(News $news) {}

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, News $news) {}

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(News $news) {}
}

use App\Http\Controllers\PhotoController;
 
Route::resource('photos', PhotoController::class);
Route::resources([
    'photos' => PhotoController::class,
    'posts' => PostController::class,
]);

single controller

https://qiita.com/harunbu/items/7fdb824b120cbb2b3d61

Database

driver

SQLiteを使う

データ格納用のファイルを作成

$ touch database/database.sqlite

.envの更新

+ DB_CONNECTION=sqlite
- DB_CONNECTION=mysql
- DB_HOST=mysql
- DB_PORT=3306
- DB_DATABASE=laravel_breeze_multi_auth
- DB_USERNAME=sail
- DB_PASSWORD=password

マイグレート

$ php artisan migrate

テーブルの確認方法

https://tableplus.com/

tinker

$ php artisan tinker
$ sail tinker
$ App\Models\User::all()
$ DB::table('users')->get();
$ DB::table('users')->find(3);
$ DB::table('users')->where('name', 'John')->first();
$ DB::table('users')->where('name', 'John')->value('email');
$ DB::table('users')->pluck('title'); # 全ての行の title が collection で返ってくる
$ DB::table('users')->pluck('title', 'name'); # name がキーとなる形で collection が返ってくる
$ DB::table('users')->where('name', 'john')->exists();
$ DB::table('users')->where('name', 'john')->doesntExist();

https://laravel.com/docs/11.x/artisan#tinker

https://github.com/bobthecow/psysh

https://laravel.com/docs/11.x/queries#running-database-queries

mysql

sail mysql
sail shell
mysql -u sail -ppassword -h mysql
mysql -u root -ppassword -h mysql

dumpはrootでしかできないから注意。

Environment

https://laravel.com/docs/11.x/installation#initial-configuration

# .env

APP_NAME=Laravel
APP_TIMEZONE=Asia/Tokyo

APP_LOCALE=ja
APP_FALLBACK_LOCALE=ja
APP_FAKER_LOCALE=ja_JP
# config/app.php
return [
    'timezone' => 'Asia/Tokyo',
    'locale' => 'ja',
    'fallback_locale' => 'ja',
    'faker_locale' => 'ja_JP',
],
# 日本語の翻訳ファイルを追加
$ composer require --dev laravel-lang/lang laravel-lang/publisher
$ php artisan lang:add ja

# インストール後はライブラリ不要なのでアンインストールする
$ composer remove --dev laravel-lang/lang laravel-lang/publisher

Factory

https://laravel.com/docs/11.x/eloquent-factories

$ php artisan make:factory PostFactory
class FlightFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var class-string<\Illuminate\Database\Eloquent\Model>
     */
    protected $model = Flight::class;
}
public function definition(): array
{
    return [
        'name' => fake()->name(),
        'email' => fake()->unique()->safeEmail(),
        'email_verified_at' => now(),
        'password' => static::$password ??= Hash::make('password'),
        'remember_token' => Str::random(10),
    ];
}

Migration

Generating Migrations

$ php artisan make:migration create_flights_table
$ php artisan make:migration alter_users_table --table=users
  • テーブル編集の場合は--tableを後ろにつける。
  • 参考記事
$ php artisan make:migration create_users_table --create=users
  • 新規作成の場合は--createを後ろにつける。
  • 参考記事

マイグレーションファイルの命名について

$ php artisan make:migration create_users_table --create=users
  • 新規作成
$ php artisan make:migration add_name_column_to_users_table --table=users
$ php artisan make:migration add_columns_to_users_table --table=users
  • カラム追加と複数カラム追加
$ php artisan make:migration remove_name_column_from_users_table --table=users
$ php artisan make:migration remove_columns_to_users_table --table=users
  • カラム削除と複数カラム削除
$ php artisan make:migration modify_name_column_of_users_table --table=users
$ php artisan make:migration modify_name_columns_of_users_table --table=users
  • カラム修正と複数カラム修正
$ php artisan make:migration update_users_table --table=users
  • テーブル修正

Running Migrations

$ php artisan migrate

$ php artisan migrate:status

$ php artisan migrate --pretend

Rolling Back Migrations

# 最新を戻す
$ php artisan migrate:rollback

# 5個戻す
$ php artisan migrate:rollback --step=5

# バッチを3個戻す
$ php artisan migrate:rollback --batch=3

# dry-run
$ php artisan migrate:rollback --pretend

# すべてを
$ php artisan migrate:reset

Roll Back and Migrate Using a Single Command

$ php artisan migrate:refresh
 
# Refresh the database and run all database seeds...
$ php artisan migrate:refresh --seed
  • 全てのmigrationをrollbackした後、migrateする。テーブルをdropする訳ではない。
  • データを一旦綺麗にするというイメージで良い。
$ php artisan migrate:refresh --step=5
  • これも、データを一旦綺麗にするというイメージ。
  • 大量にmigrationがある状況で、全部rollbackするよりは早いよね?というのが利点だろうと思う。

Drop All Tables and Migrate

$ php artisan migrate:fresh
 
$ php artisan migrate:fresh --seed
  • 全テーブルをdropした後、migrateする。

Tables

Creating Tables

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
 
Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email');
    $table->timestamps();
});

Updating Tables

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
 
Schema::table('users', function (Blueprint $table) {
    $table->integer('votes');
});

Renaming / Dropping Tables

use Illuminate\Support\Facades\Schema;
 
Schema::rename($from, $to);
Schema::drop('users');
Schema::dropIfExists('users');

Columns

Creating Columns

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
 
Schema::table('users', function (Blueprint $table) {
    $table->integer('votes');
});
php artisan make:migration add_image_column_of_ramens_table --table=ramens
public function up(): void
{
    Schema::table('ramens', function (Blueprint $table) {
        $table->string('image');
    });
}

public function down(): void
{
    Schema::table('ramens', function (Blueprint $table) {
        $table->dropColumn('image');
    });
}

Available Column Types

$table->id();
$table->boolean('confirmed');
$table->timestamp('added_at', precision: 0);
$table->foreignId('user_id');
$table->integer('votes');
$table->longText('description');
$table->string('name', length: 100);
$table->text('description');

Column Modifiers

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
 
Schema::table('users', function (Blueprint $table) {
    $table->string('email')->nullable();
});
  • カラム型定義の後に続ける修飾子.
  • 要は制約のこと。
->autoIncrement()
->nullable($value = true)
->useCurrent()
->useCurrentOnUpdate()
->after('column')
  • uniqueは違うよ!

Column Order

$table->after('password', function (Blueprint $table) {
    $table->string('address_line1');
    $table->string('address_line2');
    $table->string('city');
});
  • MySQLを使っている時に利用できる。
  • after()メソッドで、第一引数で指定したカラムの後に追加できる。

Modifying Columns

Schema::table('users', function (Blueprint $table) {
    $table->string('name', 50)->change();
});
Schema::table('users', function (Blueprint $table) {
    $table->integer('votes')->unsigned()->default(1)->comment('my comment')->change();
});
  • 全ての保持させたい定義を記述した上で、change()メソッドを使う。
  • 含めていない定義は削除されるから注意。
// Add an index...
$table->bigIncrements('id')->primary()->change();
 
// Drop an index...
$table->char('postal_code', 10)->unique(false)->change();
  • indexは削除されない。
  • indexの追加・削除・修正をする場合は、明示的に指定する必要がある。

Renaming Columns

Schema::table('users', function (Blueprint $table) {
    $table->renameColumn('from', 'to');
});

Dropping Columns

Schema::table('users', function (Blueprint $table) {
    $table->dropColumn('votes');
});
Schema::table('users', function (Blueprint $table) {
    $table->dropColumn(['votes', 'avatar', 'location']);
});

Available Command Aliases

Command
$table->dropRememberToken();
$table->dropTimestamps();

Indexes

Creating Indexes

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
 
Schema::table('users', function (Blueprint $table) {
    $table->string('email')->unique();
});
  • 1行で実行しちゃう。
$table->unique('email');
  • カラム定義したあとに、追加もできる。
  • あまり使わないかも?
$table->index(['account_id', 'created_at']);
  • 複数カラムを同時にもできる。
  • カラム定義して、その後まとめて、indexつけるって感じで、こちらの方が使いそう。
$table->unique('email', 'unique_email');
  • 第二引数で付けたいindex名を指定できる。
  • 指定しない限りは、Laravelが勝手に良きにindex名を振ってくれる。

Available Index Types

Command
$table->primary('id');
$table->unique('email');
$table->index('state');

uniqueでnullableにしたい場合がある。

  • 空の場合はunique制約を効かせず、値が入った時には効かせるという。普通にチェーンすれば大丈夫。

    $table->string('email')->nullable()->unique();
    
  • 空文字列はnullではないので注意する。

Foreign Key Constraints

WIE

注意

  • 外部キー制約の前に、他のカラム定義を書く。
  • up()で作成し、 down()で削除する場合は、カラム削除の命令も書いておく必要がある。
$table->foreign('user_id')->references('id')->on('users');

$table->foreignId('user_id')->constrained();

$table->foreignId('user_id')->constrained(
    table: 'users', indexName: 'posts_user_id'
);

$table->foreignId('user_id')
      ->constrained()
      ->onUpdate('cascade')
      ->onDelete('cascade');

$table->foreignId('user_id')
      ->constrained()
      ->onUpdate('cascade')
      ->onDelete('cascade');

$table->cascadeOnUpdate();
$table->restrictOnUpdate();
$table->noActionOnUpdate();
$table->cascadeOnDelete();
$table->restrictOnDelete();
$table->nullOnDelete();	
$table->dropForeign('posts_user_id_foreign');

$table->dropForeign(['user_id']);

Model

show

$ php artisan model:show Flight

Create model and migration simultaneously

$ php artisan make:model Product -m

Inserting and Updating Models

Inserts

<?php
 
namespace App\Http\Controllers;
 
use App\Http\Controllers\Controller;
use App\Models\Flight;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
 
class FlightController extends Controller
{
    /**
     * Store a new flight in the database.
     */
    public function store(Request $request): RedirectResponse
    {
        // Validate the request...
 
        $flight = new Flight;
 
        $flight->name = $request->name;
 
        $flight->save();
 
        return redirect('/flights');
    }
}
  • Eloquentを使うおかげで、データベースからモデルを取ってくる必要がない。
  • データベースにデータを登録するには、モデルのインスタンスを作成し、各attributeを設定するだけ。
  • save()メソッドを呼んだタイミングで、created_atupdated_atにタイムスタンプがセットされるため、手動でセットする必要はない。
use App\Models\Flight;
 
$flight = Flight::create([
    'name' => 'London to Paris',
]);
  • よりシンプルに書けるね。
  • 登録されたモデルのインスタンスが返値で返ってくる。
  • fillabelプロパティかguaredeプロパティを設定しておく必要がある。

Updates

Mass Assignment

use App\Models\Flight;
 
$flight = Flight::create([
    'name' => 'London to Paris',
]);
<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['name'];
}
  • create()メソッドを使う場合、上記のように、 fillableプロパティをモデルクラスに設定する必要がある。
  • これは、Eloquent models が DB への mass assignment vulnerability から守るため。
  • 基本的には、 mass assignment を使ってデータを格納していくので、設定してあげる。

Allowing Mass Assignment

/**
 * The attributes that aren't mass assignable.
 *
 * @var array
 */
protected $guarded = [];
  • 格納されたくないカラムを配列に書く。
  • 逆に全てのカラムを許可する場合は、fillableプロパティではなく、こちらで設定する方が簡単かも。

Request

Route

https://laravel.com/docs/11.x/routing

group

https://laravel.com/docs/11.x/routing#route-groups

prefix

https://laravel.com/docs/11.x/routing#route-group-prefixes

Route::prefix('admin')->name('admin.')->group(function () {
    Route::get('/users', function () {
        // Matches The "/admin/users" URL
        // Route assigned name "admin.users"...
    })->name('users');
});

route model binding

https://laravel.com/docs/11.x/routing#route-model-binding

URLのセグメント名と対応するModel名を一致させることで、Laravelが良きに解決してくれる。

use App\Models\User;
 
Route::get('/users/{user}', function (User $user) {
    return $user->email;
});
use App\Http\Controllers\UserController;
use App\Models\User;
 
// Route definition...
Route::get('/users/{user}', [UserController::class, 'show']);
 
// Controller method definition...
public function show(User $user)
{
    return view('user.profile', ['user' => $user]);
}

Resourcesでまとめてnameを付ける

Route::resource('/admin/ramen', RamenController::class)
    ->names('admin');

Resourcesのパラメータを書き替えたい

※urlが深いとできないから注意。

Route::resource('/ramen', RamenController::class)
    ->parameter('raman', 'ramen');

Formでの基本的なルート設定

Route::get('/', [AdmissionController::class, 'show'])
    ->name('show');
Route::post('/', [AdmissionController::class, 'post'])
    ->name('post');

Route::get('/confirm', [AdmissionController::class, 'confirm'])
    ->name('confirm');
Route::post('/confirm', [AdmissionController::class, 'send'])
    ->name('send');

Route::get('/thanks', [AdmissionController::class, 'complete'])
    ->name('complete');
  • get 問い合わせフォームを表示
  • post 問い合わせフォーム遷移先
  • get 確認画面
  • post 確認画面からフォーム遷移先
  • get 完了画面

viewでの書き方は複数ある

<a href="{{ url('/form') }}">お問い合わせはこちら</a>
<a href="{{ route('form.show') }}">お問い合わせはこちら</a>
<a href="{{ action('SampleFormController@show') }}">お問い合わせはこちら</a>

■ url()
長所:URLがどうなるかわかりやすい
短所:各機能のURLを知っておく必要がある

■ route()
長所:記述がシンプル。URLを書き換える時ルーティングの設定ファイルだけで良い
短所:URLがどうなるかわかりにくい

■ action()
長所:クラス名と関数名がわかるので、遷移先の処理が追いかけやすい
短所:クラス名が長いのテンプレートが読みづらくなる

新しいルートを追加したのにNotFoundになる

ルーティングの順番に問題がある場合が多い。

上から読み込まれるので、

Route::get('/users/{user}', 'User@show');
Route::get('/users/hoge', 'User@hoge');

の順番にすると、hogeが動かない。

Response

https://laravel.com/docs/11.x/responses

redirect

https://laravel.com/docs/11.x/responses#redirects

redirecting to named routes

return redirect()->route('login');

// For a route with the following URI: /profile/{id}
return redirect()->route('profile', ['id' => 1]);

// For a route with the following URI: /profile/{id} ※
return redirect()->route('profile', [$user]);

If you would like to customize the value that is placed in the route parameter, you can specify the column in the route parameter definition (/profile/{id:slug}) or you can override the getRouteKey method on your Eloquent model:

/**
 * Get the value of the model's route key.
 */
public function getRouteKey(): mixed
{
    return $this->slug;
}

https://laravel.com/docs/11.x/responses#populating-parameters-via-eloquent-models

redirecting with flashed session data

https://laravel.com/docs/11.x/responses#redirecting-with-flashed-session-data

Route::post('/user/profile', function () {
    // ...
 
    return redirect('dashboard')->with('status', 'Profile updated!');
});
@if (session('status'))
    <div class="alert alert-success">
        {{ session('status') }}
    </div>
@endif

Shell

シェルにアクセス

sail shell

Seeding

https://laravel.com/docs/11.x/seeding

基本

$ php artisan make:seeder UserSeeder
# database/seeders/UsersTableSeeder.php

class UsersTableSeeder extends Seeder
{
    public function run()
    {
        DB::table('users')->insert([
            'name' => Str::random(10),
            'email' => 'john@doe.com',
            'password' => Hash::make('password')
        ]);
        
        News::factory()->count(10)->create();
    }
}
$ php artisan db:seed --class=UserSeeder

Model factory

https://laravel.com/docs/11.x/seeding#using-model-factories

まず、seedingを考える前に、[factory](# Factory) を作成しないといけない。

public function run(): void
{
    User::factory()
            ->count(50)
            ->hasPosts(1)
            ->create();
}

hasPosts(1)メソッドは 1つの関連するPostデータを持っている。

Calling Additional Seeders

# database/seeders/DatabaseSeeder.php

public function run(): void
{
    $this->call([
        UserSeeder::class,
        PostSeeder::class,
        CommentSeeder::class,
    ]);
}

Running

https://laravel.com/docs/11.x/seeding#running-seeders

$ php artisan db:seed
$ php artisan db:seed --class=UserSeeder

Test

php artisan make:test FirstTest
php artisan make:test FirstTest --unit
php artisan test

Validation

Validation Quickstart

Writing the Validation Logic

  • validationが失敗すると、これは自動的に適切な処理である、Illuminate\Validation\ValidationExceptionexceptionが投げられる。
  • 挙動としては、元居た場所に戻される。
  • 全てのバリエーションエラーとリクエストをflashデータとしてセッションに格納する。
  • XHR requestの場合は、jsonが返される。
/**
 * Store a new blog post.
 */
public function store(Request $request): RedirectResponse
{
    $validated = $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
    ]);
 
    // The blog post is valid...
 
    return redirect('/posts');
}
$validatedData = $request->validate([
    'title' => ['required', 'unique:posts', 'max:255'],
    'body' => ['required'],
]);
  • 配列で区切ることもできるよ
$validatedData = $request->validateWithBag('post', [
    'title' => ['required', 'unique:posts', 'max:255'],
    'body' => ['required'],
]);
  • 名前付きvalidation error bagでvalidation errorをグループ分けできる。

Displaying the Validation Errors

  • $errors変数は、Illuminate\View\Middleware\ShareErrorsFromSessionミドルウェアによって、アプリケーションの全てのビューと共有される。従って、このミドルウェアを適用させれば、自由に使えるということですね。
  • $errors変数は、Illuminate\Support\MessageBagのインスタンス。
<!-- /resources/views/post/create.blade.php -->
 
<h1>Create Post</h1>
 
@if ($errors->any())
    <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif
 
<!-- Create Post Form -->

The @error Directive

  • bladeのディレクティブ。
  • 個別の attribute の validation error message が存在するか確認する。
  • このディレクティブ内で、$message変数を書くことで、個別のエラーメッセージを出せる。
<!-- /resources/views/post/create.blade.php -->
 
<label for="title">Post Title</label>
 
<input id="title"
    type="text"
    name="title"
    class="@error('title') is-invalid @enderror">
 
@error('title')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror
<input ... class="@error('title', 'post') is-invalid @enderror">
  • named error bag を使っている時は、第二引数で指定してあげる。

Repopulating Forms

  • 検証エラーのためにリダイレクトレスポンスを生成すると、リクエストの入力値全てをセッションにフラッシュする。
  • これは、次のリクエスト中に入力に簡単にアクセスしやすくするため。
  • 従って、ユーザーが送信しようとしたフォームを再入力できるようになる。
$title = $request->old('title');
  • 以前のリクエストからフラッシュされた入力値を取得する。
<input type="text" name="title" value="{{ old('title') }}">
  • oldヘルパーが提供されているので、bladeではそれを使ってアクセスする。わざわざコントローラーで詰め込みみたいなのは、不要。
  • 入力がない場合は、nullが返されるので安心。
<input type="text" name="age" value="{{ old("name", "田中") }}">
  • フォームのeditの時に、初期値を設定したい時に使う。
  • old()ヘルパーの第二引数が初期値になる。
  • 参考記事

3つある

  • Validatorファサードを使う方法
  • Requestクラスのvalidate()メソッドを使う方法
  • FormRequestクラスを使う方法

優先はFormRequestクラスを使う方法。Fat Controllerを避けるのがベストプラクティス

継承した子クラスを作って実装していく。

php artisan make:request [任意のクラス名]

authorizedは認可。ruleがルール。

public function rules(): array
	{
    	return [
        	'name'  => 'required|max:20',
          	'tel'   => 'nullable|regex:/^[0-9]{2,4}-[0-9]{2,4}-[0-9]{3,4}$/|regex:/^0\d{9,10}$/',
          	'body'  => 'required|max:1000',
            'email' => ['required', 'email:filter,dns', 'confirmed'],
            'email_confirmation' => 'メールアドレス確認',
        ];
    }

このバリデーションが通らなければ、直前の画面に302リダイレクトされる

コントローラーに依存性の注入。

use App\Http\Requests\InquiryRequest;

public  function post(InquiryRequest $request)
{
    // ここを通る時点で認可&バリデーションが通っている

}

バリデーションの最初の失敗で停止させる

/**
 * 最初のルールの失敗でバリデーションが停止する。
 * @var bool
 */
protected $stopOnFirstFailure = true;

リダイレクト先を変更する

通常、フォームをPostリクエストされた際、エラーが発生すると、リクエスト元のフォームがあったページへリダイレクトされる。

/**
 * バリデーション失敗時に、/dashboardにリダイレクトさせる場合
 * @var string
 */
protected $redirect = '/dashboard';

/**
 * 名前付きルートで指定したい場合
*/
protected $redirectRoute = 'dashboard';

バリデーション前に処理を行う

protected function prepareForValidation()
{
    // 入力データ取得 (例 03-1111ー2222
    $tel = $this->input('tel');

    // ハイフンに似た文字を半角ハイフンに変換(この処理はちゃんとやるともっと複雑)
    $tel = str_replace(['-', '―', '‐', 'ー'], '-', $tel);

    // 半角数字を全角数字に変換:結果→03-1111-2222
    $tel = mb_convert_kana($tel, 'n');

    // 変換後のtelをリクエストデータにセット
    $this->merge(['tel' => $tel]);
}

追加バリデーションの実行(カスタムバリデーションのこと)

withValidator()メソッドが用意されています。このメソッド内で$validator->after() フックメソッドを定義することで、追加バリデーション機能を実現可能。

use Illuminate\Validation\Validator;

public function withValidator(Validator $validator): void
{
    $validator->after(function (Validator $validator) {
        if ($this->input('email') === 'admin@example.com') {
            $validator->errors()->add('email', 'このメールアドレスは使用できません。');
        }
    });
}

バリデーション成功時に処理を行う

passedValidation() メソッドの中で行うことで、コントローラのアクションに到達する前にデータを加工することができる。

protected function passedValidation()
{
    // 'price' の値を整数として扱いたい場合
    $this->merge([
        'price' => intval($this->price)
    ]);
}

バリデーション失敗時に処理を行う

failedValidation()メソッドをオーバーライドすることで、バリデーション失敗時に処理を行うことが可能.

ここでは15行目で独自のレスポンスを返していますが、そうしない場合は必ず、親クラスの同メソッドを呼ぶのを忘れない

use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;

class CustomRequest extends FormRequest
{
    // ~略~
    protected function failedValidation(Validator $validator)
    {
        $response = [
            'status' => 422,
            'message' => 'Validation failed!',
            'errors' => $validator->errors()
        ];

        throw new HttpResponseException(response()->json($response, 422));
        //  parent::failedValidation($validator);
    }

エラーメッセージのカスタマイズ

Laravelのインストール直後では、バリデーションエラーが英語で表示されます。そのため、日本語でのWebアプリケーションの場合、日本語化することが必須に。

  • Laravelの言語ファイルを作成&設定する

    https://laranote.jp/laravel-language-file-validation-error-localization/

  • FormRequestの子クラスで定義する

    ここで定義する日本語のエラーメッセージは、Laravelの言語ファイルよりも優先される。そのクラスでのみ使いたいエラー表示には適している。

    public function messages()
    {
        return [
            'title.required' => 'タイトルは必須です。',
            'body.min' => '本文は最低10文字必要です。',
        ];
    }
    

    messages()メソッドにプレースホルダー(:attribute)を用意し、attribute()メソッドで置き換える方法もある。

    public function messages()
    {
        return [
            'title.required' => ':attribute は必須です。',
            'body.min' => ':attribute は最低 :min 文字必要です。',
        ];
    }
    
    public function attributes()
    {
        return [
            'title' => 'タイトル',
            'body' => '本文',
        ];
    }
    

    https://laranote.jp/laravel-formrequest-validation-error-localization/

カスタムバリデーション

https://enginiya.com/entry/how-to-make-custom-validate

コントローラー内に記述(Fat Controller の原因になるため非推奨)

直接的でシンプル。

特定のアクションや特定のコントローラでのみ使用するような一時的なバリデーションルールに適しています。

Form Requestクラスで記述

再利用性が高く、バリデーションルールと関連するロジックを1つのクラス内に集約できる。

コントローラがスリムに保たれ、テストも容易になります

public function rules()
{
    return [
        'field' => [
            'required',
            function ($attribute, $value, $fail) {
                if (mb_strtoupper($value) !== $value) {
                    $fail('大文字である必要があります');
                }
            },
        ],
    ];
}

Service Provider内で定義

アプリケーション全体で使用できるカスタムバリデーションルールを定義するのに適している。

再利用性が高い。

public function boot()
{
    Validator::extend('uppercase', function ($attribute, $value, $parameters, $validator) {
        return mb_strtoupper($value) === $value;
    });
    Validator::replacer('uppercase', function ($message, $attribute, $rule, $parameters) {
        return $attribute.' must be uppercase.';
    });
}
$request->validate([
    'field1' => 'required|uppercase',
    'field2' => 'required|uppercase',
]);

bootメソッドは、Laravelのサービスプロバイダ内で使用され、サービスコンテナのバインディングや、他の起動時の処理を実行するための場所として提供されている。

Validator::extend: extendメソッドを使って新しいバリデーションルールを定義。

uppercase: 新しいバリデーションルールの名前を付ける。

無名関数: バリデーションのロジックを定義するコールバック関数。値がすべて大文字である場合にtrueを返う。

Validator::replacer: replacerメソッドを用いて、特定のバリデーションルールに関連するエラーメッセージのフォーマットを定義します。

2つのフィールドに対して同時にバリデーションを掛ける

// app/Providers/AppServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Validator;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Validator::extend('name_fields_required', function ($attribute, $value, $parameters, $validator) {
            $data = $validator->getData();
            $firstName = $data['first_name'] ?? '';
            $lastName = $data['last_name'] ?? '';

            // どちらも空、またはどちらかが空である場合、バリデーションエラー
            return !empty($firstName) && !empty($lastName);
        }, 'The first name and last name fields are required.');
    }
}
// app/Http/Requests/YourFormRequest.php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class YourFormRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'first_name' => 'required_with:last_name|name_fields_required',
            'last_name' => 'required_with:first_name|name_fields_required',
        ];
    }
}

専用のバリデーションルールクラス(これ一番良さそう)

https://qiita.com/koinunopochi/items/bd1a9d72f673885f9adf

$ php artisan make:rule Uppercase
// Uppercase.php これ非推奨

public function passes($attribute, $value)
{
    return mb_strtoupper($value) === $value;
}
public function message()
{
    return ':attribute は大文字である必要があります';
}

// Upparcase.php 推奨 こっちが作られる

namespace App\Rules;
 
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
 
class Uppercase implements ValidationRule
{
    /**
     * Run the validation rule.
     */
     // $attribute 属性名、$value 値、$fail 失敗時のメッセージ
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        // このコードは、値を大文字にした結果と一致するかどうかを検証している
        // 一致しない場合は、:attribute(属性名)が大文字であるというエラーメッセージをセットで返す。
        if (strtoupper($value) !== $value) {
            $fail('The :attribute must be uppercase.');
        }
    }
}
// RequestやControllerでの使い方

$request->validate([
    'field' => ['required', new Uppercase],
]);

passesメソッドは、属性の値がルールを満たすかどうかを判定します。

この例では、値がすべて大文字であるかをチェックしています。

すべて大文字であればtrueを、そうでなければfalseを返します。

messageメソッドは、バリデーションルールが失敗したときに表示するエラーメッセージを返します。

:attributeプレースホルダは、バリデーションを実行する際の属性名に置き換えられます。

2つのフィールドに対して同時にバリデーションを掛ける

$ php artisan make:rule DualField
# app/Rules/NamesRequired.php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class NamesRequired implements Rule
{
    private $otherValue;

    public function __construct($otherValue)
    {
        $this->otherValue = $otherValue;
    }

    public function passes($attribute, $value)
    {
        // どちらのフィールドも空ではないことを確認
        return !empty($value) && !empty($this->otherValue);
    }

    public function message()
    {
        return 'Both first name and last name are required.';
    }
}
use App\Rules\NamesRequired;

public function rules()
{
    return [
        'first_name' => ['nullable', 'string', new NamesRequired(request('lastname'))],
        'last_name' => ['nullable', 'string', new NamesRequired(request('firstname'))],
    ];
}

バリデーション失敗後のデータの復元

<input type="text" name="title" id="title" value="{{ old('title', 'default value') }}">
<select name="gender">
    <option value="man" {{ old('gender', 'man') == 'man' ? 'selected' : '' }}>Man </option>
    <option value="woman" {{ old('gender', 'man') == 'woman' ? 'selected' : '' }}>Woman</option>
</select>
<input type="checkbox" name="subscribe" value="1" {{ old('subscribe', '1') == '1' ? 'checked' : '' }}>

バリデーション成功後のデータの取得

全部のデータ

FormRequestvalidatedメソッドでバリデーション済みデータを取得可能。

$validatedは配列で返される。

   public function post(InquiryRequest $request)
    {
        // ここを通る時点で認可&バリデーションが通っている

        // バリデーション済みデータの取得
        $validated = $request->validated();
        dd($validated);

一部のデータ

$request->safe()メソッドが返すIlluminate\Support\ValidatedInputオブジェクトが持つonly()except()メソッドを使う。

  public function post(InquiryRequest $request)
  {
    // ここを通る時点で認可&バリデーションが通っている

    // バリデーション済みの'name'と'email’のみ取得
    $validated = $request->safe()->only(['name', 'email']);

    // バリデーション済みの'name'と'email’以外を取得
    $validated = $request->safe()->except(['name', 'email']);

prepare For Validation()で加工したデータをold()で表示させたい

https://laranote.jp/understanding-laravel-formrequest/#:~:text=%5D%3B%0A%7D-,prepareForValidation()%E3%81%A7%E5%8A%A0%E5%B7%A5%E3%81%97%E3%81%9F%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92old()%E3%81%A7%E8%A1%A8%E7%A4%BA%E3%81%95%E3%81%9B%E3%81%9F%E3%81%84,-%E4%BE%8B%E3%81%88%E3%81%B0%E3%80%81%E5%89%8D%E9%A0%85%E3%81%AE

バリデーションエラーをview(Blade)で表示させる

https://laranote.jp/laravel-blade-validation-error-display-methods/#toc1

@if ($errors->any())
    <div class="error">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif
@if ($errors->has('email'))
    <div class="error">{{ $errors->first('email') }}</div>
@endif
    
// or
    
@error('name')
    <div class="error">{{ $message }}</div>
@enderror

$messageは、@errorディレクティブを使用した際使える変数。直前に利用した名称のエラーを$messageに自動的に入れてくれるため、これでエラーが表示できる。

php artisan make:component ErrorMessage
<x-error-message for="name" />
# resources/views/components/error-message.blade.php

@props(['for'])

@error($for)
    <span class="error">{{ $message }}</span>
@enderror

@propsディレクティブを使用してforという名前の属性を受け取る。このfor属性は、バリデーションエラーをチェックするフォーム項目名を示している。

Views

Introduction

Creating and Rendering Views

php artisan make:view greeting
  • blade.php拡張子を持つファイルを、resources/view/に作成してくれる。
Route::get('/', function () {
    return view('greeting', ['name' => 'James']);
});
  • view()ヘルパーでviewを作成。
use Illuminate\Support\Facades\View;
 
return View::make('greeting', ['name' => 'James']);
  • Viewファサードでviewを作成。

Nested View Directories

return view('admin.profile', $data);

Passing Data to Views

return view('greetings', ['name' => 'Victoria']);
  • 普通に渡す。
return view('greeting')
            ->with('name', 'Victoria')
            ->with('occupation', 'Astronaut');
  • こんなんでも渡せる。
  • いつ使うのかな、、、?

Sharing Data With All Views

<?php
 
namespace App\Providers;
 
use Illuminate\Support\Facades\View;
 
class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }
 
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        View::share('key', 'value');
    }
}
  • 全てのViewに共有させたい場合に使う。
  • AppServiceProviderクラスに実装してもいいし、切り出しても良い。

Optimizing Views

Viewを返すリクエストが実行された場合に、必要に応じてViewはコンパイルされる。

  • コンパイル済みViewがある場合はそれを返す。
  • 無ければ再コンパイルして返す。
  • 更新されている場合も再コンパイルして返す。

毎回上記のようにリクエストの度に再コンパイルが実行されるとパフォーマンスに悪影響が出る。そのため、開発時でもコンパイルしてからリクエストを実行できる

$ php artisan view:cache
  • パフォーマンス向上のためにキャッシュさせることができる。
$ php artisan view:clear
  • View Cacheをクリアする。

開発環境について

どこの環境でやっているかによってもろもろのコマンドが異なる。

local

そのままコマンドを叩けば良い

sail(docker)

それぞれのコマンドの前に sailをつけないといけない。

コンテナ内なのかローカルなのかの意識を持っておかないとここらへんで混乱してくる。コンテナ内での開発なら当然コンテナ内にインストールやらアップデートやらをしないといけないよね。sailコマンドはdockerをラップしているわけだから、sailコマンドを叩くということは、内部的にdockerコマンドを叩いていることになるよね。

development server

バックエンド用の開発サーバーとフロントエンド用の開発サーバーがそれぞれある

基本的には連携して使うことが多いから、両方立ち上げることが多いが、どっちかだけという場合もある。

sail up -dphp artisan serveなんかがバックエンド用の開発サーバーの起動コマンド

npm run devがフロントエンド用の開発サーバーの起動コマンド

start project

プロジェクトの作成

sailで作成する

# 最新バージョンでプロジェクト作成
curl -s https://laravel.build/プロジェクト名 | bash

※ バージョンを指定したい場合はcomposerで作成する

composerで作成する

# バージョンを指定してプロジェクト作成
composer create-project laravel/laravel プロジェクト名 --prefer-dist "10.*"
cd プロジェクト名
composer require laravel/sail --dev
php artisan sail:install
sail up -d

以下のようなエラーが出る時があるが、composerのバージョンを最新にしてあげると解決する場合が多い

The "https://repo.packagist.org/p2/carbonphp/carbon-doctrine-types~dev.json" file could not be downloaded: Failed to open stream: Network is unreachable

開発環境の起動

$ sail up -d
> .bashrcにエイリアスを貼ってある。なければ ./vendor/bin/sail up

開発環境の終了

$ sail stop

DBへの接続

$ sail mysql -u sail -ppassword

$ sail mysql <dbName>

APIを作ってみる

$ php artisan make:model Product -mfs --api

   INFO  Model [app/Models/Product.php] created successfully.

   INFO  Factory [database/factories/ProductFactory.php] created successfully.

   INFO  Migration [database/migrations/2024_01_11_010607_create_products_table.php] created successfully.

   INFO  Seeder [database/seeders/ProductSeeder.php] created successfully.

   INFO  Controller [app/Http/Controllers/ProductController.php] created successfully.

-m:マイグレーションファイル作成

-f:ファクトリ作成

-s:シーダー作成

--api:APIのCRUD用のコントローラー作成

マイグレーションファイルの定義

#database/migrations/2024_01_11_010607_create_products_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('text'); //追加
            $table->longText('description'); //追加
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};

モデルの定義

#app/Models/Product.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use HasFactory;

    //以下4行追加
    protected $fillable = [
        'title',
        'description',
    ];
}

ファクトリの定義

# database/factories/ProductFactory.php

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Product>
 */
class ProductFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'title' => $this->faker->title(), //追加
            'description' => $this->faker->text(), //追加
        ];
    }
}

シーダー定義

#database/seeders/ProductSeeder.php

<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Product; //追加

class ProductSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        Product::factory()->count(3)->create(); //追加
    }
}

#database/seeders/DatabaseSeeder.php

<?php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        //以下3行追加
        $this->call([
            ProductSeeder::class,
        ]);
    }
}

マイグレートとシーディング

$ php artisan migrate --seed

tinkerで確認

$ sail tinker
> \App\Models\Product::all()
= Illuminate\Database\Eloquent\Collection {#5699
    all: [
      App\Models\Product {#5956
        id: 1,
        title: "Mr.",
        description: "Nulla sit facilis cumque et. Omnis non eligendi suscipit. Occaecati voluptatem quo porro occaecati. Qui sed est officiis ut nihil architecto.",
        created_at: "2024-01-11 02:10:37",
        updated_at: "2024-01-11 02:10:37",
      },
      App\Models\Product {#5957
        id: 2,
        title: "Dr.",
        description: "Explicabo omnis perferendis repudiandae repudiandae. Fuga pariatur saepe cupiditate dolor.",
        created_at: "2024-01-11 02:10:37",
        updated_at: "2024-01-11 02:10:37",
      },
      App\Models\Product {#5958
        id: 3,
        title: "Dr.",
        description: "Aut voluptate id officiis ea dolores. Cumque nihil totam maiores. Et odit placeat itaque ut est impedit. Nisi quo ab inventore nemo vel.",
        created_at: "2024-01-11 02:10:37",
        updated_at: "2024-01-11 02:10:37",
      },
    ],
  }

データの取得

controllerの定義

#app/Http/Controllers/ProductController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Product; //追加

class ProductController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        //以下4行追加
        $products = Product::all();
        return response()->json([
            'products' => $products,
        ], 200);
    }

...省略...

routesの定義

#routes/api.php

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController; //追加

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Route::apiResource('products', ProductController::class); //追加

postmanで確認

データの登録

requestの定義

$ sail php artisan make:request StoreProductRequest
# app/Http/Requests/StoreProductRequest.php

<?php

namespace App\Http\Requests;

use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException; //追加

class StoreProductRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true; //修正
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'title' => ['required', 'string'], //追加
            'description' => ['required', 'string'], //追加
        ];
    }

    //以下追加
    protected function failedValidation(Validator $validator)
    {
        $res = response()->json([
            'message' => 'Failed create product',
            'errors' => $validator->errors(),
        ], 400);
        throw new HttpResponseException($res);
    }
}

controllerの定義

# app/Http/Controllers/ProductController.php

...省略...
    
use App\Http\Requests\StoreProductRequest; //追加

class ProductController extends Controller
{
    
...省略...
    
    public function store(StoreProductRequest $request) //修正
    {
        //以下5行追加
        $product = Product::create($request->all());
        return response()->json([
            'message' => 'Product created successfully!',
            'product' => $product,
        ], 200);
    }

...省略...

postmanで確認

  • jsonオブジェクト無のボディでpostリクエストする

  • jsonオブジェクト有のボディでpostリクエストする

$ sail php artisan
> \App\Models\Product::all()
= Illuminate\Database\Eloquent\Collection {#5960
    all: [
    
	...省略...

      App\Models\Product {#5965
        id: 4,
        title: "input title",
        description: "input description",
        created_at: "2024-01-11 02:51:08",
        updated_at: "2024-01-11 02:51:08",
      },
    ],
  }

sanctum

laravelでapiを作成する時に使うもの

Formを作ってみる

確認画面をどう作るか

sessionを使う

大きな流れは

  • フォームからの遷移先でセッションに入力値を保存
  • 確認画面の表示はセッションの入力値を使う
  • 確認画面からの遷移先もセッションの入力値を使う
  • 送信処理(確認画面からの遷移先)で二重投稿にならないようにセッションの値を空にする

show→post(sessionに入力値を保存)→confirm(sessionから入力値を取り出す)→send(sessionから入力値を取り出して送信、sessionの入力値を削除)→complete

DBを使う

  • 入力データをデータベースに一時保存し、確認画面でそのデータを参照する。最終的に送信する際に、データベースから情報を取得して処理を行う。
  • この方法はデータが大量である場合や、複数ステップにわたるフォーム処理に適している。

inputの値を受け継いでいく

  • こっちを基本的に使う
  • sessionを上手く使っていく

File Upload

基本のコード

    public function store(Request $request)
    {
		...

        if ($request->file('image')) {
            $imageName = $request->file('image')->store('public/upload');
            $formData['image'] = $imageName;
        }

        Ramen::create($formData);

        return to_route('admin.ramen.index');
    }

    public function store(Request $request)
    {
		...

        // bladeの場合はこっちを使う
        if ($request->file('image')) {
            $imageName = $request->file('image')->store('public/upload');
            $formData['image'] = $imageName;
        }
        
        // inertiaの場合はこっちを使う
        if ($image = $request->file('image')) {
            $imageName = $image->getFilename(). '.'. $image->getClientOriginalExtension();
            $image->storeAs('public/upload', $imageName);
            $formData['image'] = $imageName;
        }

        Ramen::create($formData);

        return to_route('admin.ramen.index');
    }

公開

  • Laravelでは画像の参照先はstorage/app/public/
  • ただし、そのままではビューで参照することがでない
  • storageディレクトリを参照するためにはシンボリックリンクが必要
php artisan storage:link

Inertia の場合は注意。

cd public
ln -s ../storage/public/upload

Deployしてみる

https://readouble.com/laravel/10.x/ja/deployment.html

オートローダーの最適化

composer install --optimize-autoloader --no-dev

設定のキャッシュ

php artisan config:cache

ルートのキャッシュ

php artisan route:cache

ビューのキャッシュ

php artisan view:cache

デバッグモード

# .env or config/app.php
APP_DEBUG=false

Conoha Wing

流れ

  1. 本番環境とリポジトリをSSH接続可能に

  2. publicディレクトリ内にルーティングさせる

    # /
    
    <IfModule mod_rewrite.c>
    	RewriteEngine On
        RewriteRule ^(.*)$ appName/public/$1 [L]
    </IfModule>
    
  3. バックエンド

    1. composer

      cd app-name
      ~/bin/composer install
      
    2. application encryption key

      php artisan key:generate
      
    3. 環境変数

      cp .env.example .env
      
      -bash-4.2$ diff -uwBb .env.example .env
      --- .env.example        2024-05-21 09:23:34.117917782 +0900
      +++ .env        2024-05-21 09:37:47.499062046 +0900
      @@ -1,19 +1,19 @@
      -APP_NAME=Laravel
      -APP_ENV=local
      +APP_NAME=hoge
      +APP_ENV=production
       APP_KEY=
      -APP_DEBUG=true
      -APP_URL=http://localhost
      +APP_DEBUG=false
      +APP_URL=https://hoge
      +ASSET_URL=https://hoge
      
       LOG_CHANNEL=stack
       LOG_DEPRECATIONS_CHANNEL=null
      -LOG_LEVEL=debug
      +LOG_LEVEL=error
      
       DB_CONNECTION=mysql
      -DB_HOST=127.0.0.1
      +DB_HOST=hoge_host
       DB_PORT=3306
      -DB_DATABASE=app_name
      -DB_USERNAME=root
      -DB_PASSWORD=
      +DB_DATABASE=hoge_db
      +DB_USERNAME=hoge_user
      +DB_PASSWORD=hoge_pw
      
       BROADCAST_DRIVER=log
       CACHE_DRIVER=file
      
    4. public/.htaccess

      --- .old.htaccess       2024-05-22 21:14:32.710679534 +0900
      +++ .htaccess   2024-05-22 21:14:55.664738128 +0900
      @@ -1,8 +1,4 @@
       <IfModule mod_rewrite.c>
      -    <IfModule mod_negotiation.c>
      -        Options -MultiViews -Indexes
      -    </IfModule>
      -
           RewriteEngine On
      
           # Handle Authorization Header
      
    5. AppServiceProvider

      https://laracasts.com/discuss/channels/vite/laravel-vite-assets-blockedmixed-content-issues-in-production-environment

      diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
      index 452e6b6..03e735c 100644
      --- a/app/Providers/AppServiceProvider.php
      +++ b/app/Providers/AppServiceProvider.php
      @@ -2,6 +2,7 @@
      
       namespace App\Providers;
      
      +use Illuminate\Support\Facades\URL;
       use Illuminate\Support\ServiceProvider;
      
       class AppServiceProvider extends ServiceProvider
      @@ -19,6 +20,8 @@ public function register(): void
            */
           public function boot(): void
           {
      -        //
      +        if($this->app->environment('production')) {
      +            URL::forceScheme('https');
      +        }
           }
       }
      
    6. dbのテーブル作成

      php artisan migrate
      
  4. フロントエンド

    • ローカルでプロダクションビルドしてアップロード

          "scripts": {
              "dev": "vite",
              "build": "vite build",
      +       "build:prd": "vite build --mode production"
          },
      
      sail npm run build:prd
      
    • 本番環境でビルドする

      • nodeとnpmをインストールする

        https://chigusa-web.com/blog/conoha-wing-laravel/

        • nodebrewのインストール

          ダウンロードする

          cd ~/bin
          wget git.io/nodebrew
          perl nodebrew setup
          

          パスを通す

          vim ~/.bash_profile
          > #以下に変更もしくは追記
          > PATH=$HOME/.nodebrew/current/bin:$PATH:$HOME/.local/bin:$HOME/bin
          
          source ~/.bash_profile
          nodebrew -v
          
        • node.jsをインストール

          nodebrew install-binary v16.20.0
          nodebrew ls-remote
          nodebrew list
          nodebrew use v16.20.0
          node -v
          npm -v
          

          v20.9.0だと必要パッケージが無くて怒られたので注意。

      • Nodeパッケージのインストールとビルド

        • Nodeパッケージのインストール

          cd app
          npm install
          
        • ビルド

          npm run buil
          
0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?