Laravel による書籍管理システムの作成の第2回です。
今回は各テーブルにおける「リレーション」の設定を扱いたいと思います。
1. モデルファイルのリレーション設定
前回のポストで、テーブルのマイグレーションファイルを作成し、一緒にモデルファイルを作成しました。
モデルファイルは app/Models/ ディレクトリへ6個作成されてます。
- app/Models/Author.php :著者
- app/Models/Book.php :書籍(中心となるテーブル)
- app/Models/Calligraphy.php :書影
- app/Models/Category.php :カテゴリ(マスタ)
- app/Models/Publisher.php :出版社(マスタ)
- app/Models/Tag.php :タグ
このモデルファイルたちに、各テーブルの関係性であるリレーション設定を記入していきます。
1回目にも貼っつけましたが、ER図にすると以下のように、書籍テーブルを中心に各テーブルが紐づいているイメージです。
1-1. Book モデル
対象ファイル:app/Models/Book.php
書籍は「あるカテゴリ」に属し、「ある出版社」に属するためbelongsToを定義します。
また、著者、書影、タグに対しては、著者が「1」に対し書籍は「多」となるのでhasManyを設定します。
リレーションの考え方で一番わかり易いのは、例えば著者と書籍の場合の1対多の場合は、『田中芳樹は「銀河英雄伝説」、「アルスラーン戦記」、「創竜伝」など複数の作品を手掛けている』と考えるのが一番かと思われます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; class Book extends Model { use HasFactory; protected $table = 'books'; // どのカラムを操作許可するか(fillable)の設定 protected $fillable = [ 'title', 'title_kana', 'subtitle', 'volume_number', 'isbn_code', 'publisher_id', 'publication_year', 'category_id', 'price', 'description', 'memo', 'is_deleted' ]; /** * カテゴリとのリレーション (多対1) * 書籍は1つのカテゴリに属する */ public function category(): BelongsTo { // 第2引数は外部キー(category_id)、第3引数は親のキー(id)ですが、命名規則通りのため省略可能です。 return $this->belongsTo(Category::class, 'category_id'); } /** * 出版社とのリレーション (多対1) * 書籍は1つの出版社に属する */ public function publisher(): BelongsTo { return $this->belongsTo(Publisher::class, 'publisher_id'); } /** * 著者とのリレーション * 1冊の書籍は複数の著者を持つ可能性がある */ public function authors(): HasMany { // 第2引数は相手(authors)テーブルの外部キー(book_id) return $this->hasMany(Author::class, 'book_id'); } /** * 書影とのリレーション * 1冊の書籍は複数の書影を持つ可能性がある */ public function calligraphies(): HasMany { return $this->hasMany(Calligraphy::class, 'book_id'); } /** * タグとのリレーション * 1冊の書籍は複数のタグを持つ可能性がある */ public function tags(): HasMany { return $this->hasMany(Tag::class, 'book_id'); } } |
belongsTo(多対1) の場合のアクション名(メソッド名)は単数形で、hasMany(1対多)の場合のアクション名(メソッド名)は複数形としていますが、わかりやすくするのが目的でそうなっているだけで、ここは自由に設定してもらっても問題ありません。
その様にするのが Laravelの流儀のようです。
1-2. Category モデル
対象ファイル:app/Models/Category.php
カテゴリは「複数の書籍」を持つため、hasMany を定義します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class Category extends Model { use HasFactory; // テーブル名の手動指定(前回の設定を維持) protected $table = 'm_categories'; protected $fillable = [ 'category_name', 'is_deleted' ]; /** * 書籍とのリレーション (1対多) * 1つのカテゴリは複数の書籍を持つ */ public function books(): HasMany { // 第2引数は相手(books)テーブルの外部キー(category_id) return $this->hasMany(Book::class, 'category_id'); } } |
1-3. Publisher モデル
対象ファイル:app/Models/Publisher.php
出版社も同様に「複数の書籍」を持つため、hasMany を定義します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class Publisher extends Model { use HasFactory; // テーブル名の手動指定(前回の設定を維持) protected $table = 'm_publishers'; protected $fillable = [ 'publisher_name', 'publisher_kana', 'memo', 'is_deleted' ]; /** * 書籍とのリレーション (1対多) * 1つの出版社は複数の書籍を持つ */ public function books(): HasMany { // 第2引数は相手(books)テーブルの外部キー(publisher_id) return $this->hasMany(Book::class, 'publisher_id'); } } |
1-4. Author モデル
対象ファイル:app/Models/Author.php
著者は「ある書籍」に属するため、belongsTo を定義します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; class Author extends Model { use HasFactory; protected $table = 'authors'; protected $fillable = [ 'book_id', // 親となる書籍ID 'author', // 著者名 'author_kana', // 著者名かな 'author_job', // 肩書 'is_deleted' ]; /** * 書籍とのリレーション (多対1) */ public function book(): BelongsTo { // 第2引数は自身の外部キー(book_id) return $this->belongsTo(Book::class, 'book_id'); } } |
1-5. Calligraphy モデル
対象ファイル:app/Models/Calligraphy.php
書影も「ある書籍」に属します。
テーブル名を単数形 calligraphy に指定している点に注意してください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; class Calligraphy extends Model { use HasFactory; /** * モデルに関連付けるテーブル * Laravelの規約(calligraphies)と異なるため明示的に指定 */ protected $table = 'calligraphy'; protected $fillable = [ 'book_id', 'filepath', 'memo', 'is_deleted' ]; /** * 書籍とのリレーション (多対1) */ public function book(): BelongsTo { return $this->belongsTo(Book::class, 'book_id'); } } |
1-6. Tag モデル
対象ファイル:app/Models/Tag.php
タグも同様に「ある書籍」に属します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; class Tag extends Model { use HasFactory; protected $table = 'tags'; protected $fillable = [ 'book_id', 'tag', 'is_deleted' ]; /** * 書籍とのリレーション (多対1) */ public function book(): BelongsTo { return $this->belongsTo(Book::class, 'book_id'); } } |
これで全てのリレーション設定が完了しました。
1-7. データ取得、描画の使用イメージ
ちょっと気が早いですが、でっかくなので使用するイメージを。
コントローラーとかビューとか混じっていますが、下記のようにすると、書籍データからそれに紐づく著者や書影を一括で取得できるようになります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// IDが1の書籍を取得し、関連する著者、書影、タグも一緒にロードする $book = Book::with(['authors', 'calligraphies', 'tags'])->find(1); // データの表示例 echo "書名: " . $book->title; foreach ($book->authors as $author) { echo "著者: " . $author->author . " (" . $author->author_job . ")"; } foreach ($book->tags as $tag) { echo "タグ: " . $tag->tag; } |
1-8. fillable プロパティについて
1-8-1. 「fillable」 の概要
各モデルに登場する「fillable」についてですが、これはフォームなどのフロント部分からDBのカラムにデータ保存する際に、挿入する項目を指定するという働きを持つプロパティです。
下記はBookモデルの例。
|
1 2 3 4 5 6 |
// どのカラムを操作許可するか(fillable)の設定 protected $fillable = [ 'title', 'title_kana', 'subtitle', 'volume_number', 'isbn_code', 'publisher_id', 'publication_year', 'category_id', 'price', 'description', 'memo', 'is_deleted' ]; |
id、created_at、updated_at など、自動で作成される項目以外を指定しています。
これで fillableプロパティに指定されたカラムにのみ、一括保存・更新処理が許可された ということになります。
いわゆるホワイトリスト形式でのセキュリティ対策ですね。
私が長年扱ってきたフレームワークである CakePHP の場合は、データベースにデータ保存するところで、カラム名と送信された内容を 'column' => $this->Post->name のように指定していましたが、Laravelの場合はもう少し楽に対策ができる、と考えればよいと思います。
そもそもの話ですが、Laravelでは fillable を定義しないと create()(新規登録) や update() (更新)を使った操作がエラーとなります。
これは「どのカラムにデータを入れていいかわからない」 ため、Laravelが安全のために操作を拒否している状態だからです。
データの新規登録、更新をする最には必ず定義する必要があるので、忘れすに定義しましょう。
1-8-2. guarded プロパティ
例えば登録するデータのカラムが100個あるようなシステムで fillable に100個のカラム名を指定をするのはあまり現実的ではない場合などには、除外するカラムを指定する形式である guarded プロパティを使用すると幸せになれるかもしれません。
例えば、下記のように除外するカラム「id」を指定すると、指定されたカラム以外を一括で保存・更新可能としてくれます。
指定カラム以外ですので、いわゆるブラックリストとなるわけです。
なるほどですね。
|
1 2 3 4 5 6 7 8 |
class Post extends Model { use HasFactory; protected $guarded = [ 'id' ] } |



