Laravelでユーザー管理機能をつくる

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、過去にLaravelでいろいろなシステムを開発させていただきましたが、中でもログインが必要なサイトでよくご依頼をいただく機能があります。

それは・・・・・

ユーザー管理機能

です。

ユーザー管理機能」とはその名のとおり、ユーザーを(追加・変更・削除)する機能のことで、管理者さんのために開発することになります。

もちろんサイトによっては、以下のようなプラスアルファが必要になる場合もありますが最低でも上の3つの機能があればユーザー管理は可能です。

プラスアルファの例:

  • アイコン画像の追加・変更・削除
  • CSVでのエクスポート/インポート機能
  • 特定のユーザーとして自動ログイン

そこで❗

今回はLaravelでユーザー管理機能をつくる方法をご紹介したいと思います。

なお、ここのところ少し複雑な記事が多くなってしまったので、今回は以下のような「超ミニマル」な構成でお届けしたいと思います。

  • できるだけ少ないファイル
  • できるだけ少ないメソッド
  • できるだけ少ないコード(でも見やすさ優先❗)

ぜひ皆さんのお役に立てると嬉しいです😊✨
(記事の最後でソースコード一式をダウンロードできますよ👍)

「なぜかコンタクトが右だけ多く残るんですが、
不思議でしょうがないです😫」

開発環境: Laravel 7.x

前提として

今回もLaravelにログイン機能をインストールし、テストユーザーを追加してから試してください。もしまだの方は以下のページを参考にしてください。

Laravel6.x以降でログイン機能をインストールする方法

なお、今回の記事ではユーザーの(追加・変更・削除)だけを取り扱いますが、管理者だけこの機能を有効にする場合は以下の記事で「role(役割≒権限)」も用意しておいてください。

シンプル!Laravel5.6で「権限つき」ログインさせる方法

ではやっていきましょう❗

コントローラーをつくる

まずはコントローラーをつくりますが、今回は「ミニマル」な仕様なのでいつもとは違ったコマンドを実行します。

php artisan make:controller UserController --resource --model=User

オプションの意味については次のとおりです。

  • –resource: (追加・変更・削除・表示)に対応したメソッドを追加してくれます。例:index(), create(), update()など
  • –model: 上で追加したメソッドにさらにモデルをバインディングするコードも追加してくれます。例:function show(User $user) { ... }

つまり、どうせ書くことになるコードを省略できるコマンドだと考えてください。

では、作成したコントローラーの中身を次のように変更してください。

/app/Http/Controllers/UserController.php

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function index()
    {
        $per_page = 3; // 1ページごとの表示件数
        $users = \App\User::paginate($per_page);
        return view('user.index')->with('users', $users);
    }

    public function store(Request $request)
    {
        // [ご注意]:バリデーションは省略してます

        $user = new \App\User();
        $user->name = $request->name;
        $user->email = $request->email;
        $user->password = bcrypt($request->password);
        $result = $user->save();
        return ['result' => $result];
    }

    public function update(Request $request, User $user)
    {
        // [ご注意]:バリデーションは省略してます

        $user->name = $request->name;
        $user->email = $request->email;

        if($request->filled('password')) { // パスワード入力があるときだけ変更

            $user->password = bcrypt($request->password);

        }

        $result = $user->save();
        return ['result' => $result];
    }

    public function destroy(User $user)
    {
        $result = $user->delete();
        return ['result' => $result];
    }
}

では、コードが少し長いのでメソッドごとに紹介します。

index()

今回はindex()だけが直接ブラウザでアクセスするメソッドで、他は全てAjax送信のためにあります。

この中では、1ページごとに表示するユーザー・データをpaginate()で取得し、ビューに送っています。

store()

新規ユーザー登録するメソッドです。
Ajaxの「POST」で送信されることを想定しています。

なお、bcrypt()はパスワードを暗号化するヘルパー関数です。
これを忘れるとどんなに頑張ってもログインできなくなってしまいますので忘れずつけておいてください。

update()

すでに存在するユーザーのデータを変更するメソッドで、「PUT」で送信されることを想定しています。

途中if文をつくっているのは、パスワードは入力があるときだけ保存するようにしたいからです。

なお、今回はテストですのでstore()update()にはバリデーションはつけていませんのでお気をつけください。m_ _m

destroy()

データを削除するメソッドです。
こちらは「DELETE」メソッドを想定しています。

ルートをつくる

ルートもミニマル仕様で書いてみましょう。

/routes/web.php

Route::resource('user', 'UserController')->only(['index', 'store', 'update', 'destroy']);

「おや、さっきメソッドは4つあったけど❓❓」と思った方もいらっしゃるかもしれませんが、この1行だけでOKです。

