"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

はじめてLaravel6.0:画面作成の基本とDBマイグレーション/入力画面(1)

Laravel6.0で入力画面を作成する基本中の基本として、テーブルと1:1の入力画面を作成する手順を整理します。

DBテーブル構築は「マイグレーション」、DB操作は「Eloquent」モデルを用いて実装します。

f:id:arakan_no_boku:20191003220222p:plain

 

 はじめに

 

今回から入力画面を作りながら、基本的なところを整理していきます。

最初から難しいことやると混乱するので、まずは最低限から。

今回やるのは。

データベーステーブルと1:1の、最もシンプルな入力画面です。

それでも、Laravel6.0でお作法通りにつくるには、以下の手順を踏む必要があります。

  1. DBテーブルのマイグレーション
  2. Eloquentモデルの作成
  3. コントローラクラス作成
  4. ルート設定
  5. 入力画面作成
  6. 動かして入力データをテーブルへ更新する

 

今回作成するソースの名前と簡単な仕様

 

とりあえず。

今回作成する入力画面の仕様っぽいことだけ決めときます。

入力項目は以下の3つだけにします。

  • コード:半角英数
  • 氏名:漢字
  • カナ氏名:全角カナ

手順を確認するのが目的なので、バリデーションは行いません。

画面に登録ボタンを置いて、押したらDBに更新する。

ただ、それだけやります。

作成するViewとコントローラの名称は以下にします。

  • dummy.blade.php
  • DummyController.php

アクセスするURLは以下にします。

とりあえず、ログインなしにダイレクトに表示するようにします。 

作成するテーブル名は以下にします。

  • dummy_items

注意が必要な点として、DBアクセスには、Eloquentモデルを使う場合は、名前の制約に注意する必要があります。

上記のテーブル名も、モデルの制約にしたがってます。

詳しくは後述します。

さて・・と。

 

DBテーブルのマイグレーション

 

まず、入力項目に対応したテーブルを生成します。 

Laravelでテーブルを生成するお作法にそって、マイグレーションします。

readouble.com

手順としては。

  1. php artisan make:migrationコマンドで、マイグレーション用ソース生成
  2. ソースのup()とdown()メソッドに必要な項目定義などを追記する。
  3. php artisan migrateコマンドでデータベースに反映する

です。

 

マイグレーション用ソース生成

 

まず、マイグレーション用ソース生成です。

名前は作成するテーブル名に「create」をつける必要があります。

php artisan make:migration create_dummy_items

 生成に成功すると、以下のテーブルにソースコードが生成されま

database\migrations

生成されたファイル名には、テーブル名に日付_時刻_が付いてます。

例えば、「2019_10_25_234455_create_dummy_items.php」みたいな感じです。

このファイル名は変更しません。

変更しても良いですが、面倒なだけで、メリットは何もありませんから。

生成されたソースはこんな感じです。

<?php

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

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

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

up()とdown()という2つのメソッドがあります。

up()は新しいテーブル、カラム、インデックスをデータベースに追加する時に実行するメソッドです。

up()には

$table->bigIncrements('id');
$table->timestamps();

がデフォルトでセットされてます。

ここに、新たに項目の定義を追加していけばよいというわけです。

down()はup()で行った操作を元に戻す時に実行するメソッドです。

こちらは自動生成された時にすでに「 Schema::dropIfExists('dummy_items');」が定義されていますので、そのままで大丈夫です。

readouble.com

 

ソースのup()とdown()メソッドに必要な項目定義などを追記する

 

up()にテーブル・カラムを追加していきます。

項目に対するカラム名は以下にします。

  • コード => c_code
  • 氏名 => c_name
  • カナ氏名 => c_kana_name

そうすると以下のようになります。

   public function up()
    {
        Schema::create('dummy_items', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->char('c_code', 10);
            $table->string('c_name');
            $table->string('c_kana_name');
            $table->timestamps();
        });
    }

見た通りではありますが、いくつかポイントがあります。

データ操作に「Eloquent ORM」を使うので、その「モデル規約」に従う必要があるからです。 

readouble.com

規約に従うポイントを整理していきます。

 

まず、テーブル名です。

dummy_items

 と複数形にしています。

これは「Eloquent ORM」がテーブルを探すデフォルトの動作が

他の名前を明示的に指定しない限り、クラス名を複数形の「スネークケース」にしたものが、テーブル名として使用されます。

だからです。

必然的に、対応するModelクラス名は「DummyItem」に決まります。

 

次が主キーの制約です。 

$table->bigIncrements('id');

 は自動インクリメントする主キーで、これは必須です。

かつ、カラム名は「id」固定です。 

一部抜粋すると。

Eloquentは更にテーブルの主キーがidというカラム名であると想定しています。

