学習記録:12月8日(土):【輪読会まとめ 前編】Laravel Webアプリケーション開発 Chapter5 データベース
これは 俺のインプットアウトプット記録 Advent Calendar 2018 の8日目のエントリーです。
先日、PHPフレームワーク Laravel Webアプリケーション開発 バージョン5.5 LTS対応 を購入して、輪読会に参加してきました。 僕の担当章だった 第二部 Chapter5 データベース を事前にまとめ、発表してきたのですが、実際にコードを実行しきれてませんでした。
そこで今日は、コードを実際に実行した結果も添えて、まとめを更新して公開したいと思います。(分量が多くなってしまったので前編、後編に分けます。)
やったこと
大したことはやってないんですが、以下を実施しました。もし読み進めているかたのためになればいいなあ。
- 章を読み進め、大事な部分と自分なりの意訳を書き出した
- インプットしたこと全てをアウトプット
- なるべくわかりやすく解説できるように
- コードはほぼ全て実際に実行した
- そのまま実行してもつまらないので、多少工夫
- MySQLに文字コード設定した
事前準備
まず事前準備から。MySQLを使う前提ですが、テストデータに日本語を使いたかったこと、自分なりの文字コードのこだわりがあったことで、 character-set
を設定し、Dockerコンテナをリビルドしました。
- my.cnfを以下に編集し、buildし直しておくこと
- じゃないと文字コードがlatin1のままで文字化けする
- 対象ファイルは
laradock/mysql/my.cnf
my.cnfの文字コード設定
utf8mb4
に統一します。
これで、データベース、テーブル、カラム別に文字コードを設定する必要がなくなります。むしろ文字コードをコードで個別に指定するのは、個人的にはバグの原因になるだけだと思っています。
[mysql] default-character-set=utf8mb4 [mysqld] character-set-server=utf8mb4 [client] loose-default-character-set=utf8mb4
dockerのMySQLをbuildし直す
以下のコマンドでビルドし直し、Dockerコンテナを起動します。
$ pwd /Users/mamy1326/dev/laravel_docker/laradock $ docker-compose build mysql (中略) $ docker-compose down (中略) $ docker-compose up -d nginx mysql workspace
イントロダクション
というわけで書籍を読み進めていきます。まずは導入部分から。
マイグレーションとEloquent、クエリビルダによるDB操作
5-1 マイグレーション
5-1-0 導入部
- 一般用語(移転、移行)と違い
- 実行はartisanコマンド
- DBに自分で接続する必要はない
- PHPコードで定義のためgitなどでバージョン管理しやすい
- 以下の流れで解説
5-1-1 マイグレーション処理の流れ
図5.1.1.1:マイグレーションの流れ
ただ読んで見るのもなんなので、書き起こしてみました。 この書き起こしがあったので、先日書いた 学習記録:12月7日(金):Webフレームワークに依存しない、PHP製のシンプルなSQL マイグレーションツール「Mig」を使ってみた でも理解がスムーズだったなあと思います。
5-1-2 マイグレーションファイルの作成
- マイグレーションファイル作成は以下のコマンド
リスト5.1.2.1:マイグレーションファイルの作成
$ php artisan make:migration [ファイル名] [オプション]
解説
- ファイル名には作成するマイグレーションファイルの名前
- ファイル名には年月日時分秒が付く
2014_10_12_000000_create_users_table.php
database/migrations
フォルダに作成される
- ファイル命名規則は特にないが、管理しやすい名前にしよう
- create_users_table
- add_column_to_users_table
- add_column_username_kana_to_users_table
オプション
表5.1.2.2:migrationコマンドのオプション
オプション | 機能 |
---|---|
--create=[テーブル名] | 新規テーブル作成のためのコードが付与される |
--table=[テーブル名] | 指定されたテーブルを操作するためのコードが付与される(テーブル設定の変更などで使用、ALTER TABLEなど) |
--path=[パス] | 指定されたパスにマイグレーションファイルを配置する。(アプリケーションのベースパスからの相対で指定する) |
マイグレーションファイルの作成
- 書籍管理プログラムを想定
- 著者(authors)
- 出版社(publishers)
- 書籍(books)
- 書籍詳細(bookdetails)
リスト5.1.2.3:マイグレーションファイルの作成
$ php artisan make:migration create_authors_table $ php artisan make:migration create_publishers_table $ php artisan make:migration create_books_table $ php artisan make:migration create_bookdetails_table
- 実際にやってみた内容
booksテーブル
$ php artisan make:migration create_books_table Created Migration: 2018_11_12_155051_create_books_table
マイグレーションファイルの確認
database/migrations
を確認- books_tableを開く
リスト5.1.2.4:マイグレーションファイル(CreateBooksTableクラス)
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateBooksTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('books', function (Blueprint $table) { $table->increments('id'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('books'); } }
解説
Illuminate\Database\Migrations\Migration
クラスを継承- upメソッドとdownメソッドを持つ
- upメソッド:データベース定義の追加変更を行う処理
- テーブル新規作成の Schema::create メソッド
- downメソッド:rollback処理
- テーブル削除の Schema::dropIfExists メソッド
- upメソッド:データベース定義の追加変更を行う処理
5-1-3 定義の記述
5-1-3-1 テーブル作成処理
- Schema::create メソッド
- upメソッド に定義を記載していく
表5.1.3.1:テーブル定義(authorsテーブル)
カラム | 型 | 備考 |
---|---|---|
id | AUTO_INCREMENT | - |
name | varchar(100) | 著者氏名 |
kana | varchar(100) | 著者氏名(カナ) |
created_at | timestamp | 作成日時 |
updated_at | timestamp | 更新日時 |
リスト5.1.3.5-1:authorsテーブル作成のためのコード
<?php class CreateAuthorsTable extends Migration { public function up() { Schema::create('authors', function (Blueprint $table) { $table->increments('id'); // ①auto increment $table->string('name', 100); // ②型メソッド(ここはstring) $table->string('kana', 100); // 同上 $table->timestamps(); // ③created_at, updated_atを自動作成 }); } public function down() { Schema::dropIfExists('authors'); } }
表5.1.3.2:テーブル定義(booksテーブル)
カラム | 型 | 備考 |
---|---|---|
id | AUTO_INCREMENT | - |
name | varchar(100) | 書籍名 |
bookdetail_id | integer | 書籍詳細ID |
author_id | integer | 著者ID |
publisher_id | integer | 出版社ID |
created_at | timestamp | 作成日時 |
updated_at | timestamp | 更新日時 |
リスト5.1.3.5-2:booksテーブル作成のためのコード
<?php class CreateBooksTable extends Migration { public function up() { Schema::create('books', function (Blueprint $table) { $table->increments('id'); $table->string('name', 100); $table->integer('bookdetail_id'); $table->integer('author_id'); $table->integer('publisher_id'); $table->timestamps(); }); } public function down() { Schema::dropIfExists('books'); } }
表5.1.3.3:テーブル定義(bookdetailsテーブル)
カラム | 型 | 備考 |
---|---|---|
id | AUTO_INCREMENT | - |
isbn | varchar(100) | ISBNコード |
published_date | date | 出版日 |
price | integer | 価格 |
created_at | timestamp | 作成日時 |
updated_at | timestamp | 更新日時 |
リスト5.1.3.5-3:bookdetailsテーブル作成のためのコード
<?php class CreateBookdetailsTable extends Migration { public function up() { Schema::create('bookdetails', function (Blueprint $table) { $table->increments('id'); $table->string('isbn', 100); $table->date('published_date'); $table->integer('price'); $table->timestamps(); }); } public function down() { Schema::dropIfExists('bookdetails'); } }
表5.1.3.4:テーブル定義(publishersテーブル)
カラム | 型 | 備考 |
---|---|---|
id | AUTO_INCREMENT | - |
name | varchar(100) | 出版社名 |
address | text | 住所 |
created_at | timestamp | 作成日時 |
updated_at | timestamp | 更新日時 |
リスト5.1.3.5-4:publishersテーブル作成のためのコード
<?php class CreatePublishersTable extends Migration { public function up() { Schema::create('publishers', function (Blueprint $table) { $table->increments('id'); $table->string('name', 100); $table->text('address'); $table->timestamps(); }); } public function down() { Schema::dropIfExists('publishers'); } }
表5.1.3.6:生成できるカラムタイプとスキーマビルダの構文
カラムタイプ | スキーマビルダ | 備考 |
---|---|---|
BOOLEAN型 | $table->boolean('カラム名'); | ー |
CHAR型 | $table->char('カラム名', 'サイズ'); | 第二引数で文字列長を指定 |
DATE型 | $table->date('カラム名'); | ー |
DATETIME型 | $table->dateTime('カラム名'); | ー |
DOUBLE型 | $table->double('カラム名', '最大桁数', '小数点の右側の桁数'); | 第二引数で有効な全体桁数、第三引数で小数点以下の桁数を指定 |
FLOAT型 | $table->float('カラム名', ''); | 同上 |
ID(主キー)カラム | $table->increments('カラム名'); | オートインクリメントするINT型カラムを生成。bigだと bigIncrements を使う |
INTEGER型 | $table->integer('カラム名'); | ー |
JSON型 | $table->json('カラム名'); | ー |
5-1-3-2 テーブル削除処理
- Schema::dropIfExists メソッド
- 自動生成されている
- あれば削除、なければ何もしない
- 特にコードを追加することはない
5-1-3-3 そのほかのテーブル定義メソッド
表5.1.3.7:カラムに属性を与えるメソッド
メソッド | 内容 |
---|---|
$table->string('name_kana', 100)->alter('name'); | 指定カラムの直後にカラム追加(MySQLのみ) |
$table->date('birthday')->nullable(); | NULL許可 |
$table->integer('blood_type')->default(0); | デフォルト値 |
$table->inclementes('bookdetail_id')->unsigned(); | 符号なし |
リスト5.1.3.8:VARCHARに対してNULL許容
$table->string('name', 50)->nullable();
リスト5.1.3.9:INTEGERに対してデフォルト
$table->integer('type')->default(0);
表5.1.3.10:インデックス系
メソッド | 内容 |
---|---|
$table->primary('id'); | PK付与 |
$table->primary(['id', 'parent_id']); | 複合PK |
$table->unique('book_id'); | UK付与。$table->integer('book_id')->unique(); でもOK |
$table->index('booldetail_id'); | 通常のindex付与 |
$table->index('bookdetail_id', 'bookdetails_idx'); | キー名を指定してindex付与 |
リスト5.1.3.11:カラムにインデックスを与える
<?php // idというINTEGER型のカラムにインデックスを付与する $table->integer('id')->index(); // 分けて書くことも可能 $table->integer('id'); $table->index('id'); // インデックスに名前を付与 $table->index('id', 'books_idx');
5-1-4 マイクレーションの実行とロールバック
- migrate コマンド
database/migrations
フォルダにあるマイグレーションファイルが対象- データベースに反映されていない内容を実行
リスト5.1.4.1:migrateコマンド実行
$ cd ../laradock $ docker-compose exec --user=laradock workspace bash (laradocのdocker内) $ cd /var/www $ php artisan migrate # artisanコマンドのある場所で実行 Migrating: 2018_11_12_155051_create_books_table Migrated: 2018_11_12_155051_create_books_table Migrating: 2018_11_13_160808_create_authors_table Migrated: 2018_11_13_160808_create_authors_table Migrating: 2018_11_13_161537_create_bookdetails_table Migrated: 2018_11_13_161537_create_bookdetails_table Migrating: 2018_11_13_161550_create_publishers_table Migrated: 2018_11_13_161550_create_publishers_table
リスト5.1.4.2:マイグレーション実行結果
$ docker-compose exec mysql bash (mysqlのdocker内) $ mysql -udefault -p default
mysql> show tables; +-------------------+ | Tables_in_default | +-------------------+ | authors | | bookdetails | | books | | migrations | | password_resets | | publishers | | users | +-------------------+ 7 rows in set (0.01 sec) mysql> show create table authors\G *************************** 1. row *************************** Table: authors Create Table: CREATE TABLE `authors` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, `kana` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci 1 row in set (0.00 sec) mysql> show create table books\G *************************** 1. row *************************** Table: books Create Table: CREATE TABLE `books` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, `bookdetail_id` int(11) NOT NULL, `author_id` int(11) NOT NULL, `publisher_id` int(11) NOT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci 1 row in set (0.00 sec) mysql> show create table bookdetails\G *************************** 1. row *************************** Table: bookdetails Create Table: CREATE TABLE `bookdetails` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `isbn` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, `published_date` date NOT NULL, `price` int(11) NOT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci 1 row in set (0.00 sec) mysql> show create table publishers\G *************************** 1. row *************************** Table: publishers Create Table: CREATE TABLE `publishers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, `address` text COLLATE utf8mb4_unicode_ci NOT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci 1 row in set (0.00 sec)
リスト5.1.4.3:ロールバックコマンド - 直前のマイグレーションのみロールバックしてくれる
$ php artisan migrate:rollback
リスト5.1.4.4:リセットコマンド - 全てのマイグレーションをまとめて元に戻す
$ php artisan migrate:reset
5-1-4-1 マイグレーションの実行状態を管理するテーブル
- 実行、未実行、ロールバックの状態管理するテーブル
- 以下に登録されていないマイグレーションファイルを実行
migrate:rollback
コマンドはbatch
がもっとも大きい番号のマイグレーションファイルが対象
mysql> show create table migrations\G *************************** 1. row *************************** Table: migrations Create Table: CREATE TABLE `migrations` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `batch` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci 1 row in set (0.00 sec) mysql> select * from migrations; +----+------------------------------------------------+-------+ | id | migration | batch | +----+------------------------------------------------+-------+ | 1 | 2014_10_12_000000_create_users_table | 1 | | 2 | 2014_10_12_100000_create_password_resets_table | 1 | | 3 | 2018_11_12_155051_create_books_table | 2 | | 4 | 2018_11_13_160808_create_authors_table | 2 | | 5 | 2018_11_13_161537_create_bookdetails_table | 2 | | 6 | 2018_11_13_161550_create_publishers_table | 2 | +----+------------------------------------------------+-------+ 6 rows in set (0.00 sec)
5-2 シーダー
- アプリケーション実行に必要なデータをシーダー機能を使って投入
導入部分
- マスタデータなどの初期データを投入する機能
- テストデータなども
- これらのデータをコードで実行する仕組みがシーダー
- Seeder、Factoryクラスでデータ投入
- Fakerでテストデータを作るところも紹介
5-2-1 シーダーの作成
リスト5.2.1.1:シーダーの作成
- database/seeder
にSeederクラスが生成される
- ファイル命名規則はmigrationでも言ったように適切にね
- Authorsテーブルにデータ投入なら AuthorsTableSeeder
$ php artisan make:seeder (ファイル名)
リスト5.2.1.2:作成直後のSeederクラス - Seeder実行
$ cd ../laradock $ docker-compose exec --user=laradock workspace bash (docker内) $ pwd /var/www $ php artisan make:seeder AuthorsTableSeeder Seeder created successfully.
作成直後のSeederクラス
<?php use Illuminate\Database\Seeder; class AuthorsTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { // } }
データ登録処理を追加
<?php (中略) public function run() { //AUthorsテーブルにレコードを10件登録する $now = \Carbon\Carbon::now(); for ($i = 1; $i <= 10; $i++) { $author = [ 'name' => '著者名' . $i, 'kana' => 'チョシャメイ' . $i, 'created_at' => $now, 'updated_at' => $now, ]; DB::table('authors')->insert($author); } }
5-2-2 シーダークラスを利用するための設定
database\seeds
の DatabaseSeeder.php を開く- runメソッドに以下を追加
php artisan make:seeder AuthorsTableSeeder
された時点でコメントで記述されているのでコメントアウトするだけでいい
リスト5.2.2.1:Authorテーブルにデータ登録を行う処理 - DatabaseSeeder.phpのrunメソッド
<?php (中略) public function run() { $this->call(AuthorsTableSeeder::class); }
5-2-3 シーディングの実行
リスト5.2.3.1:シーディング実行コマンド - データ投入実行
$ php artisan db:seed Seeding: AuthorsTableSeeder
リスト5.2.3.2:シーディング実行結果 - authorsテーブルのレコード
mysql> select * from authors; +----+-------------+----------------------+---------------------+---------------------+ | id | name | kana | created_at | updated_at | +----+-------------+----------------------+---------------------+---------------------+ | 1 | 著者名1 | チョシャメイ1 | 2018-11-14 15:35:16 | 2018-11-14 15:35:16 | | 2 | 著者名2 | チョシャメイ2 | 2018-11-14 15:35:16 | 2018-11-14 15:35:16 | | 3 | 著者名3 | チョシャメイ3 | 2018-11-14 15:35:16 | 2018-11-14 15:35:16 | | 4 | 著者名4 | チョシャメイ4 | 2018-11-14 15:35:16 | 2018-11-14 15:35:16 | | 5 | 著者名5 | チョシャメイ5 | 2018-11-14 15:35:16 | 2018-11-14 15:35:16 | | 6 | 著者名6 | チョシャメイ6 | 2018-11-14 15:35:16 | 2018-11-14 15:35:16 | | 7 | 著者名7 | チョシャメイ7 | 2018-11-14 15:35:16 | 2018-11-14 15:35:16 | | 8 | 著者名8 | チョシャメイ8 | 2018-11-14 15:35:16 | 2018-11-14 15:35:16 | | 9 | 著者名9 | チョシャメイ9 | 2018-11-14 15:35:16 | 2018-11-14 15:35:16 | | 10 | 著者名10 | チョシャメイ10 | 2018-11-14 15:35:16 | 2018-11-14 15:35:16 | +----+-------------+----------------------+---------------------+---------------------+ 10 rows in set (0.00 sec)
5-2-4 Fakerの利用
- より現実に近いテストデータをよしなに作ってくれる
- Laravelではなく外部のライブラリ
- 詳しくはこのへん
表5.2.4.1:Fakerで作成できる主なダミーデータ
項目名 | 出力データ |
---|---|
name | 氏名 |
メールアドレス | |
safeEmail | 安全な電子メール(存在しない) |
password | パスワード(PVqg5V!{/6MWHzg/FLe]、とか) |
country | 国名 |
address | 住所 |
phoneNumber | 電話番号 |
company | 企業名 |
realText | テキストデータ |
PublishersTableSeeder準備
$ php artisan make:seeder PublishersTableSeeder Seeder created successfully.
リスト5.2.4.2:Fakerでのデータ投入コード - PublishersTableSeederにFakerを使ったデータ投入
<?php (中略) public function run() { //Fakerを使ってレコードを10件投入 $faker = Faker\Factory::create('ja_JP'); $now = \Carbon\Carbon::now(); for ($i = 1; $i <= 10; $i++) { $publisher = [ 'name' => $faker->company . '出版', 'address' => $faker->address, 'created_at' => $now, 'updated_at' => $now, ]; DB::table('publishers')->insert($publisher); } }
DatabaseSeeder.phpのrunメソッドにPublishersTableSeeder追加
<?php (中略) public function run() { $this->call(AuthorsTableSeeder::class); $this->call(PublishersTableSeeder::class); }
リスト5.2.4.3:実行コマンドと結果 - Seeder実行
$ php artisan db:seed Seeding: AuthorsTableSeeder Seeding: PublishersTableSeeder
Fakerでのデータ投入結果
mysql> select * from publishers; +----+------------------------------+-----------------------------------------------------------------------------------+---------------------+---------------------+ | id | name | address | created_at | updated_at | +----+------------------------------+-----------------------------------------------------------------------------------+---------------------+---------------------+ | 1 | 有限会社 佐藤出版 | 5493319 富山県伊藤市北区佐藤町宮沢2-2-9 ハイツ桐山104号 | 2018-11-14 15:56:28 | 2018-11-14 15:56:28 | | 2 | 有限会社 杉山出版 | 7208783 大分県小泉市東区山口町加納2-3-1 ハイツ杉山102号 | 2018-11-14 15:56:28 | 2018-11-14 15:56:28 | | 3 | 有限会社 工藤出版 | 7078513 埼玉県松本市中央区斉藤町三宅4-10-9 ハイツ喜嶋105号 | 2018-11-14 15:56:28 | 2018-11-14 15:56:28 | | 4 | 株式会社 杉山出版 | 5177296 三重県中村市西区原田町渡辺8-3-1 | 2018-11-14 15:56:28 | 2018-11-14 15:56:28 | | 5 | 株式会社 青山出版 | 2219948 千葉県西之園市東区井上町佐々木4-5-8 ハイツ井高110号 | 2018-11-14 15:56:28 | 2018-11-14 15:56:28 | | 6 | 有限会社 大垣出版 | 6776246 島根県加納市西区高橋町田辺6-6-4 | 2018-11-14 15:56:28 | 2018-11-14 15:56:28 | | 7 | 有限会社 原田出版 | 2796245 福岡県藤本市中央区田中町工藤10-4-10 | 2018-11-14 15:56:28 | 2018-11-14 15:56:28 | | 8 | 株式会社 吉本出版 | 9972025 福井県廣川市東区吉本町笹田5-4-2 ハイツ青山104号 | 2018-11-14 15:56:28 | 2018-11-14 15:56:28 | | 9 | 有限会社 佐々木出版 | 8481973 群馬県山田市北区宇野町喜嶋6-2-10 | 2018-11-14 15:56:28 | 2018-11-14 15:56:28 | | 10 | 株式会社 田辺出版 | 3881619 高知県津田市中央区喜嶋町宮沢9-1-7 ハイツ藤本110号 | 2018-11-14 15:56:28 | 2018-11-14 15:56:28 | +----+------------------------------+-----------------------------------------------------------------------------------+---------------------+---------------------+ 10 rows in set (0.00 sec)
5-2-5 Factoryを利用する例
- 大量のデータ投入に便利
database\factories\ModelFactory.php
内に- Eloquentクラスごとのファクトリーを記述
- シーダーで利用するダミーデータを簡単生成
モデルクラスを作る
- ファクトリークラスを作成し、データ投入処理を定義
- シーダークラスのrunメソッドに、ファクトリークラスの利用処理追加
- DatabaseSeederクラスのrunメソッドで「3.」を呼び出す
リスト5.2.5.1:モデルクラス作成
- app\BookDetail.php
$ php artisan make:model Bookdetail Model created successfully.
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Bookdetail extends Model { // }
リスト5.2.5.2:ファクトリークラス作成
- database\factories
に作成
$ php artisan make:factory ModelFactory Factory created successfully.
- 処理が空なのでfakerで定義
$factory->define
の第一引数に記述したクラスにこの内容がインジェクションされる?
<?php use Faker\Generator as Faker; $factory->define(App\Bookdetail::class, function (Faker $faker) { $faker->locale('ja_JP'); $now = \Carbon\Carbon::now(); return [ 'isbn' => $faker->isbn13, 'published_date' => $faker->date($format = 'Y-m-d', $max = 'now'), 'price' => $faker->randomNumber(4), 'created_at' => $now, 'updated_at' => $now, ]; });
リスト5.2.5.3:bookdetailsテーブルに50件追加 - BookdetailsTableSeeder作成
$ php artisan make:seeder BookdetailsTableSeeder Seeder created successfully.
BookdetailsTableSeeder編集
<?php use Illuminate\Database\Seeder; class BookdetailsTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { factory(\App\Bookdetail::class, 50)->create(); } }
リスト5.2.5.4:DatabaseSeederクラスへの定義追加
<?php use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $this->call(AuthorsTableSeeder::class); $this->call(PublishersTableSeeder::class); $this->call(BookdetailsTableSeeder::class); } }
リスト5.2.5.5:Seeder実行後のbookdetailsテーブル
$ php artisan db:seed Seeding: AuthorsTableSeeder Seeding: PublishersTableSeeder Seeding: BookdetailsTableSeeder
- 一括投入されたことがわかる
- Fakerクラスでデータが作られている
- SeederとFakerを組み合わせてデータ投入できる
- マイグレーションと合わせて、バージョン管理しておくといいよ
実際のテーブルの中身
mysql> select * from bookdetails; +----+---------------+----------------+-------+---------------------+---------------------+ | id | isbn | published_date | price | created_at | updated_at | +----+---------------+----------------+-------+---------------------+---------------------+ | 1 | 9795923865058 | 1980-04-24 | 6129 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 2 | 9794940757186 | 1987-06-05 | 7514 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 3 | 9789270927361 | 1998-06-08 | 2773 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 4 | 9792653154490 | 2008-05-31 | 2996 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 5 | 9798697951064 | 1972-11-14 | 5773 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 6 | 9794957103815 | 1982-06-29 | 2381 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 7 | 9782603321232 | 2003-03-02 | 5667 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 8 | 9784540122101 | 2005-06-15 | 9117 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 9 | 9798658650449 | 1983-05-03 | 7808 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 10 | 9792355798435 | 1996-03-03 | 7392 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | (中略) | 40 | 9797964095913 | 1971-05-12 | 7286 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 41 | 9796726059019 | 1999-02-25 | 7591 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 42 | 9785517103987 | 2006-05-12 | 9723 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 43 | 9784238339682 | 2009-02-24 | 9908 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 44 | 9784171697207 | 1986-05-02 | 3931 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 45 | 9785986632216 | 1973-02-12 | 9888 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 46 | 9794838164485 | 1975-02-12 | 7932 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 47 | 9797802033428 | 1984-02-28 | 3756 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 48 | 9783292365408 | 2007-12-22 | 66 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 49 | 9788521682677 | 2000-11-14 | 9093 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | | 50 | 9780480668365 | 2007-02-26 | 586 | 2018-11-14 16:36:18 | 2018-11-14 16:36:18 | +----+---------------+----------------+-------+---------------------+---------------------+ 50 rows in set (0.00 sec)
5-3 Eloquent
- LaravelのORM
- 1つのテーブルに1つのEloquent
- リレーション定義のメソッドを使えば複数テーブルも操作可能
5-3-1 クラスの作成
リスト5.3.1.1:Eloquentのクラスファイル作成コマンド
$ php artisan make:model (クラス名)
リスト5.3.1.2:作成直後のクラス
- App\Author
に出力される
$ php artisan make:model Author Model created successfully.
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Author extends Model { // }
5-3-2 規約とプロパティ
- 対応するテーブル名、主キーのカラム名などルールがある
- プロパティを指定することで任意の設定にできる
5-3-2-1 テーブルとの関連づけ
テーブル名が1単語
- テーブル名を複数形で作成
- クラス名を単数形で作成
- 暗黙的に関連づけされる
- authors テーブルは Author クラス
テーブル名が複数単語
- スネークケースのテーブル名
- クラス名はキャメルケース
- book_sample テーブルは BookSample クラス
- 複数形の話はどこいった?
- 試してないです
- 複数形の話はどこいった?
それ以外のケースに対応するには
protected $table
に定義
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Author extends Model { // t_author テーブルを関連づける protected $table = 't_author'; }
5-3-2-2 プライマリキーの定義
- PKをEloquentクラスに定義する
- Eloquentのメソッドにキー値を与えるだけでレコード取得できる
- PKはデフォルト id
- 明示的に設定するには以下
リスト5.3.2.2:任意のカラム名を主キーに設定
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Author extends Model { // 主キーを定義 protected $primaryKey = 'authors_id'; }
5-3-2-3 タイムスタンプの定義
- デフォルトは
- created_at に作成日時
- updated_at に更新日時
- 必要としない設定も可能
リスト5.3.2.3:タイムスタンプを記録しない設定
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Author extends Model { // タイムスタンプを記録しない(デフォルトはtrue) protected $timestamps = false; }
5-3-2-4 Mass Assignment による脆弱性への対策
- 後述の created update メソッドの引数に連想配列を渡す
- データ登録が可能
- Mass Assignmentという便利機能
- 有効にするには以下
リスト5.3.2.4:編集可能なカラムを設定(ホワイトリスト)
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Author extends Model { // nameとkanaを指定可能にする protected $fillable = [ 'name', 'kana', ]; }
リスト5.3.2.5:編集を認めないカラムを設定(ブラックリスト)
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Author extends Model { // id, created_at, updated_atを編集不可 protected $guarded = [ 'id', 'created_at', 'updated_at', ]; }
表5.3.2.6:その他のEloquentプロパティ
プロパティ | 説明 | デフォルト |
---|---|---|
$connection | データベース接続 | 設定ファイル database.php で設定されたデフォルト |
$dateFormat | タイムスタンプのフォーマット | Y-m-d H:i:s |
$incrementing | PKが自動増加かどうか | true |
5-3-3 データ検索・データ更新の基本
5-3-3-1 全権抽出 - all
- all メソッド
リスト5.3.3.1:レコード全件取得
<?php $authors = \App\Author::all(); foreach ($authors as $author) { echo $author->name; // nameカラム表示 }
リスト5.3.3.2:レコード数の取得
<?php $authors = \App\Author::all(); $count = $authors->count();
リスト5.3.3.3:filterメソッドでの絞り込み
<?php $authors = \App\Author::all(); $filtered_authors = $authors->filter(function ($author) { // idが5より大きいレコード抽出 return $author->id > 5; }); // 絞り込み結果を取得 foreach ($filtered_authors as $author) { echo $author->name; // nameカラム表示 }
5-3-3-2 プライマリキー指定による抽出 - find, findOrFail
- PK指定でレコード取得
- 戻り値は
Illuminate\Database\Eloquent\Model
のインスタンス
リスト5.3.3.4:findメソッドで利用
<?php // authorsテーブルの id=10 のレコード取得 $authors = \App\Author::find(10);
リスト5.3.3.5:findOrFailメソッドで利用
<?php try { // authorsテーブルの id=10 のレコード取得 $authors = \App\Author::findOrFail(10); } catch (Illuminate\Database\Eloquent\ModelNotFoundException $e) { // 見つからなかった時の処理 }
5-3-3-3 条件指定による抽出 -whereXXX
- where句に相当
- XXXにはカラム名
リスト5.3.3.6:whereXXXメソッドで条件指定
<?php // authorsテーブルの name='山田太郎' のレコード取得 $authors = \App\Author::whereName('山田太郎')->get();
5-3-3-4 新しいレコードの登録 - create, save
- 配列を引数
- create
- Eloquentモデルのインスタンス作成
- 各カラムの値を設定して登録
- save
- 自動的にcreated_at, updated_atに値が入るよ
リスト5.3.3.7:createメソッドで登録
<?php \App\Author::create([ 'name' => '著者A', 'kana' => 'チョシャエー', ]);
リスト5.3.3.8:saveメソッドで登録
<?php $author = new \App\Author(); $author->name = '著者A', $author->kana = 'チョシャエー', $author->save();
5-3-3-5 データ更新 - update
- findで指定して、create, saveと同じ感じ
リスト5.3.3.9:updateメソッドで更新
<?php $author = \App\Author::find(1)->update(['name' => '著者B']);
リスト5.3.3.10:saveメソッドで更新
<?php $author = new \App\Author::find(1); $author->name = '著者B', $author->kana = 'チョシャビー', $author->save();
5-3-3-6 データ削除 - delete, destroy
リスト5.3.3.11:deleteメソッドで削除
<?php $author = \App\Author::find(1) $author->delete();
リスト5.3.3.12:destroyメソッドで削除 - PKがわかってる場合
<?php $author = new \App\Author::destroy(1); $author = new \App\Author::destroy([2, 5, 7]); // 以下も同じ動作 $author = new \App\Author::destroy(2, 5, 7);
5-3-4 データ操作の応用
5-3-4-1 クエリビルダによるデータ操作を行う
- 次章で詳述
- 本項では where orderBy メソッド
リスト5.3.4.1:クエリビルダーによるデータ抽出
<?php // id=1 or 2 を取得 $authors = \App\Author::where('id', 1)->orWhere('id', 2)->get(); // idが5以上を並び替え $authors = \App\Author::where('id', '>=', 5) ->orderBy('id') ->get();
5-3-4-2 結果をJSONで取得する
- APIで返す結果とかで重宝しそう
リスト5.3.4.2:抽出結果をJSONで取得
<?php $author = \App\Author::find(1); return $author->toJson();
リスト5.3.4.3:toJsonメソッドの結果
{"id":1,"name":"著者名1","kana":"チョシャメイイチ","created_at":"2018-07-18 14:27:09","updated_at":"2018-07-18 14:27:09"}
5-3-4-3 カラムの値に対して固定の編集を加える
- 金額に3桁ごとにカンマ、半角カナを全角、などを自動実行できる
- アクセサ
- Eloquentのカラムに get[カラム名]Attribute の名前でメソッド追加
- 編集処理(mb_convert系など)を書く
- SELECTしたカラムに対し実行される
- 発火はカラムを参照したとき、変数に入れたりechoされたりのタイミング
- ミューテータ
リスト5.3.4.4:アクセサとミューテータを定義
<?php declare(strict_types=1); namespace App; use Illuminate\Database\Eloquent\Model; class Author extends Model { public function getKanaAttribute(string $value): string { // kanaカラムを半角カナに変換、DBから値を取得して表示した際 return mb_convert_kana($value, "k"); } public function setKanaAttribute(string $value): string { // kanaカラムを全角カナに変換、Eloquentインスタンスのカラム値に代入された際 $this->attributes['kana'] = mb_convert_kana($value, "KV"); } }
リスト5.3.4.5:アクセサとミューテータが定義されたカラムを利用
<?php // DBからデータ取得時 $authors = \App\Author::all(); foreach ($authors as $author) { echo $author->kana; // getKanaAttributeで全角半角変換され表示される } // DBへデータ登録時 $author = \App\Author::find(Input::get('id')); $author->kana = Input::get('kana'); // setKanaAttributeで半角から全角に変換される $author->save();
5-3-4-4 「データがない場合のみ登録」をシンプルに実装
- firstOrCreate, firstOrNew メソッド
リスト5.3.4.6:データがない場合のみデータ登録(通常)
<?php $author = \App\Author::where('name', '=', '著者A')->first(); if (empty($author)) { $author = \App\Author::create(['name' => '著者A']); }
リスト5.3.4.7:firstOrCreate でデータがない場合のみデータ登録
<?php $author = \App\Author::firstOrCreate(['name' => '著者A']);
リスト5.3.4.8:firstOrNew でデータがない場合のみデータ登録
<?php $author = \App\Author::firstOrNew(['name' => '著者A']); $author->save();
5-3-4-5 論理削除を利用する
- delete, destroyは物理削除
deleted_atカラムを追加し、使えるようにすれば日付が入り、論理削除となる
migrationでalter table用のファイル追加
- migrationファイルに定義変更処理追加
- migration実行でカラム追加
- ModelにSoftDeleteトレイト定義
- 論理削除データを扱う
リスト5.3.4.9:alter table用のmigrationファイル追加
$ php artisan make:migration softdelete_authors_table --table=authors
Created Migration: 2018_11_15_174305_softdelete_authors_table
リスト5.3.4.10:deteted_atを追加
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class SoftdeleteAuthorsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('authors', function (Blueprint $table) { $table->softDeletes(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('authors', function (Blueprint $table) { $table->dropColumn('deleted_at'); }); } }
リスト5.3.4.11:migrationでカラム追加
$ php artisan migrate Migrating: 2018_11_15_174305_softdelete_authors_table Migrated: 2018_11_15_174305_softdelete_authors_table
mysql> show create table authors\G *************************** 1. row *************************** Table: authors Create Table: CREATE TABLE `authors` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, `kana` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=51 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci 1 row in set (0.00 sec)
リスト5.3.4.12:SoftDeletesトレイトの定義 - ここまでで論理削除できるようになる
<?php declare(strict_types=1); namespace App; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class Author extends Model { use SoftDeletes; }
リスト5.3.4.13:論理削除も含めたデータ抽出 - ここまでで、Authorモデルのdelete, destroyメソッドは論理削除になる - deleted_atに日付が入る、NULLなら有効レコード - 論理削除されたデータを扱う場合は withTrashed, onlyTrashed
<?php // 削除済みも一緒に取得 $authors = App\Author::withTrashed()->get(); // 削除済みだけ取得 $deleted_authors = App\Author::onlyTrashed()->get();
5-3-5 関連性を持つテーブル群の値をまとめて操作する(リレーション)
- authors(著者)は複数のbooks(書籍)を書いている
- publishers(出版社)は複数のbooks(書籍)を販売している
- 1対1、1対多、多対多
5-3-5-1 一対一の定義 - hasOne, belongsTo
- hasOne
- 上から下、正引き
- belongsTo
- 下から上、逆引き
- パラメータ
- 第一
- 関連づけるModel
- 第二
- リレーションの外部キーがモデル名に基づいている場合
- 自動的にModelは外部キーを持っている
- この規約をオーバーライドしたければ、hasOneメソッドの第2引数を指定
- 第三
- リレーションで他のidを使う場合、カスタムキーを指定
- 第一
リスト5.3.5.1:hasOneによる正引きリレーション
<?php declare(strict_types=1); namespace App; use Illuminate\Database\Eloquent\Model; class Book extends Model { public function detail() { return $this->hasOne('\App\Bookdetail'); } }
リスト5.3.5.2:belongsToによる逆引きリレーション
<?php declare(strict_types=1); namespace App; use Illuminate\Database\Eloquent\Model; class Bookdetail extends Model { public function book() { return $this->belongsTo('\App\Book'); } }
リスト5.3.5.3:リレーションされたカラム呼び出し
<?php $book = \App\Book::fine(1); echo $book->detail->isbn; // リレーションされた書籍詳細の情報を取得できる
5-3-5-2 一対多の関係の定義 - hasMany
リスト5.3.5.4:hasManyによる一対多のリレーション
<?php declare(strict_types=1); namespace App; use Illuminate\Database\Eloquent\Model; class Author extends Model { public function books() { return $this->hasMany('\App\Book'); } }
リスト5.3.5.5:リレーションされたカラムの呼び出し - 逆引きは belongsTo を使う
<?php $books = \App\Author::find(1)->books(); // Authorしか取得してないのに、著者に紐づく書籍が全て取得できる foreach ($books as $book) { echo $book->name; }
5-3-6 実行されるSQLの確認
リスト5.3.6.1:適用されるSQL取得
<?php $sql = \App\Author::where('name', '=', '著者A')->toSql();
リスト5.3.6.2:取得できるSQL - プリペアドステートメント化されている
select * from authors where name = ?
リスト5.3.6.3:getQueryLogでSQL取得
<?php // SQL出力を有効化 DB::enableQueryLog(); // データ操作実行 $authors = \App\Author::find([1, 3, 5]); // SQL取得 $queries = DB::getQueryLog(); // SQL出力を無効化 DB::disableQueryLog();
リスト5.3.6.4:getQueryLogで取得できるSQL配列 - findの中身はIN句に変換されている - 検索対象のテーブル、レコード数によってはパフォーマンスに影響する可能性がわかる - SQLを確認してより効率的な処理をしよう
<?php array:1 [ 0 => array:3 [ "query" => "select * from authors where authors.id in (?, ?, ?)" "bindings" => array:3 [ 0 => 1 1 => 3 2 => 5 ] "time" => 11.55 ] ]
中間まとめ
この後、クエリビルダ、リポジトリパターンと続きますが、分量が多い(現時点で3.7万字)ので、2つに分割します。Eloquentは便利だしOOPで実装するにはこっちのほうがいいのかなって思ったりしますが、次章のクエリビルダでも十分じゃないかな、と思ったりもします。
とはいえ開発現場や実装済みのプロダクト・サービスでは使われていること多いでしょうし、覚えておいて損はないし、いったんLaravelの横道に従って書いてみて、 Laravelで最速のコードを書ける ところまで行ければそれはそれで素晴らしいとも思いました。
データベースを取り扱う場合に、可能な限り抽象化されているというのはやはり利点の方が勝るとも思うので、どうにかしてEloquent、クエリビルダの両方を使ってみて、結果を検証したいなって思いました。
migration、Seeder、Fakerについてはもう、便利だなの一言に尽きます。いくら上手にERDを作っても、後からの追加変更は必ず起きると思います。
そんな時、コードで世代管理されていれば歴史も辿りやすいし、理解も早い。僕は実際にコマンドラインでデータベースに接続して見る派なのですが、そうでない場合はコードでサクッと見れる方が便利だよな、とも思いました。
テストデータもそれらしいのがサクッと作れ、こちらもまたコードで世代管理できる。むかーしC言語でスタブを書きまくっていた時代を振り返ると、便利になったもんだなあ、と感慨深いです。
僕は恥ずかしながら今まで、フレームワークの機能を正しく使って開発をした経験がほぼありません。なので本書を読んで、データベースの章でmigrationを実行し、Seeder、Fakerでデータを作ってクエリを実行してみる。
道具はやはり、使い方と使い道を間違えなければ非常に便利だな、って思いました。
この後も読み進め、終わった段階で自分の作りたいサービスをLaravelで作ってみようと思っています。
というわけで後半に続きます。