なぜなら、Route::resource()は、【追加・変更・削除・表示】に必要な以下のメソッド全てを1行で書くことができる機能だからです。

  • index(): 一覧表示
  • create():追加(フォーム)
  • store(): 追加(送信先)
  • show(): 1件表示
  • edit(): 変更(フォーム)
  • update(): 変更(送信先)
  • destory(): 削除

※つまり、create()store()、そしてedit()update()はペアになります。

なお、Route::resource()only()で必要なメソッドだけを有効にできます。

ビューをつくる

繰り返しですが、ビューも「ミニマル」でいきます❗(1ファイルだけで実装します)

以下のようなファイルを作成してください。

/resources/views/user/index.blade.php

<html>
<head>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div id="app" class="p-5">
        <!-- 一覧表示するブロック ① -->
        <div v-if="state=='index'">
            <div class="mb-3">
                <button type="button" class="btn btn-success" @click="changeState('create')">追加</button>
            </div>
            <table class="table table-striped">
                <thead>
                    <tr>
                        <th>名前</th>
                        <th>E-Mail</th>
                        <th></th>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="user in users">
                        <td v-text="user.name"></td>
                        <td v-text="user.email"></td>
                        <td class="text-right">
                            <button class="btn btn-warning" type="button" @click="changeState('edit', user)">変更</button>
                            <button class="btn btn-danger" type="button" @click="onDelete(user)">削除</button>
                        </td>
                    </tr>
                </tbody>
            </table>
            <!-- ページ移動のリンク ③ -->
            {{ $users->links() }}
        </div>
        <!-- 追加&変更するブロック ② -->
        <div v-if="state=='create' || state == 'edit'">
            <div class="form-group">
                <label>名前</label>
                <input type="text" class="form-control" v-model="params.name">
            </div>
            <div class="form-group">
                <label>メールアドレス</label>
                <input type="text" class="form-control" v-model="params.email">
            </div>
            <div class="bg-light px-3 py-2 mb-3" v-if="state == 'edit'">以下は省略可</div>
            <div class="form-group">
                <label>パスワード</label>
                <input type="password" class="form-control" v-model="params.password">
            </div>
            <div class="form-group">
                <label>パスワード(確認)</label>
                <input type="password" class="form-control" v-model="params.passwordConfirmation">
            </div>
            <button type="button" class="btn btn-link" @click="changeState('index')">戻る</button>
            <button type="button" class="btn btn-primary" @click="onSave">保存する</button>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
    <script>

        new Vue({
            el: '#app',
            data: {
                state: 'index',
                params: {
                    id: -1,
                    name: '',
                    email: '',
                    password: '',
                    passwordConfirmation: ''
                },
                users: [
                    // ユーザーデータをJSON化 ④
                    @foreach($users as $user)
                    {!! $user !!},
                    @endforeach
                ]
            },
            methods: {
                changeState(state, value) { // 状態を変化させて表示を切り替え ⑤

                    if(state === 'create') {

                        this.params = {
                            id: -1,
                            name: '',
                            email: '',
                            password: '',
                            passwordConfirmation: ''
                        };

                    } else if(state === 'edit') {

                        this.params = value;

                    }

                    this.state = state;

                },
                onSave() { // データ保存(追加&変更) ⑥

                    const params = this.params;
                    let url = '/user';
                    let method = 'POST';

                    if(this.state === 'edit') { // 変更の場合

                        url += '/'+ this.params.id;
                        method = 'PUT';

                    }

                    axios({ url, method, params })
                        .then(response => {

                            if(response.data.result === true) {

                                location.reload(); // 再読み込み

                            }

                        });

                },
                onDelete(user) { // データ削除 ⑦

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

                        const url = '/user/'+ user.id;
                        axios.delete(url)
                            .then(response => {

                                if(response.data.result === true) {

                                    location.reload(); // 再読み込み

                                }

                            });

                    }

                }
            }
        });

    </script>
</body>
</html>

ここが今回一番複雑な部分になりますので、部分ごとに紹介していきます。

一覧表示するブロック ・・・  ①

まずはじめに、今回はVueを使って以下3つの状態によって表示が切り替わるようにします。(この状態を保持する変数がstateです)

  • index: 一覧表示
  • create: 追加(登録フォーム)
  • edit: 変更(変更フォーム)

そして、「一覧表示するブロック」はstateindexのとき表示することになり、<table> ... </table>タグではユーザーデータの一覧を表示することになります。

追加&変更するブロック ・・・ ②

ここは入力フォームがあってstatecreateeditのときに表示されます。

つまり、データの登録と変更は、全く同じフォームを使ってが実行されることに注目してください。そして、v-modelを使ってフォームの内容を変化させます。

ページ移動のリンク ・・・ ③

ページ移動リンクは、Laravelが提供するものを使います。

なお、今回は「ミニマル仕様」ということでページ移動リンクはVueで表示するのはやめにしました。(ちょっと複雑になり、ミニマルではなくなるので・・・😂)

