Laravel + Livewire で CRUD を実装してみる 〜 よりシンプルを求めて 〜

こんにちは❗フリーランス・エンジニアの 九保すこひ です。

さてさて、特にJavaScriptがそうだと思いますが、フロントエンドはホントに変化が早いので、トレンドについていくのがなかなか大変じゃないでしょうか。

しかも、新しいものがすべて生き残るわけではなく、芸能界と同じくスターになれるのは「ひとつまみ(にぎりより少ない😅)」ですよね。

そして、私のパターンで言うと「シンプル&強力」なVue.jsが好きなのですが、これもバージョンが3に上がったあたりから「シンプルさ」を失ってしまった感があり、以前よりVueのニュースを聞くことが減っているような気もしています。(まだまだ人気はあると思いますが!)

そこで、いろいろと別のものを試してみるべきだろうと考えていたのですが、この度個人的な開発で livewire を使ってみることになったので、今回はその予習も兼ねて記事をお届けすることにしました。

livewireは「フロントエンドでやってることをPHP側に取り戻す」みたいなコンセプトのテクノロジーです。

そこで❗

今回はLaravel + livewireでシンプルなブログ記事のCRUD(追加・表示・編集・削除)機能をつくってみます。

ぜひ、何かの参考になりましたら嬉しいです😄✨

「なんと、Swift Mailer が
開発終了してました 😳」

開発環境: Laravel 8.x、livewire 2.8

パッケージをインストールする

livewireという名前ですが、これはcomposerのパッケージです。
なので、まずは以下のコマンドを実行してパッケージのインストールをしておいてください。

composer require livewire/livewire

これで、livewire開発の準備が整いました。
実際に開発をしていきましょう❗

DBまわりを用意する

今回DBにはarticlesというブログ記事を管理するテーブルを用意します。
そのため、まずは以下のコマンドを実行してください。

php artisan make:model Article -ms

すると、「モデル」「マイグレーション」「Seeder」の3ファイルが作成されるので、中身をそれぞれ以下のように変更してください。

app/Models/Article.php

<?php

namespace App\Models;

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

class Article extends Model
{
    use HasFactory;

    protected $guarded = ['id'];
}

※ 通常Mass assignmentは使わない主義なので$guarded$fillableはセットしないのですが、Copilotがおすすめしてきたので、今回はこっちにしてみました。

