【Laravel 入門】「Eloquent」を利用したデータベースの操作やテーブルの結合

Laravel

DBクラスを使用してデータベースにアクセスすることができますが、
レコードの値を連想配列としてまとめたものが更に配列やコレクションとしてまとめられており、
あまり便利な形で得られるわけではありません。


そこで、Laravel に内蔵されている「Eloquent」という ORM を使うと、PHPのオブジェクトのように扱うことができるようになります。



今回は、「Eloquent」の使い方について紹介していきます。


参考書

ORMとは

ORMとは、「Object-Relationa Mapping」の略であり、互換性のないデータを自動的に変換して相互にデータをやり取りできるようにするための橋渡しのような機能です。


データベースのテーブルやレコードは、PHPのクラスなどと構造そのものが異なっていますが、
「Eloquent」という ORM を使うことで、データベースから取り出したレコードをPHPのオブジェクトに変換して渡したり、反対にPHPからデータベースに渡すときは、PHPオブジェクトをレコードに変換して渡すことができます。


「Eloquent」は、「モデル」というテーブルの内容を定義したクラスを利用してデータベースを操作するように設計されています。

基本的な使い方

モデルの作成

今回は、すでに「people」というテーブルと、以下のようなレコードを用意しています。


では、people テーブルを操作する「Person」モデルを作成します。
コマンドラインで以下を実行します。

php artisan make:model Person


すると、「app/Models」の中に「Person.php」が作成されていますが、とりあえず今は何も触りません。

ポイント

Laravel では「テーブル名は複数形、モデル名は単数形」という命名規則があります。
これに従うことで、テーブルとモデルを自動的に関連付けて動作するようになっています。



ではテンプレートを作成します。
「views/person/index.blade.php」ファイルをつくり、そこに以下のように記述します。

@section('content')
    <table>
        <tr><th>id</th><th>名前</th><th>メールアドレス</th><th>年齢</th></tr>
        @foreach ($people as $person)
            <tr><td>{{$person->id}}</td><td>{{$person->name}}</td><td>{{$person->mail}}</td><td>{{$person->age}}</td></tr>
        @endforeach
    </table>
@endsection



では続いてコントローラを作成します。コマンドラインで以下を実行します。

php artisan make:controller PersonController


「app/Http/Controllers」の中の「PersonController.php」を開き、以下のように記述します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Person;

class PersonController extends Controller
{
    public function index(Request $request)
    {
        $people = Person::all();
        return view('person.index', ['people' => $people]);
    }
}


この場合は、モデル名::all() で全レコードを取得しています。
取得されたレコードは「illuminate\Database\Eloquent 名前空間」にある Collection クラスのインスタンスになります。


最後に「web.php」にルート情報を記述します。

Route::get('/person', 'App\Http\Controllers\PersonController@index');


ブラウザで「/person」にアクセスすると、無事に表示されました。

モデルにメソッドを追加

ひとつひとつのレコードはモデルのインスタンスとしてまとめられているため、プロパティやメソッドを追加することが可能です。


今回は、先ほど作った「Person.php」の Person クラスに「getData」メソッドを追加してみます。

public function getData()
{
    return $this->id . ': ' . $this->name . ' [' . $this->mail . '] ' . '(' . $this->age . ')';
}


テンプレートを以下のように修正します。

@section('content')
    <table>
        @foreach ($people as $person)
            <tr><td>{{$person->getData()}}</td></tr>
        @endforeach
    </table>
@endsection


ブラウザには以下のように表示され、無事に getData メソッドが呼び出されていることが確認できました。

id で検索

find メソッドを使うことで、「id」フィールドから指定のレコードを検索ことができます。

つまり、プライマリーキーは「id」という名前で整数値が入るフィールドであるという前提のもと実行されます。

モデルクラス ::find( 整数 );




では実際に使ってみます。

今回は、「/find」という検索用のページを用意します。
「views/person/find.blade.php」ファイルを作り、以下のように記述します。

