|||||||||||||||||||||

なんぶ電子

- 更新: 

Laravelでデータベース連携

Laravel DB App

前回、LaravelでPHPの開発環境を設定しHello Worldまでコーディングをしました。今回はデータベースと連携をしたいと思います。

サンプルとして紹介する環境は、Windwos10(64bit)と、MariaDb10.6.4です。SQLiteやpostgreSQL、SQL Serverの接続方法に関しては紹介していますが、マイグレーションのメソッドとカラムの型の結びつきにおいては、紹介しているものとは異なると思います。

MariaDBのインストール

まずデータベースをインストール・設定します。

MariaDBのページよりダウンロードします。ダウンロードページ掲載されている一覧からstable表記のある一番新しいものを選択してください。すでにMariaDBやMySQLがインストールされている人は、おそらくバージョン間の差はそれほどないと思いますのでインストールしたものとして読み進めてください。

PCの環境に合わせてファイルをダウンロードしてください。編集時点で10.9のRC(リリース候補)があり、10.8.3という安定板もありましたが、LTS(Long Term Support)の最新版であるmariadb-10.6.8-winx64.msiをダウンロードしました。

インストールの途中でMariaDBにおけるroot(管理者)のパスワードを求められますので、設定して覚えておいてください。他は初期値のままでインストールを進められると思います。

インストールが終わったら今度はデータベースにLaravel用の設定をします。

インストールで同梱されていた「MySQL Client」アプリを起動するか、コマンドプロンプト等のターミナルからMariaDBのバイナリフォルダへ移動して、「mysql -u root -p」とコマンドを入力します。

PS>cd "c:¥Program Files¥MariaDB 10.6¥bin"
PS >.¥mysql -u root -p
Enter password: **********
(インストール時に設定したパスワードを入力してください)

MariaDb[(none)]> CREATE DATABASE laravel_project;
MariaDb[(none)]> CREATE USER laraveluser;
MariaDb[(none)]> GRANT ALL PRIVILEGES ON laravel_project.* TO 'laraveluser'@'127.0.0.1' IDENTIFIED BY 'laravelpass';
MariaDb[(none)]> exit

コマンドは上から順に次のようになっています。

  1. データベースの作成

    Laravel用のデータはここで設定したデータベースに保存されます。他でデータベースを利用している際はこのデータベース名で保存域を切り分けます。

    Laravelで複数のプロジェクトを作成する場合は、プロジェクト毎にデータベースを変えた方が管理がしやすいと思います。Laravelのデフォルトの挙動ではデータベース名はプロジェクト名と同じになっていました。

    筆者の経験上データベース名は小文字で定義しておくのがおすすめです。また、ここでは設定しませんがテーブル名では、Debian等大文字小文字の区別のあるOSで動くMariaDBはそれを区別するので、意識しておくとトラブルが少ないと思います。

  2. ユーザーの作成

    LaravelのPHPからデータベースへアクセスする際のユーザーを作成します。

  3. 権限の設定

    作成したユーザーにLaravel用のデータベースの全権限を与えています。ただしローカルアクセス元がローカルホスト(127.0.0.1)の時しか接続できません。IDENTIFIED BYのキーワードとともにユーザーのパスワードも設定します。

通常、データベースにデータを保存するにはテーブルを作成する必要もあるのですが、Laravelでテーブル管理の機能が提供されているのでそちらを利用します。

もし一連の作業をGUIツールで操作したいなら、デフォルトで同梱されているHeidiSQLからも同様の操作が可能です。

Laravelとの連携(.envファイル)

ここからはLaravelでデータベースを設定します。

Laravelのプロジェクト作成時に、そのルートフォルダに.envファイルが生成されていると思います。

その中にデータベース連携の設定のひな形が既に存在するのでそれを修正します。

.env

...
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_project
DB_USERNAME=laraveluser
DB_PASSWORD=laravelpass
...

DB_CONNECTIONの値はMariaDBでもMySQLでも、「mysql」でいいです。これは両者が姉妹のようなプログラムの為です。DB_PORTはデータベースインストール時に特に変更していなければ「3306」です。

他に利用できるデータベースは次の通りです。

  • sqlite

    DB_CONNECTIONの値は「sqlite」とします。sqliteはファイル管理の為ポートの設定はありません。また、DB_DATABASEの値にsqliteのdbファイルへのフルパスを指定します。

  • postgreSQL

    DB_CONNECTIONの値は「pgsql」、DB_PORTの値は「5432」(デフォルト)とします。

  • SQL Server

    DB_CONNECTIONの値は「sqlsrv」、DB_PORTの値は「1433」(デフォルト)とします。

