[Laravel] MySQLと連動させてCRUDを作成する

アプリケーションを作る際に基本的な機能となるCRUDをLaravelとMySQLを使って作成してみます。CRUDはCreate, Read, Update, Deleteの略になっています。
データの一覧表示、新規データの作成、データの表示、データの更新、データの削除。アプリケーションには必須と言える機能です。現在はシングルページアプリケーション(SPA)が主流となりつつあります。LaravelとVue.jsなどを組み合わせてSPAでサイトを構築する場合も多いと思いますが、ここでは、SPAではなく、ページ遷移のあるベーシックなサイトを作ります。

LaravelとMySQLを連動させる方法は下記の記事を御覧ください。
[Laravel] インストールとMySQLの接続
[Laravel] MySQLのテーブル作成とテストデーター自動生成
[Laravel] MySQLのデータをLaravelで表示してみる

開発環境:Laravel5.x , MySQL 8.x , CentOS 7.x , Apache 2.4

設計

今回は自分の持ち物を管理するDBを作成してみます。パソコン、スマホ、バッグ、ノート、車などを登録するイメージです。
データベースのテーブル名はitems,Laravelのモデル名はItemとします。

マイグレーションでテーブルを作成

make:modelの実行

# php artisan make:model Item --migration
Model created successfully.
Created Migration: 2020_04_12_150100_create_items_table

これによりプロジェクトルートのappディレクトリの中に「Item.php」というモデルファイルが生成されます。
また、databaseのmigrationsディレクトリの中に「2020_04_12_150100_create_items_table」というマイグレーションファイルが生成されます。
これらを編集していきます。

app/Item.phpの設定

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Item extends Model
{
    protected $fillable = [
        'name',
        'memo',
    ];
}

Modelには protected $fillableを設定します。
$fillableはLaravel独自の機能となります。
Laravelはデーターベースとやり取りする仕組み「Eloquent」によってDB操作が簡単にできるようになっています。基本的なDB操作は自分でSQL文を書かなくても、Eloquentのメソッドで実行することができます。

勝手にテーブルにアクセスできるとトラブルの原因になったりするので、デフォルトではアクセスに制限がかけられています。どのデータにアクセスしたらよいかを設定するのが $fillableです。

protected $fillable = ['name','memo',];

$fillableの後の括弧の中はフィールド名をシングルクォーテーションで囲み、カンマで区切って記載します。
$fillableは指定したものを利用可能とするのでホワイトリスト形式になります。

これとは逆にブラックリスト形式での登録も用意されています。アクセスOKのカラムが少ない場合はホワイトリスト形式、アクセスOKのカラムが多い(アクセス禁止が少ない)場合はブラックリスト形式のほうが使いやすいです。
ブラックリストの場合は下記のように記載します。

protected $guarded = ['name'];

Laravelでは $fillableと$guardedのどちらかが設定されていればOKです。

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

「2020_04_12_150100_create_items_table.php」を開いて編集します。

<?php

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

class CreateItemsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('items', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('memo');
            $table->timestamps();
        });
    }

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

カラムを2つ追加したいので、
$table->string(‘name’);
$table->string(‘memo’);
を記載します。

マイグレーションの実行

# php artisan migrate
Migrating: 2020_04_12_150100_create_items_table
Migrated:  2020_04_12_150100_create_items_table (0.01 seconds)

MySQLでテーブルが作られたか確認してみます。

mysql>  SHOW COLUMNS FROM items;
+------------+-----------------+------+-----+---------+----------------+
| Field      | Type            | Null | Key | Default | Extra          |
+------------+-----------------+------+-----+---------+----------------+
| id         | bigint unsigned | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255)    | NO   |     | NULL    |                |
| memo       | varchar(255)    | NO   |     | NULL    |                |
| created_at | timestamp       | YES  |     | NULL    |                |
| updated_at | timestamp       | YES  |     | NULL    |                |
+------------+-----------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

マイグレーションファイルで $table->timestamps();という記載がありますが、これによって created_at , updated_at が自動に作られます。「Eloquent」でデータを追加、更新するとこの値(タイムスタンプ)が自動に記録される仕組みになっています。

コントローラー

make:controllerの後ろに–resourceをつけると CRUD対応になります。

# php artisan make:controller ItemController --resource

ルーティング

routesディレクトリのweb.phpにルーティングを記載します。

Route::resource('/item', 'ItemController');

この一行をweb.phpに記載するだけで、下記のようなルーティングが行われるようになります。

「ルーティングの確認」

# php artisan route:list

2020-04-13_00h48_49

コントローラーの編集

コントローラー内には下記のアクションが作られています。
index()
create()
store()
show()
edit()
update()
destroy()