@section('content')
    <form action="/person/find" method="post">
    @csrf
        <input type="text" name="input" value="{{$input}}">
        <input type="submit" value="検索">
    </form>
    @if(isset($target))
    <table>
        <tr>
            <td>{{$target->getData()}}</td>
        </tr>
    </table>
    @endif
@endsection


isset( 変数 ) は、引数が定義されていれば true を返します。



では続いてコントローラを編集します。

GETアクセスしたとき用の「find」メソッドと、
POST送信されたとき用の「search」メソッドを用意します。

public function find(Request $request)
{
    return view('person.find', ['input' => '']);
}

public function search(Request $request)
{   
    $input = $request->input;
    $target = Person::find($input);
    $params = ['input' => $input, 'target' => $target];
    return view('person.find', $params);
}


上記の中で、

$target = Person::find($input);

この部分が id の検索をおこない、$target に対象のレコードを入れています。


最後に「web.php」にルート情報を記述します。

Route::get('/person/find', 'App\Http\Controllers\PersonController@find');
Route::post('/person/find', 'App\Http\Controllers\PersonController@search');


ブラウザでの挙動↓

スコープで検索

レコードを id 以外の条件で絞り込みたいとき、where メソッドを使います。
この where は、「illluminate\Database\Eloquent 名前空間」にある Builder クラスのインスタンスを返します。

//複数のレコードを取得する場合
モデルクラス ::where( フィールド名 , 値 )->get();

//最初のレコードだけを取得する場合
モデルクラス ::where( フィールド名 , 値 )->first();


「 “フィールド名” が “値” であるレコードを取得する」という使い方です。


複数の条件で絞る場合は where をつなぎ合わせることで実装できますが、非常にわかりにくくなってしまいますね。

そのようなときは「スコープ」を使うと、複数の条件でもわかりやすく設定することができます。

ローカルスコープ

これは、条件で絞り込むメソッドをモデル内に用意しておき、必要に応じてメソッドを呼び出して絞り込むというものです。

public function scope名前($query, 引数)
{   
    ...処理...
    return 絞り込んだビルダ;
}


スコープを定義するためのメソッド名は、必ず頭に「scope」をつけ、その後に大文字で始まる名前を命名します。

第1引数に渡されている $query には、where と同じく Builder クラスのインスタンスが渡されます。

つまりこのメソッドは、レコードを絞り込む処理をして得られたビルダを返すという形になります。



では実際に使ってみましょう。
Person モデルクラスに以下のような2つのスコープを追加します。

class Person extends Model
{
    public function getData()
    {
        return $this->id . ': ' . $this->name . ' [' . $this->mail . '] ' . '(' . $this->age . ')';
    }

    public function scopeOlderThan($query, $min)
    {   
        return $query->where('age', '>=', $min);
    }

    public function scopeYoungerThan($query, $max)
    {   
        return $query->where('age', '<=', $max);
    }
}


それぞれ、引数に指定した年齢以上・以下のビルダを返しています。


では続いてコントローラの find メソッドと search メソッドを編集します。

public function find(Request $request)
{
    return view('person.find', ['min' => '', 'max' => '']);
}

public function search(Request $request)
{   
    $min = $request->min;
    $max = $request->max;
    $targets = Person::olderThan($min)->
                       youngerThan($max)->get();
    $params = ['min' => $min, 'max' => $max, 'targets' => $targets];
    return view('person.find', $params);
}


上記の記述の中で、以下の部分に注目してください。

$targets = Person::olderThan($min)->
                   youngerThan($max)->get();


このように、スコープ用に用意したメソッドを呼び出す際は、メソッド名の最初の scope は不要になります。


あとは、テンプレートを少し修正します。