詳しくはLaravel:「Database」の項目を確認してください。

DB_HOSTの値ははデータベースが稼働しているPCのアドレスここではローカルホスト(127.0.0.1)です。これを変更するようなら先のMariaDBの権限設定のIPアドレスも変更する必要があります。ユーザー名、パスワードはデータベースに設定した値の通りに入れて下さい。

これらの.envの設定は、プロジェクト内のconfig/database.phpが呼ばれる際に利用されます。通常は特に意識する必要はないですが、カスタマイズしたい場合はこちらの.phpファイルを編集します。たとえば、sqliteでDB_DATABASEにファイル名を設定する際に、開発環境と実行環境のパスの差を吸収さえるため、database_path関数等を加えたりするかもしれません。

config/database.php

...
'sqlite' => [
  'driver' => 'sqlite',
  'url' => env('DATABASE_URL'),
  #'database' => env('DB_DATABASE', database_path('database.sqlite')),
  'database' => database_path(env('DB_DATABASE')),
  'prefix' => '',
  'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
...

マイグレーション

ここからはしばらく、「Laravel 9.x マイグレーション」を参考にマイグレーションについて触れていきます。

通常システムやデータの移転などを意味する「マイグレーション」という言葉ですが、Laravelではテーブル定義等、データベースの初期設定のような意味合いになります。Laravel側でテーブルを管理することで、データベースのテーブルレイアウトを伴ったアプリの改変時など管理の省力化になります。

マイグレーション用のファイルをLaravelで作成するには次のコマンドを入力します。

php artisan make:migration マイグレーションファイル名

この時、マイグレーションファイル名に「create_」という接頭をつけると、それ以降をテーブル名として判断します。また_tableと接尾をつけた場合はその部分はカットしてテーブル名にします。あとでコードの記述の省力化をしたいならテーブル名はsで終わるようにしておきます。

たとえば、「php artisan make:migration order_lists_table」とすると次のようなマイグレーションファイルがプロジェクトのdatabase¥migrationsフォルダに作成されます。

database¥migrations¥2022_06_10_025324_create_order_lists_table.php

<?php

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

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

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

公式の解説には、Laravelがテーブル名を認識できた時という表記でしたので他にもテーブル名を認識させる方法があるのかもしれません。認識できなかった場合は、up()、down()のメソッドが空白になっているだで、自分で記述すれば問題ありません。

このファイルにタイム単プが入っている理由はLaravelが実行順序を把握する為だそうです。

up()メソッドはイニシャル時、down()メソッドはリカバー時に利用されます。中で呼び出されるSchemaクラスの静的メソッド(※)が、データベースのテーブル操作と結びついています。

※LaravelにおいてSchemaは正確にはファサード:facadeと呼ばれる「利用可能なクラスの静的メソッドへのインターフェース」です。

createの第2引数に渡している関数の中でテーブルの定義をしています。デフォルトだとid列を作成するBlueprint::id()と、Blueprint::timestamp()メソッドが設定されています。

この状態のマイグレーションファイルをデータベースに反映させてみます。コマンドは「php artisan mirate」ですが、そのまえにPHPの設定ファイル上でデータベースライブラリが読み込まれているか確認してください。

php.ini

...
# コメントアウト状態になっていたら解除します。;を消します。
# また、PHP全体を通してextenstionの設定が初めてなら「extenstion_dir」の部分も確認してください。
extension=pdo_mysql
...

上記を設定していない場合は次のようなエラーがでます。ここではmysqlですが、sqliteやpostgreを使用する場合は、該当するエントリーのコメントアウト解除をしてください。

Illuminate¥Database¥QueryException 
could not find driver (SQL: select * from information_schema.tables where table_schema = information and table_name = migrations and table_type = 'BASE TABLE')

また.envに設定した情報と実際のMariaDBの環境が違ってもエラーとなります。うまくいかない場合はそのあたりも確認してみてください。

成功時はマイグレーションのステータスがウインドウに表示されます。

migrateコマンドではデータベースの管理テーブルに対象のテーブルがリストされていない場合、ファイル名の順にupメソッドを呼びます。

php artisan migrate
...
Migrating: 2022_06_10_030848_create_order_lists_table
Migrated:  2022_06_10_030848_create_order_lists_table (5.95ms)
...

MariaDBで実際に確認します。さきほど同様にrootでアクセスしてもいいですが、作成したLaravel用のユーザーでも接続できます。その際は-A でデータベース名を指定してアクセスすることが必要です。

PS > .¥mysql -u laraveluser -p -A laravel_project -h 127.0.0.1
Enter password: ***********
Welcome to the MariaDB monitor.  Commands end with ; or ¥g.
Your MariaDB connection id is 12
Server version: 10.6.8-MariaDB mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '¥h' for help. Type '¥c' to clear the current input statement.

MariaDB [laravel_project]> desc order_lists;
+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| id         | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | timestamp           | YES  |     | NULL    |                |
| updated_at | timestamp           | YES  |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+
3 rows in set (0.032 sec)

id()メソッドにより、idというフィールド名で符号なし20バイトのbigintの列をauto_incrementオプション付作られました。そしてtimestamp()メソッドにより、crate_atとupdate_atというフィル―ド名のtimestampの列が作られました。

同時に管理用のmigrationsという名前のテーブルも作成されます。

実行したマイグレーションを取り消したい場合は、次のようにします。

php artisan migrate:rollback

migrate:rollbackコマンドではファイル名の逆順にdownメソッドを呼びます。

カラムの構成を変えた後など、rollback後に再度migrateを実行するケースが多いと思いますが、そのような時のためにrollback後にmigrateを実行する、refreshというコマンドもあります。

php artisan migrate:refresh

もし、マイグレーション管理ファイルとデータベースとの同期が崩れてしまってrollbackがエラーとなる場合は、freshという一度すべてのテーブルを削除した後にmigrateを実行するコマンドもあります。このコマンドはmigrateコマンドでテーブルを作成したかそうでないかを問わずにデータベース内のテーブル全てを削除しますので注意してください。

php artisan migrate:fresh

カラム追加のためにBlueprint($table)オブジェクトで使えるメソッドのリストはavailable-column-typesに紹介されています。

たとえば次のようなものがあります。

  • $table->increments('seq');

    先ほど出てきたidメソッドのinteger版です。idメソッドはカラム名を省略することができますが、こちらはカラム名が必須です。

  • $table->char('user_id',20);

    user_idという名前で、20字のタイプがCHARの列ができます。

  • $table->string('user_name',50);

    VARCHARの列を作りたい場合は、stringメソッドを呼びます。

  • $table->decimal('average',8,2);

    MariaDB同様、Decimalの定義の場合は、全体長さと小数点の長さのふたつを引数に渡します。

  • $table->integer('rank');

    MariaDBではintと略している場合も多いかもしれませんが、メソッドにはintは無くintegerとなります。

  • $table->text('memo');

    MariaDBではTEXT型(65,535)のカラムとなります。

  • $table->longText('long-memo');

    MariaDBではLONGTEXT型(4,294,967,295)のカラムとなります。

  • $table->json('json');

    jsonはMariaDBではLONGTEXT型として扱われます。

  • $table->timestamp('ts');

    MariaDBでは同名の、「 1970-01-01 00:00:01 」~「 2038-01-19 03:14:07 」の値を保持できる日付型です。

  • $table->datetime('dt');

    MariaDBでは同名の、「 1000-01-01 00:00:00 」~「 9999-12-31 23:59:59 」の値を保持できる日付型です。

  • $table->binary('image');

    binaryはMariaDBのBLOBに相当します。MariaDBのBLOBはサイズが65,535バイトとわりと小さいため、「 MEDIUM BLOB (16M)」や「 LONG BLOB (4G)」などを使いたくなるケースがあるとおもいますが、それらに該当するメソッドは存在しないようです。

    そのような場合はALTER TABLEを使って直接クエリを実行します。

    ...
    public function up(){
    
      Schema::create('attachment_files', function (Blueprint $table) {
        $table->id();
        $table->string('file_name',255);
      });
      
      DB::statement("ALTER TABLE attachment_files ADD raw_data MEDIUMBLOB");
    }
    ...
  • $table->enum("customer_type", ["normal", "special"]);

    emunにも対応しています。

またprimary keyの設定は次のようになります。

$table->primary('id');
#$table->primary(['id','sub-id']);
$table->unique('col_unique');
$table->index(['col_1','col_2']);

複数列の場合は文字列の配列にして渡します。['id','sub-id']、primaryをuniqueにすればユニーク制約、indexにすればindexにできます。

また、dropPrimary、dropUnique、dropIndexと、それぞれを除去するメソッドも備わっています。

デフォルト値を設定したい場合は、カラム定義のあとにさらにメソッドを呼び、「->default(デフォルト値);」とします。

ここまでで、Laravelのデータベースの設定について一通りのことができるようになりました。

これらの設定やコーディングはデプロイや移植時、テーブルレイアウト修正時等をLaravelから操作するもので、一旦アプリが動いてしまうと触ることは少ないかもしれません。

モデルの作成

先に設定方法でデータベースの設定が終わったものとして、今度はアプリからデータベースにアクセスしてデータを取得したり、アプリからデータベースを更新できるようにします。

ここでは次のようなマイグレーションファイルでテーブルが作成してあるものとします。単純に名前とメールアドレスを記録しておくだけのテーブルです。

先ほどの例示したものに加えて、テーブルのcharsetをutf8mb4に設定、2038年以降の日付を設定できないtimestamp形式をdatetime形式に変更、登録時、修正時に自動的に値が入るようにしています。

database¥migrations¥2022_06_10_025324_create_email_lists_table.php

<?php
use Illuminate¥Database¥Migrations¥Migration;
use Illuminate¥Database¥Schema¥Blueprint;
use Illuminate¥Support¥Facades¥Schema;

class CreateEmailLists extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create("email_lists", function (Blueprint $table) {
            # charset設定
            $table->charset = 'utf8mb4';
         
            $table->id();
            $table->string("name", 50);
            $table->string("email", 255);
            
            # タイムスタンプ自動設定
            # MariaDBのDEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMPに相当します
            $table->datetime('created_at')->useCurrent();
            $table->datetime('update_at')->useCurrent()->useCurrentOnUpdate();
        });
    }

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

