はじめてのLaravel 〜 Blade #2

# 環境
- PHP 8.2
- Laravel 11

:::note warn
注釈
Laravelの環境構築は完了済みの状態からスタートします
:::

# インストール
Composerを使用してローカル環境でLaravelプロジェクトを作成します。

~~~
composer create-project laravel/laravel chirper
~~~

phpのバージョンがサポート期限内であることが必要なようなので、うまくいかない場合は`` php -v ``などでバージョンを確認して、必要に応じ更新しておくと良さそうです。

Composerの`` create-project ``コマンドでは、DBとしてSQLiteデータベースを`` database/database.sqlite ``に自動的に作成してくれるようです。
プロジェクトが作成されたら、Laravelのartisanコマンドを使用してローカル開発サーバーを起動します。

```
cd chirper
php artisan serve
```

これによりhttp://localhost:8000 でアプリにアクセスできるようになります。
![Laravel index](https://bootcamp.laravel.com/img/screenshots/fresh.png)
こんな感じのページが表示されました。

# Laravel Breezeのインストール
次に、Laravel Breezeをインストールしていきます。
Laravel Breezeとはログイン・アカウント登録・パスワードリセット・メール認証・パスワード確認などLaravelにおける認証機能をシンプルに実装できるものです。

Laravel Breezeは、BladeテンプレートやVue、Reactなどのビューレイヤーを扱えるようですが、今回はBladeチュートリアルのためBladeを使用します。

ターミナルでchirperディレクトリを開き、スタックをインストールしていきます。

```
composer require laravel/breeze --dev
php artisan breeze:install blade
```

Breezeはフロントエンドの依存関係をインストールして構成するため、Bladeテンプレートに変更を加えた際は、Viteサーバーを起動してCSSを自動的に再コンパイルしてブラウザを更新する。

```
npm run dev
php artisan serve
```

localhost:8000にアクセスするとサイトの右上に「Log in」と「Register」が表示されているはずです。
Registerをクリックして、アカウント登録していきます。

![Regiterページ](https://bootcamp.laravel.com/img/screenshots/register.png)

# Chirpsの作成
ここからはChirpsと呼ばれるショートメッセージをユーザーが投稿できるように構築していきます。

## モデル・マイグレーション・コントローラー
ユーザーがChirpを投稿できるようにするには、モデル・マイグレーション・コントローラーを作成する必要があります。
<dl>
    <dt>モデル</dt>
    <dd>DB内のテーブル操作を行うためのインターフェースを提供します</dd>
    <dt>マイグレーション</dt>
    <dd>DB内のテーブルを簡単に作成・変更する。マイグレーションにより、アプリケーションが実行される全ての場所で同じDB構造を構築できる。</dd>
    <dt>コントローラー</dt>
    <dd>アプリケーションに対して行われたリクエストを処理し、応答を返す役割を担う。</dd>
</dl>

Laravelではこれらの機能をartisanコマンドを使用して作成し、構築していく。
次のコマンドを使用して、Chirpのモデル・マイグレーション・コントローラーを作成する。
```
php artisan make:model -mrc Chirp
```
`` php artisan make:model --help ``コマンドを使用すると利用可能なオプションを確認することができます。

先ほどのコマンドにより、次の３つのファイルが作成されます。
- `` app/Models/Chirp.php `` - モデル
- `` database/migrations/<timestamp>_create_chirps_table.php `` - DBテーブルを作成するマイグレーション
- `` app/Http/Controllers/ChirpController.php `` - 受信したリクエストを受け取り、応答を返すHTTPコントローラー

## ルーティング
コントローラーのURLを作成する必要があります。`` routes ``ディレクトリを編集することでルーティングの設定をしていきます。
下記の２つのルートを設定します
- `` index ``にはフォームとChirpの一覧が表示される
- `` store ``には新しいChirpを保存するためのページを設定する

また、２つのルートに２つのミドルウェアを設定します。
- `` auth ``：ログインしたユーザーのみがアクセスできるようにする
- `` verified ``：メール認証を有効にする

```route/web.php
<?php

use App\Http\Controllers\ChirpController;
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;

Route:get('/', function () {
    return view('welcome');
});

Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
 
Route::middleware('auth')->group(function () {
    Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
    Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
    Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});

Route::resource('chirps', ChirpController::class)
    ->only(['index', 'store'])
    ->middlewere(['auth', 'verified']);

require __DIR__.'/auth.php';
```
これにより以下のルートが作成されます。
| リクエスト | URI | アクション | ルート名 |
|:-:|:-:|:-:|:-:|
| GET | `` /chirps `` | index | chirps.index |
| POST | `` /chirps `` | store | chirps.store |

ルートを確認したいときは、`` php artisan route:list ``コマンドで確認できます。
`` ChirpController ``の`` index ``クラスを編集してルートとコントローラーをテストします。

```app/Http/Controllers/ChirpController.php
<?php

use Illuminate\Http\Response;

class ChirpController extends Controller
{
    /**
    * Display a listing of the resource.
    */
    public function index(): Response
    {
        return response('Hello, world!');
    }
}
```
ログイン済みの場合は、https://localhost:8000/chirps にアクセスすると、「Hello, world!」が表示されます。

## Blade
それではBladeを表示するため、`` ChirpController ``の`` index ``メソッドを更新し、Bladeビューをレンダリングします。
```app/Http/Controllers/ChirpController.php
<?php
...
use Illuminate\View\View;

class ChirpController extends Controller
{
    /**
    * Display a listing of the resource.
    */
    public function index(): View
    {
        return view('chirps.index');
    }
...
}
```
次に新しいChirpを作成するためのフォームを含むBladeテンプレートビューを作成する。
```resources/views/chirps/index.blade.php
<x-app-layout>
    <div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
        <form method="POST" action="{{ route('chirps.store') }}">
            @csrf
            <textarea 
                name="message" 
                placeholder="{{ __('What\'s on your mind?')}}"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"
            >{{ old('message') }}</textarea>
            <x-input-error :messages="$errors->get('message')" class="mt-2" />
            <x-primary-button class="mt-4">{{ __('Chirp') }}</x-primary-button>
        </form>
    </div>
</x-app-layout>
```
これでブラウザのページを更新すると、デフォルトレイアウトによりフォームがレンダリングされるようになります。

![デフォルトレイアウト](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662420/3ddfef6e-1404-74b6-6328-42529e46b873.png)

## ナビゲーションメニュー
続いて、Breezeが提供するナビゲーションメニューにリンクを追加してみましょう。
`` navigation.blade.php ``を編集して、デスクトップ画面のメニュー項目を追加します。

```resources/views/layouts/navigation.blade.php
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
    <x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
        {{ __('Dashboard') }}
    </x-nav-link>
    <x-nav-link :href="route('chirps.index')" :active="request()->routeIs('chirps.index')">
        {{ __('Chirps') }}
    </x-nav-link>
</div>
```

モバイル画面の場合も同様です。

```resources/views/layouts/navigation.blade.php
<div class="pt-2 pb-3 space-y-1">
    <x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
        {{ __('Dashboard') }}
    </x-responsive-nav-link>
    <x-responsive-nav-link :href="route('chirps.index')" :active="request()->routeIs('chirps.index')">
        {{ __('Chirps') }}
    </x-responsive-nav-link>
</div>
```

## Chirpを投稿する
フォームを使用して新しいChirpを作成できるように`` chirps.store ``メソッドを設定していきます。

```app/Http/Controllers/ChirpController.php
<?php
 ...
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\View\View;
 
class ChirpController extends Controller
{
 ...
    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'message' => 'required|string|max:255',
        ]);
 
        $request->user()->chirps()->create($validated);
 
        return redirect(route('chirps.index'));
    }
 ...
}

```

Laravelの検証機能を使用して、投稿するメッセージが255文字以内であることを確認します。
次に、リレーションシップを活用してログインしたユーザーに属するレコードを作成し、最後にリダイレクトによって`` chirps.index ``に返します。

## リレーションシップの作成
先ほどのコントローラーに設定したリレーションシップを定義していきます。

```app/Models/User.php
<?php
 ...
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
 
class User extends Authenticatable
{
 ...
    public function chirps(): HasMany
    {
        return $this->hasMany(Chirp::class);
    }
}
```

## 割り当て保護
アプリケーションのすべてのデータをリクエストからユーザーに渡すことは危険なため、ユーザーが過剰な権限を得ないように一括割り当てによって保護します。

```app/Models/Chirp.php
<?php
 ...
class Chirp extends Model
{
 ...
    protected $fillable = [
        'message',
    ];
}
```

## マイグレーションの更新
アプリケーション作成中に、マイグレーションを適用されるため、下記コマンドによりDB構造を確認できます。

```
php artisan db:show
php artisan db:table users
```

現在ChirpとUserの関係を結びつける列が不足しているため、マイグレーションを更新して設定していきましょう。

```database/migrations/<timestamp>_create_chirps_table.php
<?php
 ...
return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('chirps', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->string('message');
            $table->timestamps();
        });
    }
 ...
};
```

編集が完了したら、マイグレーションしましょう。
```
php artisan migrate
```
データベースを最初から再構築する必要がある場合は、`` php artisan migrate:fresh ``により対応します。

## テスト
作成したフォームによってChirpを投稿する準備ができました。
まずは空メッセージと255字の字数制限が機能していることを確認します。

![文字数制限を確認](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662420/e36b3b39-6894-74c1-87f5-3c8e1fa92c15.png)

![空文字制限を確認](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662420/17ed1a26-7c91-a53b-2c4e-339c1d180a18.png)

しっかりと空文字と文字数への検証が行われています。
現在はまだ投稿したChirpを表示する設定をしていないため、表示はされませんが投稿はできるようになりました。

# Chirpの表示
さて、Chirpを投稿する機能を追加したことでChirpを表示する準備が整いました。
いよいよChirp一覧を表示していきましょう。

## Chirpの取得
`` ChirpController ``の`` index ``を編集して、すべてのユーザーからのChirpをページに渡していきます。

```app/Http/Controllers/ChirpController.php
<?php
 ...
class ChirpController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index(): View
    {
        return view('chirps.index', [
            'chirps' => Chirp::with('user')->latest()->get(),
        ]);
    }
 ...
}
```

Eloquentの`` with ``メソッドを使用して、Chirpに関連づけたユーザーを読み込んでいます。また、`` latest ``スコープにより、Chirpを最新順にして返しています。

## Chirpの作成者を取得する
Chirpの作成者を表示するため、ChirpとUserの関係情報を返すように設定していきます。

```app/Models/Chirp.php
<?php
 ...
use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
class Chirp extends Model
{
 ...
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}
```

## ビューの更新
次に、フォームの下にChirpを表示するため、`` chirps.index ``コンポーネントを更新します。

```resources/views/chirps/index.blade.php
<x-app-layout>
    <div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
        <form method="POST" action="{{ route('chirps.store') }}">
            @csrf
            <textarea
                name="message"
                placeholder="{{ __('What\'s on your mind?') }}"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"
            >{{ old('message') }}</textarea>
            <x-input-error :messages="$errors->store->get('message')" class="mt-2" />
            <x-primary-button class="mt-4">{{ __('Chirp') }}</x-primary-button>
        </form>
 
        <div class="mt-6 bg-white shadow-sm rounded-lg divide-y">
            @foreach ($chirps as $chirp)
                <div class="p-6 flex space-x-2">
                    <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600 -scale-x-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
                    </svg>
                    <div class="flex-1">
                        <div class="flex justify-between items-center">
                            <div>
                                <span class="text-gray-800">{{ $chirp->user->name }}</span>
                                <small class="ml-2 text-sm text-gray-600">{{ $chirp->created_at->format('j M Y, g:i a') }}</small>
                            </div>
                        </div>
                        <p class="mt-4 text-lg text-gray-900">{{ $chirp->message }}</p>
                    </div>
                </div>
            @endforeach
        </div>
    </div>
</x-app-layout>
```

早速ブラウザで先ほど投稿したChirpが表示されているか確認してみましょう。

![Chirpの表示](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662420/fc3faedb-659e-9306-66ce-a7cd635d5d7a.png)

しっかりと投稿できていますね。

# Chirpの編集
ここからはChirpの編集機能を作成していきます。

## ルーティング
まずはルーティングを編集して、編集のためのルートを作成していきましょう。

```routes/web.php
<?php
 ...
Route::resource('chirps', ChirpController::class)
    ->only(['index', 'store', 'edit', 'update'])
    ->middleware(['auth', 'verified']);
 ...
```

コントローラーのルートテーブルはこのようになります。
| リクエスト | URI | アクション | ルート名 |
|:-:|:-:|:-:|:-:|
| GET | `` /chirps `` | index | chirps.index |
| POST | `` /chirps `` | store | chirps.store |
| GET | `` /chirps/{chirp}/edit `` | edit | chirps.edit |
| PUT/PATCH | `` /chirps/{chirp} `` | update | chirps.update |

## 編集ページへのリンク
次にBladeを編集して編集ページへのリンクを作成していきます。
Chirpの作成者にのみ編集を許可し、編集されたかどうかも判断できるようにしましょう。

```resources/views/chirps/index.blade.php
<x-app-layout>
    <div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
        <form method="POST" action="{{ route('chirps.store') }}">
            @csrf
            <textarea
                name="message"
                placeholder="{{ __('What\'s on your mind?') }}"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"
            >{{ old('message') }}</textarea>
            <x-input-error :messages="$errors->get('message')" class="mt-2" />
            <x-primary-button class="mt-4">{{ __('Chirp') }}</x-primary-button>
        </form>
 
        <div class="mt-6 bg-white shadow-sm rounded-lg divide-y">
            @foreach ($chirps as $chirp)
                <div class="p-6 flex space-x-2">
                    <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600 -scale-x-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
                    </svg>
                    <div class="flex-1">
                        <div class="flex justify-between items-center">
                            <div>
                                <span class="text-gray-800">{{ $chirp->user->name }}</span>
                                <small class="ml-2 text-sm text-gray-600">{{ $chirp->created_at->format('j M Y, g:i a') }}</small>
                                @unless ($chirp->created_at->eq($chirp->updated_at))
                                    <small class="text-sm text-gray-600"> &middot; {{ __('edited') }}</small>
                                @endunless
                            </div>
                            @if ($chirp->user->is(auth()->user()))
                                <x-dropdown>
                                    <x-slot name="trigger">
                                        <button>
                                            <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
                                                <path d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z" />
                                            </svg>
                                        </button>
                                    </x-slot>
                                    <x-slot name="content">
                                        <x-dropdown-link :href="route('chirps.edit', $chirp)">
                                            {{ __('Edit') }}
                                        </x-dropdown-link>
                                    </x-slot>
                                </x-dropdown>
                            @endif
                        </div>
                        <p class="mt-4 text-lg text-gray-900">{{ $chirp->message }}</p>
                    </div>
                </div>
            @endforeach
        </div>
    </div>
</x-app-layout>
```

## 編集フォームの作成
Chirpを編集するため、フォームを含む新しいBladeビューを作成します。
ほとんどindexページのフォームと一緒ですが、既存の投稿データを受け取り、入力フォームに事前入力されるようにします。

```resources/views/chirps/edit.blade.php
<x-app-layout>
    <div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
        <form method="POST" action="{{ route('chirps.update', $chirp) }}">
            @csrf
            @method('patch')
            <textarea
                name="message"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"
            >{{ old('message', $chirp->message) }}</textarea>
            <x-input-error :messages="$errors->get('message')" class="mt-2" />
            <div class="mt-4 space-x-2">
                <x-primary-button>{{ __('Save') }}</x-primary-button>
                <a href="{{ route('chirps.index') }}">{{ __('Cancel') }}</a>
            </div>
        </form>
    </div>
</x-app-layout>
```

## コントローラーの更新
編集フォームを表示するため、コントローラーを更新していきます。
編集ボタンはChirpの作成者にのみ表示されますが、リクエストしたユーザーが作成者であることを改めて確認します。

```app/Http/Controllers/Chirp/Controller.php
?php
 ...
use Illuminate\Support\Facades\Gate;
use Illuminate\View\View;
 
class ChirpController extends Controller
{
 ...
    /**
     * Show the form for editing the specified resource.
     */
     public function edit(Chirp $chirp): View
    {
        Gate::authorize('update', $chirp);
 
        return view('chirps.edit', [
            'chirp' => $chirp,
        ]);
    }
    /**
     * Update the specified resource in storage.
     */
     public function update(Request $request, Chirp $chirp): RedirectResponse
    {
        Gate::authorize('update', $chirp);
 
        $validated = $request->validate([
            'message' => 'required|string|max:255',
        ]);
 
        $chirp->update($validated);
 
        return redirect(route('chirps.index'));
    }
 ...
}
```

## 承認
デフォルトの設定ではChirpを誰も編集できないように設定されています。
モデルポリシーを作成し、編集権限を設定していきましょう。
```
php artisan make:policy ChirpPolicy --model=Chirp
```
これにより、Chirpの作成者のみが編集する権限を持つように指定するためのポリシークラスを作成します。

```app/Policies/ChirpPolicy.php
<?php
 ...
class ChirpPolicy
{
 ...
    /**
     * Determine whether the user can update the model.
     */
    public function update(User $user, Chirp $chirp): bool
    {
        return $chirp->user()->is($user);
    }
 ...
}
```

## テスト
ブラウザで動作を確認してみましょう。
別アカウントを作成して、自分の投稿だけ編集できることも確認します。

![編集ボタンの動作確認](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662420/e2fa243d-7a84-0665-2903-d24a152149a9.png)

# Chirpの削除
次にChirpを削除する機能を作成していきましょう。

## ルーティング
削除ページを作成するため、ルーティングを編集していきます。

```routes/web.php
<?php
 ...
Route::resource('chirps', ChirpController::class)
    ->only(['index', 'store', 'edit', 'update', 'destroy'])
    ->middleware(['auth', 'verified']);
 ...
```

これにより、コントローラーのルートテーブルは以下のようになります。
| リクエスト | URI | アクション | ルート名 |
|:-:|:-:|:-:|:-:|
| GET | `` /chirps `` | index | chirps.index |
| POST | `` /chirps `` | store | chirps.store |
| GET | `` /chirps/{chirp}/edit `` | edit | chirps.edit |
| PUT/PATCH | `` /chirps/{chirp} `` | update | chirps.update |
| DELETE | `` /chirps/{chirp} `` | destroy | chirps.destroy |

## コントローラーの更新
`` ChirpController ``にて`` destroy ``クラスのメソッドを編集し、Chirpを削除してindexに戻るように設定します。

```app/Http/Controllers/ChirpController.php
<?php
 ...
class ChirpController extends Controller
{
 ...
    /**
     * Remove the specified resource from storage.
     */
     public function destroy(Chirp $chirp): RedirectResponse
    {
        Gate::authorize('delete', $chirp);
 
        $chirp->delete();
 
        return redirect(route('chirps.index'));
    }
}
```

## 承認
編集と同様にChirpの作成者だけが自分のChirpを削除できるようにしたいので、`` ChirpPolicy ``の`` dalete``クラスのメソッドを編集しましょう。

```app/Policies/ChirpPolicy.php
<?php
 ...
class ChirpPolicy
{
 ...
    /**
     * Determine whether the user can delete the model.
     */
    public function delete(User $user, Chirp $chirp): bool
    {
        return $this->update($user, $chirp);
    }
 ...
}
```

実行する内容は編集の際と同様ですので、`` update ``メソッドを呼び出して同じロジックを定義します。

## ビューの更新
最後に、indexビューのドロップダウンメニューに削除ボタンを追加します。

```resources/views/chirps/index.blade.php
<x-app-layout>
    <div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
        <form method="POST" action="{{ route('chirps.store') }}">
            @csrf
            <textarea
                name="message"
                placeholder="{{ __('What\'s on your mind?') }}"
                class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"
            >{{ old('message') }}</textarea>
            <x-input-error :messages="$errors->get('message')" class="mt-2" />
            <x-primary-button class="mt-4">{{ __('Chirp') }}</x-primary-button>
        </form>
 
        <div class="mt-6 bg-white shadow-sm rounded-lg divide-y">
            @foreach ($chirps as $chirp)
                <div class="p-6 flex space-x-2">
                    <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600 -scale-x-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
                    </svg>
                    <div class="flex-1">
                        <div class="flex justify-between items-center">
                            <div>
                                <span class="text-gray-800">{{ $chirp->user->name }}</span>
                                <small class="ml-2 text-sm text-gray-600">{{ $chirp->created_at->format('j M Y, g:i a') }}</small>
                                @unless ($chirp->created_at->eq($chirp->updated_at))
                                    <small class="text-sm text-gray-600"> &middot; {{ __('edited') }}</small>
                                @endunless
                            </div>
                            @if ($chirp->user->is(auth()->user()))
                                <x-dropdown>
                                    <x-slot name="trigger">
                                        <button>
                                            <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
                                                <path d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z" />
                                            </svg>
                                        </button>
                                    </x-slot>
                                    <x-slot name="content">
                                        <x-dropdown-link :href="route('chirps.edit', $chirp)">
                                            {{ __('Edit') }}
                                        </x-dropdown-link>
                                        <form method="POST" action="{{ route('chirps.destroy', $chirp) }}">
                                            @csrf
                                            @method('delete')
                                            <x-dropdown-link :href="route('chirps.destroy', $chirp)" onclick="event.preventDefault(); this.closest('form').submit();">
                                                {{ __('Delete') }}
                                            </x-dropdown-link>
                                        </form>
                                    </x-slot>
                                </x-dropdown>
                            @endif
                        </div>
                        <p class="mt-4 text-lg text-gray-900">{{ $chirp->message }}</p>
                    </div>
                </div>
            @endforeach
        </div>
    </div>
</x-app-layout>
```

## テスト
ブラウザで削除できるか試してみましょう！

![削除の動作確認](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662420/51a49382-77da-98ca-cc61-0bd8174efb98.png)

無事削除ボタンが表示され、削除ができるようになりました。

# 通知とイベント
新しいChirpが作成された時、メール通知を送信できるようにしましょう。

メール送信のサポートに加えて、LaravelはSMSやSlackなどの通知もサポートしているようです。また、通知はDBに保存することもできるため、Webインターフェースに表示することも可能です。

## 通知の作成
artisanコマンドを使用して、通知を作成していきましょう。

```
php artisan make:notification NewChirp
```
これにより、`` NewChirp.php ``をカスタマイズして、作成者の名前と投稿内容の一部を含む通知を設定していきましょう。

```app/Notifications/NewChirp.php
<?php
namespace App\Notifications;
 
use App\Models\Chirp;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Str;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
 
class NewChirp extends Notification
{
    use Queueable;
 
    /**
     * Create a new notification instance.
     */
     public function __construct(public Chirp $chirp)
    {
        //
    }
 ...
    /**
     * Get the mail representation of the notification.
     */
    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject("New Chirp from {$this->chirp->user->name}")
                    ->greeting("New Chirp from {$this->chirp->user->name}")
                    ->line(Str::limit($this->chirp->message, 50))
                    ->action('Go to Chirper', url('/'))
                    ->line('Thank you for using our application!');
    }
 ...
}
```

`` ChirpController ``から直接通知を送信することもできますが、そうするとコントローラーの作業量が増え、特にDBを照会してメール送信を行う場合は、処理速度が遅くなります。

代わりに、アプリケーションを高速に保つため、バッググラウンドキューで処理できるイベントを作成しましょう。

## イベントの作成
イベントは、単一のイベントに互いに依存しない複数のリスナーを所持できます。
artisanコマンドで新たなイベントを作成しましょう。
```
php artisan make:event ChirpCreated
```
新しく作成されたChirpごとにイベントを実行するため、通知を行えるようにしましょう。

```app/Events/ChirpCreated.php
<?php
 
namespace App\Events;
 
use App\Models\Chirp;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
 
class ChirpCreated
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
 
    /**
     * Create a new event instance.
     */
     public function __construct(public Chirp $chirp)
    {
        //
    }
 ...
}
```

## イベントの配信
イベントクラスを作成しましたので、Chirpが作成されるたびに通知を実行する準備ができました。
イベントはEloquentモデルの作成に関連しているため、モデルの設定によってイベントを実行できるようにします。

```app/Models/Chirp.php
<?php
 
namespace App\Models;
 
use App\Events\ChirpCreated;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
class Chirp extends Model
{
    use HasFactory;
 
    protected $fillable = [
        'message',
    ];
 
    protected $dispatchesEvents = [
        'created' => ChirpCreated::class,
    ];
 
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}
```

これでChirpが作成されるたびに、`` ChirpCreated ``イベントが送信されるようになります。

## イベントリスナーの作成
次にイベントリスナーの設定を行います。
まずはリスナーを作成しましょう。
```
php artisan make:listener SendChirpCreatedNotifications --event=ChirpCreated
```

新しいリスナーは`` app/Listner/SendChirpCreatedNotifications.php ``に配置されます。
通知を送信できるようにリスナーを設定していきましょう。

```app/Listners/SendChirpCreatedNotifications.php
<?php
 
namespace App\Listeners;
 
use App\Events\ChirpCreated;
use App\Models\User;
use App\Notifications\NewChirp;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class SendChirpCreatedNotifications implements ShouldQueue
{
 ...
    /**
     * Handle the event.
     */
    public function handle(ChirpCreated $event): void
    {
        foreach (User::whereNot('id', $event->chirp->user_id)->cursor() as $user) {
            $user->notify(new NewChirp($event->chirp));
        }
    }
}
```

デフォルトでは「データベース」キューはジョブを非同期で処理するために使用されます。キューに入れられたジョブの処理を開始するにはartisanコマンドを実行する必要があります。
```
php artisan queue:work
```

また、Chirp作成者を除くプラットフォーム全体のユーザーに通知を送信するように構成しました。実際にはフォロー機能などを作成して、フォローしているユーザーがChirpを作成した時のみ通知を送るのがいいでしょう。

## テスト
それではブラウザで動作確認していきましょう。
今回はローカルメールテストツールとして`` mailtrap ``を使用しました。
使い方については、こちらを参考にしてください。

https://qiita.com/kazumacchi/items/751232c538ff5a674b1a

![メールの動作確認](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662420/721f2a73-afa5-9976-cead-8ec4d6f3139c.png)

無事メール送信ができました。
これでChirpを作成されたときに通知ができるようになりましたね。

# おわり
Bootcampだとこの後にデプロイの方法の紹介がありますが、ここは全コース共通のようなので最後にやるとしましょう。
これにて、Bladeテンプレートでのチュートリアルは終了です。