@section('content')
    <form action="/person/find" method="post">
    @csrf
        <input type="text" name="min" value="{{$min}}">歳以上
        <input type="text" name="max" value="{{$max}}">歳以下
        <input type="submit" value="検索">
    </form>
    @if(isset($targets))
    <table>
        <tr><th>id</th><th>名前</th><th>メールアドレス</th><th>年齢</th></tr>
        @foreach ($targets as $target)
            <tr><td>{{$target->id}}</td><td>{{$target->name}}</td><td>{{$target->mail}}</td><td>{{$target->age}}</td></tr>
        @endforeach
    </table>
    @endif
@endsection


今回は、「20歳以上30歳以下」で絞り込みました。
ブラウザでの挙動↓

グローバルスコープ

先ほどつくったローカルスコープは、「条件で絞り込むメソッドを必要に応じて呼び出して利用する」というものでしたが、
グローバルスコープは、そのモデルでのすべてのレコード取得にそのスコープが適用されるというものになります。

boot メソッド

グローバルスコープを適用するには、モデルクラスに boot メソッドを利用します。
これは、モデルの初期化専用のメソッドになります。

モデルが作成される際の初期化処理として、グローバルスコープを組み込みます。

protected static function boot()
{
    parent::boot();
  
  static::addGlobalScope('スコープ名', function(Builder $builder) {
      ...絞り込み処理...
   });
}


ご覧のように、静的メソッドとして用意します。

addGlobalScope はグローバルメソッドを追加するメソッドです。Builder を使って絞り込み処理を作成します。



では実際に作っていましょう。
今回は、40歳以上で絞り込むグローバルスコープを追加します。「Person.php」を以下のように書きます。

use Illuminate\Database\Eloquent\Builder;//追加
protected static function boot()
{
    parent::boot();
  
  static::addGlobalScope('olderThan40', function(Builder $builder) {
      $builder->where('age', '>', 40);
   });
}


ではブラウザで「/person」にアクセスします。
グローバルスコープを追加していなければ、以下のようにすべてのレコードが表示されます。


グローバルスコープを追加してから再読み込みすると、

絞り込みができていることが確認できました。

Scopeクラス

複数のモデルで使いまわすような汎用性の高いグローバルスコープは、「Scope クラス」として作成しておくと便利です。

このクラスを書くファイルの配置場所は特に決まりはありませんが、
今回は「app」フォルダの中に「Scopes」というフォルダを作り、「olderThan30.php」というファイルを作ります。
(30歳以上で絞り込むスコープを作成します)

以下のような形式になります。

class クラス名 implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        ...絞り込み処理...
    }
}


apply メソッドに Builder と Model のインスタントを引数として渡し、絞り込み処理を行います。

引数に Model を渡して処理を行うため、特定のモデルに囚われず汎用的な処理を行うスコープを作ることができます。


今回は以下のように記述します。

<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class olderThan30 implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('age', '>=', 30);
    }
}


そして、「Person.php」を以下のように修正します。

use App\Scopes\olderThan30;//追加
protected static function boot()
{
    parent::boot();

    static::addGlobalScope(new olderThan30);
}


ブラウザで「/person」にアクセスすると、絞り込みができていることが確認できます。

インスタンスの追加・更新・削除

インスタンスの追加

「Eloquent」で新たにレコードを追加したいときは、モデルのインスタンスを作成し保存するというかたちになります。


では実際につくってみます。
Person クラスを以下のように修正します。

class Person extends Model
{   
    protected $guarded = array('id');
}


$guarded は、「値を用意しておかない項目」に指定することで、値が null であってもエラーにならないようにするために使用します。

例えばフォームから送信した値をもとにインスタンスをつくって保存する場合、
モデルでは基本的に必要なすべての項目に値が揃っていれば保存ができるようになりますが、
「id」のような「値を用意しておかない項目」もあります。

そのようなときに、$guarded でその項目を指定しておくと、エラーにならずに済むというわけです。



次にテンプレートをつくります。
「views/person」の中に「add.blade.php」というファイルをつくり、以下のように記述します。