前回少し触れましたが、LaravelのMVCモデルにおいては、データ処理はモデル(M)によって行われるべきということでした。そこでLaravelのコード上にモデルを作成します。

ここではEmailListsという名前でモデルを作成します。モデルの名前を規則に沿わせると記述が少なくてすみます。対象のテーブル名から最後のsを外して「スネークケースをキャメルケースににします」言い換えると、_を除去して先頭と_で区切った直後の文字を大文字にします。例えばテーブル名が「email_lists」なら「EmailList」とします。さきほどデータベースのテーブル名がsで終わるようにした方がいいと書いたのはこの為です。

ちなみにモデル名がsで終わる際はsの付与はされないようです。

PS >php artisan make:model EmailList

appフォルダのModelsフォルダの中にひな形となるファイルが作成されると思います。間違えて作成されたら一度ファイルを消して作り直せばいいです。

できたクラスの中で$fillable配列の値として編集可能な列の名称の設定をします。ここでの設定はこれだけですが、継承元のModelクラスの中ではいろいろな処理がされます。

app¥Models¥EmailList.php

<?php

namespace App¥Models;

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

class EmailList extends Model
{
    use HasFactory;
    #protected $table = 'email_lists';
    protected $fillable = ["name", "email"];
    #protected $guarded = ['id'];
}

