1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

環境

  • PHP 8.2
  • Laravel 11

注釈
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
こんな感じのページが表示されました。

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ページ

Chirpsの作成

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

モデル・マイグレーション・コントローラー

ユーザーがChirpを投稿できるようにするには、モデル・マイグレーション・コントローラーを作成する必要があります。

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

Laravelではこれらの機能をartisanコマンドを使用して作成し、構築していく。
次のコマンドを使用して、Chirpのモデル・マイグレーション・コントローラーを作成する。

php artisan make:model -mrc Chirp

php artisan make:model --helpコマンドを使用すると利用可能なオプションを確認することができます。

先ほどのコマンドにより、次の3つのファイルが作成されます。

  • app/Models/Chirp.php - モデル
  • database/migrations/<timestamp>_create_chirps_table.php - DBテーブルを作成するマイグレーション
  • app/Http/Controllers/ChirpController.php - 受信したリクエストを受け取り、応答を返すHTTPコントローラー

ルーティング

コントローラーのURLを作成する必要があります。routesディレクトリを編集することでルーティングの設定をしていきます。
下記の2つのルートを設定します

  • indexにはフォームとChirpの一覧が表示される
  • storeには新しいChirpを保存するためのページを設定する

また、2つのルートに2つのミドルウェアを設定します。

  • 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コマンドで確認できます。
ChirpControllerindexクラスを編集してルートとコントローラーをテストします。

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を表示するため、ChirpControllerindexメソッドを更新し、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>

これでブラウザのページを更新すると、デフォルトレイアウトによりフォームがレンダリングされるようになります。

デフォルトレイアウト

ナビゲーションメニュー

続いて、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/_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字の字数制限が機能していることを確認します。

文字数制限を確認

空文字制限を確認

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

Chirpの表示

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

Chirpの取得

ChirpControllerindexを編集して、すべてのユーザーからの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の表示

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

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);
    }
 ...
}

テスト

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

編集ボタンの動作確認

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>

テスト

ブラウザで削除できるか試してみましょう!

削除の動作確認

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

通知とイベント

新しい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を使用しました。
使い方については、こちらを参考にしてください。

メールの動作確認

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

おわり

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

1
2
0

Register as a new user and use Qiita more conveniently

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?