@section('content')
    <form action="/person/add" method="post">
        <table>
            @csrf
            <tr><th>名前:</th><td><input type="text" name="name"></td></tr>
            <tr><th>メールアドレス:</th><td><input type="text" name="mail"></td></tr>
            <tr><th>年齢:</th><td><input type="text" name="age"></td></tr>
            <tr><th></th><td><input type="submit" value="送信"></td></tr>
        </table>
    </form>
@endsection


そして、コントローラにアクションを2つ追加します。

public function add(Request $request)
{
    return view('person.add');
}

public function create(Request $request)
{
    $person = new Person;
    $allInputs = $request->all();
    unset($allInputs['__token']);
    $person->fill($allInputs)->save();
    return redirect('/person');
}


まず、

$person = new Person;

とすることで、Person インスタンスを作成します。

続いて、

$allInputs = $request->all();
unset($allInputs['_token']);

フォームの値をまとめた $allInputs から、「_token」という非常時フィールドを unset で削除しています。

この「_token」という値は、CSRF用非表示フィールドとして用意される項目であり、テーブルにはないフィールドです。こうしたものは削除しておきます。


そして、

$person->fill($allInputs)->save();

fill メソッドは、引数に用意されている配列の値をモデルのプロパティに代入するものです。
そして、save メソッドでインスタンスを保存します。



最後に、「web.php」にルート情報を追加します。

Route::get('/person/add', 'App\Http\Controllers\PersonController@add');
Route::post('/person/add', 'App\Http\Controllers\PersonController@create');


ではブラウザで挙動を確認しましょう。
元々は、id が1から10までのレコードを持っていましたが、11番目が新たに追加されます。

インスタンスの更新

ではテンプレートをつくります。
「views/person」の中に「edit.blade.php」というファイルをつくり、以下のように記述します。

@section('content')
    <form action="/person/edit" method="post">
        <table>
            @csrf
            <input type="hidden" name="id" value="{{$target->id}}">
            <tr><th>名前:</th><td><input type="text" name="name" value="{{$target->name}}"></td></tr>
            <tr><th>メールアドレス:</th><td><input type="text" name="mail" value="{{$target->mail}}"></td></tr>
            <tr><th>年齢:</th><td><input type="text" name="age" value="{{$target->age}}"></td></tr>
            <tr><th></th><td><input type="submit" value="送信"></td></tr>
        </table>
    </form>
@endsection


そして、コントローラにアクションを2つ追加します。

public function edit(Request $request)
{   
    $target = Person::find($request->id);
    return view('person.edit', ['target'=>$target]);
}

public function update(Request $request)
{
    $target = Person::find($request->id);
    $allInputs = $request->all();
    unset($allInputs['_token']);
    $target->fill($allInputs)->save();
    return redirect('/person');
}

まず、

$target = Person::find($request->id);

で、更新対象となるインスタンスを取得します。

その後の処理は、さきほどの「追加」の処理のときと変わらないですね。


あとは、「web.php」にルート情報を追加します。

Route::get('/person/edit', 'App\Http\Controllers\PersonController@edit');
Route::post('/person/edit', 'App\Http\Controllers\PersonController@update');



では今回は、id=3 のレコードの年齢を変更してみます。

ブラウザで「/person/edit?id=3」にアクセスし以下のように変更すると、無事更新していることが確認できました。

インスタンスの削除

ではテンプレートをつくります。
「views/person」の中に「del.blade.php」というファイルをつくり、以下のように記述します。

@section('content')
    <form action="/person/del" method="post">
        <table>
            @csrf
            <input type="hidden" name="id" value="{{$target->id}}">
            <tr><th>名前:</th><td>{{$target->name}}</td></tr>
            <tr><th>メールアドレス:</th><td>{{$target->mail}}</td></tr>
            <tr><th>年齢:</th><td>{{$target->age}}</td></tr>
            <tr><th></th><td><input type="submit" value="送信"></td></tr>
        </table>
    </form>
@endsection


そして、コントローラにアクションを2つ追加します。