もしクラス名が命名規則に沿っていない場合は$tableプロパティでテーブル名を指定する事ができます。また編集可能な列の設定ではなく禁止の列を指定したい場合は$guardedに配列で設定します。

コントローラーを作成

次に、コントローラー(C)を作成します。これはユーザーインターフェースであるビュー(V)とモデルの仲介役となるものでした。

PS >php artisan make:controller EmailListController

実行後は、app¥Http¥Contollersフォルダにファイルが入ります。

app¥Http¥Controllers¥EmailListController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

//設定したモデル
use App\Models\EmailList;

class EmailListController extends Controller
{
    // 読み出し用
    public function selectall()
    {
        // 全データの取り出し
        $all = EmailList::all();
        // email.listビューにデータを渡します
        return view("mail-list", ["alldata" => $all]);
    }

    // 書き込み用
    public function insert(Request $request)
    {
        //内容チェック(バリデーション)
        $request->validate([
            // 空白禁止+最大50字 (|で複数指定)
            "namepost" => "required | max:50",
            // 空白禁止+メール表現チェック+正規表現チェック(配列で指定)
            // メール表現チェックに文字数制限が含まれているという前提です
            "emailpost" => ['required', 'email', 'regex:/^.*@gmail¥.com$/'],
        ]);
        $name = $request->input("namepost");
        $email = $request->input("emailpost");
        // テーブルのカラムに値をセットして挿入
        EmailList::insert(["name" => $name, "email" => $email]);

        // データ再表示
        $all = EmailList::all();
        return view("mail-list", ["alldata" => $all]);
    }
}

このコントローラーにはふたつのメソッドがありますが、どちらも「mail-list」ビューを返しています。

Laravelのモデルに関しては、もう少し掘り下げた記事を別に用意していますので、よろしかったら合わせてお読みください。