さらに、Eloquentは主キーを自動増分される整数値であるとも想定しています。

つまり、デフォルト状態で主キーは自動的にintへキャストされます。

ということだからです。

 

続けて。

タイムスタンプの制約です。

$table->timestamps();

名前指定がありませんが、これで「 created_at」と「updated_at」という2つのカラムを作ります。

これもLaravel標準の「Eloquent ORM」を使うには重要なポイントです。

デフォルトでEloquentはデータベース上に存在するcreated_at(作成時間)とupdated_at(更新時間)カラムを自動的に更新します。

 からです。

 

前にも書いたように、down()は今回、生成されたままで変更しません。

    public function down()
    {
        Schema::dropIfExists('dummy_items');
    }

こちらはシンプルにテーブルがあるかチェックして、あればドロップするだけです。 

これで一旦定義はOKとします。 

 

php artisan migrateコマンドでデータベースに反映する

 

さて、ソースに記述を追加できたら、以下のコマンドを実行します。

php artisan migrate

これにより、データベースにテーブルを作成します。

それだけなのですが。

ひとつ補足します。

既にマイグレーション済のソースが残っている場合です。

上記のコマンドでは特に対象のソースを指定しません。

なので、単純に実行したら、既にマイグレーション済のものも再実行されてテーブルが消えてしまうのではないか?・・と心配になります。

でも、心配ありません。

実は、一度実行したソース名がテーブルmigrationsに記録されています。

ここにある限り、再実行されません。

実際、MySQLMariaDB)に接続して、migrationsテーブルをSelectするとこんな感じになってます。

f:id:arakan_no_boku:20191015002026p:plain

マイグレーションのソースのファイル名を下手に変更したら、面倒なことになる理由がここにあります。

さて。

マイグレーションの結果生成されたテーブルはこうです。

f:id:arakan_no_boku:20191015235754p:plain

いちおう、想定通りにはできてます。

 

Eloquentモデルの作成 

 

先ほど生成したテーブルに対応する Eloquentモデルを作ります。

readouble.com

Eloquentモデルについての説明を引用すると。

それぞれのデータベーステーブルは関連する「モデル」と結びついています。

モデルによりテーブル中のデータをクエリできますし、さらに新しいレコードを追加することもできます。

 ということですが「ふーん」という感じです。

まあ。

やってみればわかるでしょう・ということですすめます。

まず、artisanコマンドで、スケルトンのソースを生成します。

対象テーブルが「dummy_items」なので、テーブル名制約に従えば、モデルクラスの名前は必然的に「DummyItem」に決まります。

php artisan make:model DummyItem

実行すると、「app」フォルダに「DummyItem.php」ができます。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class DummyItem extends Model
{
    //
}

とりあえず、今回やる程度のことなら、Modelに関してはこれで終わりです。

中身が空なので、本当に大丈夫か?って心配になりますが、テーブル名制約にそって正しい名前でモデルクラスができていれば、これだけで、テーブルへのアクセスができるということなのですね。

なので次にいきます。

 

コントローラクラス作成 

 

別に特殊なコントローラを作るわけではないので、ダイレクトに作ってもよいのですが、手順として、以下のようにコマンドで生成します。

php artisan make:controller DummyController

これで、「app\Http\Controllers」フォルダに、DummyController.phpができます。

生成されただけだと、こんな感じです。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class DummyController extends Controller
{
    //
}

ここに以下を追加していきます。

  •  モデルを利用するためのuse文
  • データベースを更新するstore()メソッド

修正後のソースはこちらです。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\DummyItem;

class DummyController extends Controller
{
   
    public function store(Request $request)
    {
        
        $code = $request->code;
        $name = $request->name;
        $kana = $request->kana;
        $dummyItem = new DummyItem;
        $dummyItem->c_code = $code;
        $dummyItem->c_name = $name;
        $dummyItem->c_kana_name = $kana;
        $dummyItem->save();
        $msg = "登録プログラムで。「" .$code . " " . $name . " " . $kana . "」を登録しました。";
        return view('dummy', compact(
            'msg'
        ));
        
    }
}

ポイントを補足します。

画面の入力値は、Requestオブジェクトから取得します。

つまり。

入力画面にあたる「dummy.blade.php」の<input>タグのnameは

  • code
  • name
  • kana

にしないといけない・・ということがここで決まります。

 

前に生成したDummyItemモデルをstore()内でnewしています。

このクラス自体はほぼ空ですが、名前付けルールに従って自動的に「dummy_items」テーブルを参照しにいきます。

なので。

DummyItemクラスのどこにも定義されていない、テーブルのカラム「c_code」「c_name」「c_kana_name」に対して代入もできてしまいます。

 