public function del(Request $request)
{   
    $target = Person::find($request->id);
    return view('person.del', ['target'=>$target]);
}

public function remove(Request $request)
{
    Person::find($request->id)->delete();
    return redirect('/person');
}


インスタンスの削除は非常にシンプルです。
以下のように書くだけですね。

Person::find($request->id)->delete();


あとは、「web.php」にルート情報を追加します。

Route::get('/person/del', 'App\Http\Controllers\PersonController@del');
Route::post('/person/del', 'App\Http\Controllers\PersonController@remove');


では今回は、id=3 のレコードを削除してみます。

ブラウザで「/person/del?id=3」にアクセスし「送信」を押すと、無事削除できていることが確認できました。

モデルのリレーション

リレーションとは、複数のテーブルの関連付けをいいます。

今回は、これまでに作った「people テーブル」と、新しく「posts テーブル」(メッセージの投稿を集めたもの) を作り、このふたつを関連付けてみます。

Posts テーブルの作成

「posts テーブル」には、以下のようなフィールドを用意しておきます。

id投稿ID
person_id投稿者のID(people テーブルのID)
message投稿内容
created_at作成日時
updated_at更新日時


下2つの日時に関しては、マイグレーションを使ってテーブルを作成するときに自動的に生成されます。


では、まずはマイグレーションファイルを作ります。
コマンドラインで以下を実行します。

php artisan make:migration create_posts_table


これで、「database/migrations」の中に生成したファイルが配置されるため、それを開いて以下のように記述します。

<?php

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

class CreatePostsTable extends Migration
{
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('person_id');
            $table->string('message');
            $table->timestamps();
        });
    }

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


$table->timestamps(); とすることで、created_atupdated_at フィールドの追加と値の保存を Laravel が自動的に行います。



では、シーディング処理(初期状態で入っているレコードの登録)もしておきます。
以下のコマンドを実行し、シーダーファイルを作成します。

php artisan make:seeder PostsTableSeeder


「database/seeders」の中に配置されるので、開いて以下を記述します。

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;//追加

class PostsTableSeeder extends Seeder
{
    public function run()
    {
        $params = [
            'person_id' => '1',
            'message' => 'おはよう',
        ];
        DB::table('posts')->insert($params);

        $params = [
            'person_id' => '3',
            'message' => 'こんにちは',
        ];
        DB::table('posts')->insert($params);

        $params = [
            'person_id' => '5',
            'message' => 'さようなら',
        ];
        DB::table('posts')->insert($params);

        $params = [
            'person_id' => '2',
            'message' => 'おやすみ',
        ];
        DB::table('posts')->insert($params);

        $params = [
            'person_id' => '4',
            'message' => 'ありがとう',
        ];
        DB::table('posts')->insert($params);

        $params = [
            'person_id' => '1',
            'message' => '十円拾った',
        ];
        DB::table('posts')->insert($params);

        $params = [
            'person_id' => '3',
            'message' => '未来から来ました',
        ];
        DB::table('posts')->insert($params);

        $params = [
            'person_id' => '5',
            'message' => 'お腹空いた',
        ];
        DB::table('posts')->insert($params);

        $params = [
            'person_id' => '2',
            'message' => 'にんじん買う',
        ];
        DB::table('posts')->insert($params);
    }
}


そして、「database/seeders」の中の「DatabaseSeeder.php」に以下のように書いてシーディング処理を登録しましょう。

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this->call(PeopleTableSeeder::class);
        $this->call(PostsTableSeeder::class);//追加
    }
}


では、コマンドラインでマイグレーションを実行します。

php artisan migrate


そして、シーディングも実行します。

php artisan db:seed


これでデータベースの設定が終わりました。


では続いて Post モデルを作成します。
コマンドラインで以下を実行します。

php artisan make:model Post


「app/Models」の中に「Post.php」が作成されるのでそれを開き、以下のように記述します。

<?php

namespace App\Models;

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

class Post extends Model
{
    protected $guarded = array('id');
}