$requestからvalidateメソッドを呼ぶことで、入力データのチェックを行います。このメソッドで利用できるチェック項目の一覧は公式ページに載っています。また、Requestオブジェクトからvalidateを呼ぶと、エラー時には旧値を保存した上、メッセージと共に(結果が$errorsに入ります)呼び出し元のページへリダイレクトされるようになっています。

それらの挙動を変えたかったりチェックのルールを自作したり、LaravelのValidationのカスタマイズをしたい場合につきましては、別記事を参考にしてください。

ビューの作成

先ほどのコントローラーで戻り値となっていたビューを作成します。

ユーザーインターフェースであるビューを作成します。これはresources¥views¥フォルダに作成します。このファイルは手動で作成します。.phpの前の.bladeはLaravelの処理対象のビューであることを示しています。

@で始まる部分は、Laravelの記法です。@if~@endifまでは指定された条件が真のときのみ、@issetは指定されたデータが存在したときのみに囲まれた部分を出力します。@foreachはPHPのforeach同様要素の数だけ繰り返します。

これは通常のHTMLの記法ですが、formのタグの箇所でactionの値として/adduserが入っています。このアドレスに対してこのあとルーターの設定をします。

また「{{ csrf_field() }}」の部分はXSRF対策用のフィールドです。

resources¥views¥mail-list.blade.php

<!DOCTYPE HTML>  
<html>  
  <head>
      <title>メールリスト</title>  
  </head>  
  <body>  
    <h1>メールリスト</h1>  
    <!-- エラーメッセージ -->  
    @if ($errors->any())  
      <h3>エラーメッセージ</h3>
      <ul>  
        @foreach ($errors->all() as $error)  
          <li>{{ $error }}</li>  
        @endforeach  
      </ul>  
    @endif  
    
    <!--アドレスリスト作成-->  
    @isset($alldata)  
      <table>
        <tr><th>名前</th><th>アドレス</th></tr>  
        @foreach($alldata as $d)  
          <tr><td>{{ $d->name }}</td><td>{{ $d->email }}</td></tr>  
        @endforeach  
      </table>  
    @endisset  

    <!-- 登録画面 -->  
    <h3>アドレス登録</h3>  
    <form action="/adduser" method="POST">  
      <div>
        <label for="namepost">名前:</label>
        <input type="text" name="namepost" id="namepost">  
      </div>
      <div>  
        <label for="emailpost">メールアドレス:</label>
        <input type="email" name="emailpost" id="emailpost"> 
      </div>
      <div>{{ csrf_field() }}</div>
      <div>  
        <button type="submit"> 送信 </button>
      </div>
    </form>  
  </body>  
</html>

ルーターの設定

最後にWebのアクセスからの流れをルーターで設定します。routes¥web.phpに記述します。

mail-listにアクセスすると、全件表示処理後にmail-listビューにデータが渡されて表示されます。

登録時は、フォームからPOSTで/adduserへデータ送信するのでpostとなっているところに注意してください。さらに、URLが/adduserになっている状態でページでリロードされた際はGETメソッドが使われて、GETメソッドに対する処理が指定されていないとエラーとなるので、GETメソッドではURLが/mail-listとなっている時と同じ挙動にします。

routes¥web.php

<?php

use Illuminate\Support\Facades\Route;

use App\Http\Controllers\EmailListController;

Route::get('/mail-list',[EmailListController::class,'selectall']);
Route::post('/adduser',[EmailListController::class,'insert']);
Route::get("/adduser", [EmailListController::class, "selectall"]);
...

参考にさせていただきましたサイトの皆様、ありがとうございました。

筆者紹介


自分の写真
がーふぁ、とか、ふぃんてっく、とか世の中すっかりハイテクになってしまいました。プログラムのコーディングに触れることもある筆者ですが、自分の作業は硯と筆で文字をかいているみたいな古臭いものだと思っています。 今やこんな風にブログを書くことすらAIにとって代わられそうなほど技術は進んでいます。 生活やビジネスでPCを活用しようとするとき、そんな第一線の技術と比べてしまうとやる気が失せてしまいがちですが、おいしいお惣菜をネットで注文できる時代でも、手作りの味はすたれていません。 提示されたもの(アプリ)に自分を合わせるのでなく、自分の活動にあったアプリを作る。それがPC活用の基本なんじゃなかと思います。 そんな意見に同調していただける方向けにLinuxのDebianOSをはじめとした基本無料のアプリの使い方を紹介できたらなと考えています。

広告