ユーザーデータをJSON化 ・・・ ④

ここがLaravel(PHP)Vue(JavaScript)が連動している部分です。

まず$usersはコントローラーから送られたDBのユーザーデータで、これをループすることで動的に JavaScript のコードを書き出しています。

例えば、以下のようなコードが作成され、オブジェクトの配列(コレクション)を作成することができます。

// 👇 PHPでJavaScriptを書き出す❗
[
    {"id":1,"name":"\u592a\u90ce","email":"taro@example.com", (...省略) },
    {"id":1,"name":"\u6b21\u90ce","email":"jiro@example.com", (...省略) },
    {"id":1,"name":"\u4e09\u90ce","email":"saburo@example.com", (...省略) },
]

状態を変化させて表示を切り替え ・・・ ⑤

changeState()は、stateの中身を変更する(つまり、Vueが自動で表示切り替えする)という役割だけでなく、stateの中身によってはparamsの中身も変更することになります。

paramsは、Ajax送信される(ユーザーの)データですので、ここを変更することで<input>の中身もVueが自動で変更してくれることになります。

便利ですね😊👍

データ保存(追加&変更) ・・・ ⑥

onSave()は、入力フォームの「保存する」ボタンがクリックされたときに実行されることになりますが、注目してほしいのは、「state」が「create」のときと「edit」のときで、URLと送信メソッドが違ってくるという点です。

つまり、今回でいうと以下のようになります。

create

  • URL: /user
  • 送信メソッド: POST
  • 実行されるメソッド: store()

edit

  • URL: /user/(ユーザーID)
  • 送信メソッド: PUT
  • 実行されるメソッド: update()

そして、保存が完了するとページを強制的に再読み込みします。

データ削除 ・・・ ⑦

onDeleteは、個別ユーザーデータを削除するメソッドで「削除」ボタンがクリックされたときに実行されます。

こちらでも削除が完了したらページを強制的に再読み込みしています。

テストしてみる

では、実際にうまく動くかチェックしてみましょう❗

まずはhttp://***/userにアクセスしてみます。

ユーザーデータとページリンクが表示されました😊👍
では、この状態で2ページ目を表示してみましょう。

はい❗
ページリンクもですが、ユーザーデータも内容が変更になっています。

では、次にページ上部にある「追加」ボタンをクリックして新しいユーザーを追加してみましょう。

保存する」をクリックします。すると・・・・・

先ほどのページリンクは3まででしたが4ページ目が表示されました。

ということで、急いで4ページ目を見てみましょう❗

はい❗
先ほど登録した「テストユーザー」さんが登録されています。

では、続いて「変更ボタン」をクリックして、この「テストユーザー」さんのデータを変更してみましょう。

保存する」ボタンをクリックすると・・・・・

はい❗

テストユーザー」さんが「テストユーザー2」さんに変更になり、メールアドレスも変更になっています。

成功です😊✨

ちなみに①:axiosの送信方法について

これまでLaravelに「PUT」や「DELETE」メソッドで送信する場合、「_method」というパラメータに送信メソッド名を入れて送信していましたが、今回の環境で試してみたところ、通常のPUTDELETEでも問題なく動きましたので途中で変更しました。

つまり、以下のようなコードでも問題なく動くと思います。

axios.put(url, params)
    .then(response => {

        // 変更が成功した時

    });

ちなみに②:削除したときにエラーが出たら

もしデータ削除したときに以下のようなエラーが出た場合は、DB内の外部キーが原因です。

Cannot delete or update a parent row: a foreign key constraint fails

そのため、Laravelのマイグレーションで言うと以下のような部分を見直すか、

$table->foreign('user_id')->references('id')->on('users');

もしくはonDelete('cascade')をつけてusersのデータが削除されたら連動して関連データが削除されるようにしてみてください。

$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');

私はこれでちょっとハマりました・・・😂

ダウンロードする

今回実際に開発したソースコード一式を以下からダウンロードすることができます。

※ただし、バリデーションやミドルウェアなどは一切追加していませんのでご自身で用意してください。

Laravelでユーザー管理機能をつくる
開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで、今回はLaravelでユーザー管理ページを「ミニマル」仕様で作ってみました。

通常の開発をする場合は、しっかりAjax用にコントローラーやメソッドを用意するようにしているので、私としても初の試みでしたが必要最低限でいいなら、これもOKかもしれません。

ただ、冒頭でも書きましたインポートなどの付加機能が必要な場合はそれなりに複雑になってくることが予想されますので、コントローラーもしくはメソッドはきっちり分ける方がいい場合もあると思います。

こんなカンジで、ぜひ皆さんもやってみてくださいね。

ではでは〜❗

「やっぱりコーヒーは、ゴールドブレンドの
苦いバージョンが好きです👍」

このエントリーをはてなブックマークに追加       follow us in feedly