Person モデルのときと同様、$guarded で id を「値を用意しておかない項目」として設定しエラーを回避しています。



次は、テンプレートをつくります。
「view」内に「post」というフォルダを用意し、その中に「index.blade.php」というファイルをつくりましょう。

では、以下のように記述します。

@section('content')
    <table>
        <tr><th>投稿ID</th><th>投稿者のID</th><th>投稿内容</th></tr>
        @foreach ($posts as $post)
            <tr><td>{{$post->id}}</td><td>{{$post->person_id}}</td><td>{{$post->message}}</td></tr>
        @endforeach
    </table>
@endsection


では続いてコントローラを作成します。
以下のコマンドを実行しましょう。

php artisan make:controller PostController


「app/Http/Controllers」の中に作成した「PostController.php」が配置されるので、それを開き以下のように記述します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Post;//追加

class PostController extends Controller
{
    public function index(Request $request)
    {
        $posts = Post::all();
        return view('post.index', ['posts'=>$posts]);
    }
}


最後にルート情報を追加します。

Route::get('/post', 'App\Http\Controllers\PostController@index');


ブラウザで「/post」にアクセスすると、以下のような表示になります。

主テーブルと従テーブル

今回作った2つのテーブルの関係性を考えると、
「people テーブル」が「主テーブル」、
「posts テーブル」が「従テーブル」(主テーブルに準じるテーブル)となります。


この2つを関連付けるには、従テーブルに「外部キー」を持たせる必要があります。

外部キーとは、関連するもうひとつのテーブルの id を保管するフィールドであり、今回でいうと「person_id」がそれにあたりますね。


この外部キーを使って主テーブルと従テーブルを紐づけることができます。

テーブルの結合

では実際に people テーブルと posts テーブルを結合させてみます。

1対1の関係の場合

2つのテーブルが「1対1」の関係で関連付けられており、「主テーブルから従テーブルを取得する」際のやり方をみていきます。

この場合は、主テーブル側(people テーブル)を編集します。


まずは Person クラスに以下のメソッドを追加します。

public function post()
{
    return $this->hasOne('App\Models\Post');
}


メソッド名は、関連付けるモデル名と同じ名前をつけ、
hasOne メソッドは、引数に指定したモデルへの関連付けを行います。

これにより、Person インスタンスから、関連付けられた posts tテーブルのレコードを取得できるようになります。



では続いて、「views/person/index.blade.php」のテンプレートを修正します。

@section('content')
    <table>
        <tr><th>名前</th><th>投稿</th></tr>
        @foreach ($people as $person)
            <tr>
                <td>{{$person->name}}</td>
                <td>
                    @if($person->post != null)
                        {{$person->post->message}}
                    @endif
                </td>
            </tr>
        @endforeach
    </table>
@endsection


先ほど Person クラスに post メソッドを追加したため、本来ならば「$person->post()」のように書かないといけないように感じますが、
リレーションの設定を行ったため、「$person->post」のようにプロパティとして扱うことができます。


ではブラウザで「/person」にアクセスします。


すると、それぞれの Person に紐づいた Post が表示されています。
(ただ、今回は hasOne メソッドを使ったため、ひとつずつしか投稿を取得していません)

1対多の関係の場合

今回の例のように、一人のユーザーに対し複数の投稿をもつというような「1対多」の関係における、「主テーブルから従テーブルを取得する」際のやり方をみていきます。

先ほどと同様、主テーブル側(people テーブル)を編集します。


まずは Person クラスの post メソッドを以下のメソッドに変更します。

public function posts()
{
    return $this->hasMany('App\Models\Post');
}


hasMany メソッドを使用することで、複数のレコードと関連付けることができます。


では続いて、「views/person/index.blade.php」のテンプレートを修正します。