下記のようにアクションの中身を記載していきます。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ItemController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $items = Item::all();
        return view('item.index' , compact('items'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('item.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        Item::create($request->all());
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        $item = Item::find($id);
        return view('item.show',compact('item'));
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        $item = Item::find($id);
        return view('item.edit',compact('item'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        $update = [
            'name' => $request -> name,
            'memo' =>$request -> memo
        ];
        Item::where('id', $id) ->update($update);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        Item::where('id', $id)->delete();
    }
}

Viewファイルの作成

コントローラーを作成したら、今度は表示部分のViewを作っていきます。
index , create , show , edit を作成します。

resources/views/index.blade.php

<h1> Item List</h1>
<p><a href="{{ route('item.create') }}">新規追加</a></p>
 
<table>
    <tr>
        <th>title</th>
        <th>詳細</th>
        <th>編集</th>
        <th>削除</th>
    </tr>
    @foreach ($items as $item)
    <tr>
        <td>{{ $item->name }}</td>
        <th><a href="{{ route('item.show' , $item->id)}}">詳細</a></th>
        <th><a href="{{ route('item.edit', $item->id)}}">編集</a></th>
        <th>
            <form action="{{ route('item.destroy', $item->id)}}" method="POST">
                @csrf
                @method('DELETE')
                <input type="submit" name="" value="削除">
            </form>
        </th>
    </tr>
    @endforeach
</table>

ページの上部に「新規追加」ページのリンクを作成。
リンク先は{{route(”}}の形式で設定しています。これで「ドメイン名/item/create」のリンクが生成されます。

@foreach ($items as $item) ~ @endforeachで一覧を表示しています。
コントローラーから $itemsの配列データがViewに送られてきています。
foreachの中では{{ $item->name }}という形でデータを取り出すことができます。

削除ボタンのところはLaravelの疑似フォームメソッドを使っています。
<form action=”{{ route(‘item.destroy’, $item->id)}}” method=”POST”>の部分は「ドメイン名/item/destroy」にリンクされ、itemのIDが送られます。(IDが送られるので、該当するIDのデータが削除される仕組みです)

@method(‘DELETE’)もLaravel独自のメソッドです。
LaravelのHTTPメソッドはGETとPOSTのみの対応になっています。PUTとDELETEはデフォルトでは使用することができません。フォムの中にメソッド名を指定して、擬似的にPUTやDELETEを実行できるようにする仕組みです。今回はDELETEを使います。
※PUTの場合は@method(‘PUT’)を使います。

フォームタグのすぐ下にある「@csrf」はセキュリティ対策のおまじないです。
CSRFはクロス・サイト・リクエスト・フォージェリの略です。信頼できるユーザーになりすまして、悪意のある人が許可されていないコマンド等を実行してしまうことです。これを防ぐために@csrfを記載します。

これでリクエストに含まれる「_token」とセッションに含まれる「token」が同等かを確認するようになります。表から届くデータと、裏側から届くデータを確認しているようなイメージです。

resources/views/create.blade.php
新規にデータを登録する画面です。

<h1>新規作成</h1>
<p><a href="{{ route('item.index')}}"> Item List</a></p>
 
<form action="{{ route('item.store')}}" method="POST">
    @csrf
    <p>Item名:<input type="text" name="name" value="{{old('name')}}"></p>
    <p>メモ:<input type="text" name="memo" value="{{old('memo')}}"></p>
    <input type="submit" value="登録">
</form>

こちらはシンプルなフォームになります。フォームは前項で説明したものと同じ使い方になります。

value=”{{old(‘name’)}}” はバリデーションで弾かれたとき、直前に入力していたデータが表示されるようになります。バリデーションで弾かれた時にデータが消えてしまうのはユーザビリティ上良くないのでこのような対処をしてユーザビリティを良くすることができます。

resources/views/show.blade.php
こちらは個々のデータの確認画面です。
シンプルにデータを表示しているだけですので、今までの内容で理解できますね。

<h1>詳細</h1>
<p><a href="{{ route('item.index')}}"> Item List</a> </p>
 
<table>
    <tr>
        <th>id</th>
        <th>name</th>
        <th>memo</th>
        <th>created_at</th>
        <th>updated_at</th>
    </tr>
    <tr>
        <td>{{ $item->id }}</td>
        <td>{{ $item->name }}</td>
        <td>{{ $item->memo }}</td>
        <td>{{ $item->created_at }}</td>
        <td>{{ $item->updated_at }}</td>
    </tr>
</table>

resources/view/edit.blade.php
編集画面です。

<h1>編集</h1>
<p><a href="{{ route('item.index')}}"> Item List </a></p>

<form action="{{ route('item.update',$item->id)}}" method="POST">
    @csrf
    @method('PUT')
    <p>Item名:<input type="text" name="name" value="{{ $item->title }}"></p>
    <p>メモ:<input type="text" name="memo" value="{{ $item->author }}"></p>
    <input type="submit" value="編集">
</form>

ここでは@method(‘PUT’)を使っています。データの更新のときは@method(‘PUT’)を利用すると覚えておきましょう。

画面の確認

Item List(一覧) ドメイン名/item
2020-04-13_11h32_04

新規作成 ドメイン名/item/create
2020-04-13_11h34_08

詳細 ドメイン名/item/{id}
2020-04-13_11h34_49

編集 ドメイン名/item/{id}/edit
2020-04-13_11h35_17

リダイレクト

これで基本的なページが出来上がり、動作もうまくいきました。
ただ、新規登録、更新などをした後に真っ白いページになってしまいます。それらを回避するためにリダイレクトを設定します。

新しいデータを登録(create)し、登録するとstoreの処理になります。このアクションの部分にリダイレクトを設定します。リダイレクト先は一覧(index)にします。

アクションに処理を設定したら、Viewにも表示の処理を記載します。

コントローラーの編集

ItemController/store

 return redirect()->route('item.index')->with('success', 'データが登録されました');

処理がうまくいったことをユーザーに知られるためにwithを使いコメントを表示させています。

ItemController/update

return back()->with('success', '編集完了しました');

直前のページにリダイレクトしたい場合はback()が便利です。back()を設定するだけで自動的に元のページに戻ります。

[参考]
return back()->withInput();
にすると送信されたデータが自動的にセッションに格納され、前のページでoldを設定しておくとそこに表示されるようになります。

ItemController/destroy

 return redirect()->route('item.index')->with('success', '削除しました');

ビューの編集

index.blade.php , edit.blade.php に以下を記載します。

@if ($message = Session::get('success'))
<p>{{ $message }}</p>
@endif


Author: webmaster