database/migrations/****_**_**_******_create_articles_table.php

// 省略

public function up()
{
    Schema::create('articles', function (Blueprint $table) {
        $table->id();
        $table->string('title')->comment('タイトル');
        $table->text('content')->comment('本文');
        $table->timestamps();
    });
}

※ なお、今回はテストですのでuser_idなどのフィールドは省略しています。

database/seeders/ArticleSeeder.php

<?php

namespace Database\Seeders;

use App\Models\Article;
use Illuminate\Database\Seeder;

class ArticleSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        for($i = 1; $i <= 50; $i++) {

            Article::create([
                'title' => 'タイトル ' . $i,
                'content' => "本文\n本文\n本文\n本文\n本文 " . $i
            ]);

        }
    }
}

Seederファイルはつくっただけでは実行できませんので、DatabaseSeeder.phpに登録しておきます。

database/seeders/DatabaseSeeder.php

// 省略

public function run()
{
    $this->call(ArticleSeeder::class);
}

では、DBを再構築してみましょう。
以下のコマンドを実行してください。

php artisan migrate:fresh --seed

すると実際のテーブルはこうなりました。

livewire のコンポーネントをつくる

では、次にlivewireのコンポーネントをつくります。
必要になるのは、次の2つです。

  • 記事の一覧(表示&編集&削除)
  • 送信フォーム(追加&編集)

では、ひとつずつ作っていきましょう❗

記事の一覧を表示するコンポーネントをつくる

まずは、記事を一覧表示するコンポーネントArticleListをつくります。
以下のコマンドを実行してください。

php artisan make:livewire ArticleList

すると、「livewire 用の PHPクラス」と「View ファイル」の2つが作成されます。

※ ちなみに「Would you like to show some love by starring the repo?」と表示された場合はそのままnoで大丈夫です。毎回以下のアスキー文字を表示するかどうかを聞かれているようです。

では、それぞれの中身です。

app/Http/Livewire/ArticleList.php

<?php

namespace App\Http\Livewire;

use App\Models\Article;
use Livewire\Component;
use Livewire\WithPagination;

class ArticleList extends Component
{
    use WithPagination;

    protected $listeners = [
        'refresh' => '$refresh', // 再読み込み
        'destroy' => 'destroy'
    ];

    public function render()
    {
        $articles = Article::paginate(10);

        return view('livewire.article-list', [
            'articles' => $articles,
        ]);
    }

    public function destroy(Article $article)
    {
        $article->delete();
    }
}

この中でやっていることは次のとおりです。

(1)$listeners

これは「イベントリスナー」で、例えば以下のようにすることで該当するメソッド呼び出すことができるようになります。

  • HTMLから呼び出す場合: wire:click=”$emit(‘******’)”
  • JavaScript で呼び出す場合: Livewire.emit(‘******’);
  • PHP で呼び出す: $this->emit(‘******’);

※ なお、コンポーネントが複数ある場合は、明示的に「どのコンポーネントのメソッドを呼び出すのか」が指定できるemitTo()を使うことをおすすめします。

// emitTo のサンプル(PHPの場合)

$this->emitTo('article-list', 'refresh');

また、$refreshlivewireが用意してくれているもので、コンポーネントの再読込みができます。

では、続いてビューです。

resources/views/livewire/article-list.blade.php

<div>

    <table class="w-full text-sm mb-5">
        <thead>
        <tr>
            <th class="border p-2">タイトル</th>
            <th class="border p-2">本文</th>
            <th class="border p-2">操作</th>
        </tr>
        </thead>
        <tbody>
        @foreach($articles as $article)
            <tr>
                <td class="border px-2 py-1">{{ $article->title }}</td>
                <td class="border px-2 py-1">{{ $article->content }}</td>
                <td class="border px-2 py-1 text-right">
                    <button
                        type="button"
                        class="bg-yellow-500 text-yellow-50 rounded p-2 text-xs"
                        wire:click="$emitTo('article-input', 'edit', {{ $article->id }})">
                        変更
                    </button>
                    <button
                        type="button"
                        class="bg-red-600 text-red-50 rounded p-2 text-xs"
                        onClick="onDelete({{ $article->id }})">
                        削除
                    </button>
                </td>
            </tr>
        @endforeach
        </tbody>
    </table>

    {{ $articles->links() }}

    <script>

        function onDelete(id) {

            if(confirm('削除します。よろしいですか?')) {

                Livewire.emitTo('article-list', 'destroy', id);

            }

        }

    </script>

</div>

この中で特徴的なのは、wire:clickの部分ですが、ここが先ほどご紹介した「HTMLからメソッドを呼び出す」部分になります。

つまり、今回の例で言うとArticleInputedit()を実行することができます。

また、onDelete()の部分は確認ダイアログを通したいので、あえてJavaScript内で使っていますが、この中では「JavaScriptからメソッドを呼び出す」方法を使っています。

// ArticleList の destroy() を ID をつけて実行

Livewire.emitTo('article-list', 'destroy', id);

なお、一点気をつけないといけないのは、ビューの一番外側のタグは1つしかセットできないということです。

例えば、以下の例はうまくいかなくなってしまいます。

<!-- ⚠ これは間違った例です -->

<div>1つ目</div>
<div>2つ目</div>

送信フォームのコンポーネントをつくる

では、続いて「送信フォーム」のコンポーネントArticleInputをつくります。
以下のコマンドを実行してください。

php artisan make:livewire ArticleInput

中身はそれぞれ以下のようになります。

app/Http/Livewire/ArticleInput.php

<?php

namespace App\Http\Livewire;

use App\Models\Article;
use Livewire\Component;

class ArticleInput extends Component
{
    protected $listeners = [
        'create' => 'create',
        'edit' => 'edit'
    ];

    protected $rules = [ // ここがないと wire:model に反映されない
        'article.title' => ['required'],
        'article.content' => ['required'],
    ];

    public $article;

    public function render()
    {
        return view('livewire.article-input');
    }

    public function mount()
    {
        $this->create();
    }

    public function create()
    {
        $this->article = new Article();
    }

    public function edit(Article $article)
    {
        $this->article = $article;
    }

    public function save()
    {
        $this->validate();
        $this->article->save();

        session()->flash('status', '保存が完了しました。');
        $this->emitTo('article-list', 'refresh');
        $this->create();
    }
}

ここで重要なのが、$rulesの部分です。

(実は、なかなかハマってしまったのですが)ここがないとビュー内でwire:modelとして使うことができません。

ということで、そのビューも見てみましょう。

<form wire:submit.prevent="save">
    <div>
        @if(session('status'))
            <div class="text-green-700 p-3 bg-green-300 rounded mb-3">
                {{ session('status') }}
            </div>
        @endif
    </div>
    <div class="mb-3">
        <label>タイトル</label>
        <br>
        <input type="text" class="border w-full p-1" wire:model="article.title">
        @error('article.title')
            <span class="text-red-500">{{ $message }}</span>
        @enderror
    </div>
    <div class="mb-4">
        <label>本文</label>
        <br>
        <textarea rows="7" class="border w-full p-1" wire:model="article.content"></textarea>
        @error('article.content')
        <span class="text-red-500">{{ $message }}</span>
        @enderror
    </div>
    @if($article->exists)
        <button type="submit" class="bg-blue-700 text-blue-50 p-2 rounded">変更する</button>
    @else
        <button type="submit" class="bg-purple-700 text-purple-50 p-2 rounded">登録する</button>
    @endif
</form>

これでArticleInputの中で$articleが変化したら自動的にビューの方も書き換わる、というわけです。便利ですね👍

コンポーネントを呼び出すテンプレートをつくる

では、先ほどつくった2つのコンポーネントを使う部分をご紹介します。

resources/views/article/index.blade.php

<html>
<head>
    <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
    @livewireStyles
</head>
<body>

    <div class="p-5">
        <h1 class="text-3xl mb-4">
            livewire のCRUDサンプル
        </h1>
        <div class="grid grid-cols-2 gap-7">
            <div>
                <livewire:article-list />
            </div>
            <div>
                <button
                    type="button"
                    class="bg-gray-300 text-gray-700 rounded p-2 mb-5"
                    onClick="Livewire.emitTo('article-input', 'create')">+ 追加する</button>
                <livewire:article-input />
            </div>
        </div>
    </div>

@livewireScripts
</body>
</html>

まず、この中にある@livewireStyles@livewireScriptsですが、これはlivewireを使うために必要なCSSJavaScriptを書き出す部分になります。

そのため、livewireを使うにはこの「おまじない」が必要ということになります。

また、実際にコンポーネントを使っている部分は<livewire:***** />の部分です。

ルートをつくる

では、最後にルートをつくります。

routes/web.php

Route::get('/article', fn() => view('article.index'));

作業は以上です。
お疲れ様でした😄✨

livewireを使う上で気をつけたいこと

これまでのLaravel開発にはなかった部分として以下2点を気をつけるとスムーズに使えると思います。

  • $listerners の登録: これがないとメソッドを外から実行できません
  • $rules の登録: これがないと wire:model が使えません

テストしてみる

では、実際にテストしてみましょう❗

まずはブラウザで「https://******/article」にアクセスしてみます。