値をセットして「save()メソッド」を実行します

これで、キー項目「id」および「 created_at」と「updated_at」には自動的に値がセットされます。

 

確認画面をわざわざ作るのが面倒なので、最後は、入力画面にreturnしてます。

一応、処理したことがわかるように、メッセージだけセットしてます。

 

ルート設定

 

コントローラクラスのstoreメソッドと、初期入力画面にする「dummy.blade.php」に対してルートを設定します。

routes\web.phpに以下を追加します。

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

Route::post('/newdummy', 'DummyController@store')->name('newdummy');

上が初期表示。

下が、コントローラのstore()メソッドの呼び出しです。

formの中で指定しやすいように「->name('newdummy')」を使って、名前付きルートにしています。 

 

入力画面作成

 

最後に入力画面です。

resources/viewsに「dummy.blade.php」を作成します。

今回は、ポイントを明確にするため、コード・氏名・カナ名を入力し、登録ボタンを押すと、元の画面に戻り「登録しました」的メッセージを表示するだけの画面にします。

viewにおける確認ポイントは3つです。

  • Form利用時には「@csrf」ディレクティブが必須
  • Formの「action="{{ route('newdummy') }}"」
  • 確認画面と入力画面を併用する場合に必要な{{ $msg ?? '' }}の書き方

 

Form利用時には「@csrf」ディレクティブが必須

 

まず。

@csrfディレクティブです。

Formを使う場合、これは必須です。

定義していないと、フォームの送信時にLaravelのCSRF保護ミドルウェアに対象にしてもらえず、セキュリティ的な問題が残ります。

www.trendmicro.com

 

Formの「action="{{ route('newdummy') }}"」

 

「action="{{ route('newdummy') }}"」は、登録ボタンを押した時に実行されるものです。

route('newdummy')は、前のルート定義で、DummyControllerのstore()メソッドに紐づけられていますので、それが実行されるわけです。

 

確認画面と入力画面を併用する場合に必要な{{ $msg ?? '' }}の書き方

 

横着して入力画面と確認画面を併用しています。

つまり。

$msgという変数は、確認画面として使う時・・つまりコントローラクラスからReturnされた時・・しか定義されていません。

なので。

{{ $msg }} と書くと、「$msgは未定義です」とエラーになって、初期画面表示ができなくなります。

そんな時は{{ $msg ?? '' }}と書いておくと、$msgが未定義でもエラーになりません。

 

おまけ

 

inputタグのvalueのところでold()構文を使っています。

これはバリデーションエラーの時みたいに、リダイレクトで戻ってきた時に、セッションから入力値を復元して再表示してくれる便利な構文なのですが、今回は、バリデーションを一切していないので、本来なら不要です。

でも。

次回以降にどうせバリデーションいれるから・・ということで、書いてます。

 

viewのソースです

 

dummy.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">サンプル入力</div>
                <div class="card-body">
                    <p>{{ $msg ?? '' }}</p>
                    <form method="POST" action="{{ route('newdummy') }}">
                        @csrf
                        <div class="form-group row">
                            <label for="code" class="col-md-4 col-form-label text-md-right">コード</label>
                            <div class="col-md-6">
                                <input id="code" type="text" class="form-control" name="code" value="{{ old('code') }}" required autocomplete="code" autofocus>
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="name" class="col-md-4 col-form-label text-md-right">名前</label>
                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control" name="name" value="{{ old('name') }}" required autocomplete="name">
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">カナ名</label>
                            <div class="col-md-6">
                                <input id="kana" type="text" class="form-control" name="kana" value="{{ old('kana') }}" required autocomplete="kana">
                            </div>
                        </div>
                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    登録
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

レイアウトを整えるのに、BootstrapのCSS定義を使っています。

そのため<div>タグが増えて、若干長めのソースに見えています。

まあ・・しゃあないですね。 

 

さて実行してみます

 

XAMPPでMySQLを起動します。

f:id:arakan_no_boku:20191022162539p:plain

プロジェクトフォルダをカレントにして、ビルトインサーバーを動かします。

php artisan serve

で。

http://localhost:8000/dummy

にアクセスすると、入力画面が表示されます。

f:id:arakan_no_boku:20191022163407p:plain

適当に入力します。

f:id:arakan_no_boku:20191022163549p:plain

登録ボタンを押すと。

f:id:arakan_no_boku:20191022163633p:plain

こんな感じで、数件登録してみました。

DBの登録状況をみてみると・・

f:id:arakan_no_boku:20191022163924p:plain

ちゃんとinsertされてます。

今回はこんなところでOKじゃないですかね。

次回以降は、これをベースに少しずつ拡張して、機能を覚えていこうと思います。

ではでは。