@section('content')
    <table border="1" style="border-collapse: collapse">
        <tr><th>名前</th><th>投稿</th></tr>
        @foreach ($people as $person)
            <tr>
                <td>{{$person->name}}</td>
                <td>
                    @if($person->posts != null)
                        <table>
                        @foreach ($person->posts as $post)
                            <tr><td>{{$post->message}}</td></tr>
                        @endforeach
                        </table>
                    @endif
                </td>
            </tr>
        @endforeach
    </table>
@endsection


ブラウザで「/person」にアクセスすると、関連づいた投稿がすべて表示されているのが確認できます。

従テーブルから主テーブルを取得

hasOne や hasMany は「主→従」という方向でデータを取り出していましたが、
逆に「従→主」でデータを取り出す場合は「belongsTo」メソッドを使用します。

この場合は、従テーブル側(postテーブル)を編集します。


まずは Post クラスに以下のメソッドを追加します。

public function person()
{
    return $this->belongsTo('App\Models\Person');
}


そして、「views/post/index.blade.php」のテンプレートを修正します。

@section('content')
    <table>
        <tr><th>投稿内容</th></tr>
        @foreach ($posts as $post)
            <tr><td>{{$post->message}} ({{$post->person->name}})</td></tr>
        @endforeach
    </table>
@endsection


ブラウザで「/post」にアクセスすると、主テーブルの関連づいたレコードの名前を取得できているのが確認できます。

「N+1問題」の解消

これまでのやり方は、実はデータベースとの処理という観点から見ると少し無駄が多くなっています。

例えば今回の場合、全投稿が9件あるため、まず Post 全体を取得するのに1回、それからそれぞれの Post に関連する Person を取得するのに9回、計10もデータベースにアクセスすることになり、
これを一般的に「N+1問題」といいます。


この問題を解消するには、PostController クラスを以下のように修正します。

public function index(Request $request)
{
    $posts = Post::with('person')->get();
    return view('post.index', ['posts'=>$posts]);
}


with メソッドを使うと、以下のような処理になります。
①Post だけを取得
②Post の person_id の値をまとめ、それらの id のPerson を取得


このように、たった2回のアクセスに抑えることができ、大幅なアクセス減を実現することができます。
(ブラウザでの表示はさきほどと同様です)

関連レコードの「持つ・持たない」を分ける

ここでは、投稿をもつ人と持たない人を区別して表示してみます。

PersonController クラスの index アクションを以下のように編集します。

public function index(Request $request)
{
    $hasPosts = Person::has('posts')->get();
    $hasNoPosts = Person::doesntHave('posts')->get();
    $params = ['hasPosts' => $hasPosts, 'hasNoPosts' => $hasNoPosts];
    return view('person.index', $params);
}


上記の中の、has メソッドdoesntHave メソッドは以下のように使います。

//指定のリレーションの値を持つビルダを取得
モデル::has('リレーション名')->get();

//指定のリレーションの値を持たないビルダを取得
モデル::doesntHave('リレーション名')->get();



続いて「views/person/index.blade.php」のテンプレートを修正します。

@section('content')
    <table border="1" style="border-collapse: collapse">
        <tr><th>名前</th><th>投稿</th></tr>
        @foreach ($hasPosts as $person)
            <tr>
                <td>{{$person->name}}</td>
                <td>
                    @if($person->posts != null)
                        <table>
                        @foreach ($person->posts as $post)
                            <tr><td>{{$post->message}}</td></tr>
                        @endforeach
                        </table>
                    @endif
                </td>
            </tr>
        @endforeach
    </table>
    <br>
    <table border="1" style="border-collapse: collapse">
        <tr><th>名前(未投稿)</th></tr>
        @foreach ($hasNoPosts as $person)
            <tr>
                <td>{{$person->name}}</td>
            </tr>
        @endforeach
    </table>
@endsection


ブラウザで「/person」にアクセスすると、投稿を持つ人と持たない人を区別して管理できていることが確認できます。





今回は以上になります。
ご覧いただきありがとうございました(^^)


参考書



続きはこちら↓

コメント

コンタクトフォーム

    タイトルとURLをコピーしました