すると、以下のように表示されました。(画面が横長なので2つにカットしています)

ではまず、以下のように入力して新規登録してみましょう。

登録ボタンを押すと・・・・・・

はい❗
完了メッセージが表示されました。

では、ちゃんと登録されているかページャーを移動して確認してみましょう。

うまく登録されています。

では、次に「変更」ボタンをクリックしてデータ変更できるかチェックしてみます。

クリックすると、フォームにデータが自動的に入力されるので、それぞれ以下のように変更します。

変更する」ボタンをクリックすると・・・・・・??

はい❗
データを変更することができました。

では、続いて「削除」ボタンをクリックしてみます。

うまく削除することができました。

では、最後にバリデーションがうまくいっているかもチェックしてみましょう。

フォームを空にして送信してみると・・・・・・??

はい❗
日本語化していませんが、きちんとエラーが表示されました。

成功です✨😄👍

企業様へのご提案

livewireは冒頭でもご紹介したとおり、「PHPをメインとした開発」をすることができます。

つまり、ある程度熟練したPHP開発者が1人いればフロントエンドはそれほど知識がなくても十分開発を進めることができると言っていいと考えています。

現在、ウェブ開発はあまりにも「すその」が広がりすぎて開発に多くのテクノロジーが必要になってしまいましたが、これは求人する側としてはなかなか由々しき問題ではないでしょうか。

そのため、もし「人員を減らした開発」や「学習コストが低いテクノロジー」をご希望でしたらlivewireでの開発もご検討になってみてはいかがでしょうか。そして、その際はぜひお問い合わせからご相談ください。

どうぞよろしくお願いいたします。m(_ _)m

おわりに

ということで、今回はlivewireを使ってCRUDを実装してみました。

前回はlivewireがリリースされたばかりでまだまだ便利な機能がそろってないような印象でしたが、現在はver 2になり、成熟してきた印象があります。

なお、今回livewireを使ってみて「これはいい!」と感じたところを2点まとめてみました。

  • ブラウザ側のデータをPHPで(擬似的に)リアルタイム保持してくれているので、保存が簡単: 通常だとデータを呼び出してから保存しないといけないのでとても楽です
  • リロードしなくても変更を反映してくれる: これまではコードを変更したら一度リロードしてから確認してましたが、コンポーネント部分はこれが不要になります

なので、個人的にはlivewireはもっと人気になってもいいのかな(2021.11.25現在でGitHubのスターは13.7kでした)と感じていますが、みなさんのお気持ちはどうでしょうか。

ただ、未だに一太郎が良かったって言う人もいますけど、ほぼMS Wordに置き換わってしまいましたし、電気自動車も日本は「うーん、、」ですが世界の流れに合わせざるを得ないのを見ると、好むと好まざるに限らずReactがその他を一掃しそうな気もします。技術選定は難しいところですね…😅(Reactはせめて開発環境だけでもビルドせず実行できるようになればなって感じです)

今後も個人的に模索してみます。

ではでは〜❗

「マンホールの写真あつめ、
増えてきたぞ😁」

開発のご依頼お待ちしております 😊✨ どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly