Charry
@Charry

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Laravel リレーションでの値を取り出す時の記述と動作について

制作環境

Windows 10
Laravel : 6.18.35
Laravel/ui : 1.0
Laravel-mix : 5.0.1
Bootstrap : 4.0.0
MDBootstrap : 4.19.1
chart.js : 2.9.3
XAMPP
PHP : 7.4.3
Visual Studio Code

質問理由

Laravelを勉強中の初心者です。
リレーションを学ぶ為、簡易的なプログラムを作成しているのですが、動作と記述で疑問に思ったことがあるので質問をさせていただきます。

プログラムの内容

プログラムの内容は以下の通りで、あまり使用されない内容だとは思いますが、1対1のリレーションを想定してます。
動作確認が主な目的の為、デザイン等は考慮してません。

マイグレーションファイル

Items テーブルと Stocks テーブルを作成します。

create_items_table
    public function up()
    {
        if (!Schema::hasTable('items')) {
            Schema::create('items', function (Blueprint $table) {
                $table->bigIncrements('id');
                $table->string('name');
                $table->timestamps();
            });
        }
    }
create_stocks_table
    public function up()
    {
        if (!Schema::hasTable('stocks')) {
            Schema::create('stocks', function (Blueprint $table) {
                $table->bigIncrements('id');
                $table->unsignedBigInteger('item_id');
                $table->foreign('item_id')->references('id')->on('items')->onDelete('cascade');
                $table->string('stock');
                $table->timestamps();
            });
        }
    }

モデル

Item.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

use Illuminate\Database\Eloquent\Relations\HasOne;

class Item extends Model
{
    protected $fillable = ['name'];

    public function stock(): HasOne
    {
        return $this->hasOne('App\Models\Stock');
    }
}
Stock.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Stock extends Model
{
    protected $fillable = ['stock'];

    protected $primaryKey = 'item_id';

    public function item(): BelongsTo
    {
        return $this->belongsTo('App\Models\Item')->withTimestamps();
    }
}

コントローラ

ItemController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Models\Item;

class ItemController extends Controller
{
    public function index()
    {
        $data = Item::with('stock')->get();
        return view('item', compact('data'));
    }
}

ルーティング

web.php
Route::get('/item', 'ItemController@index');

ビュー

item.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>リレーション</title>
</head>
<body>
    <table>
        <thead>
            <tr>
                <th>商品名</th>
                <th>在庫数</th>
            </tr>
        </thead>

        <tbody>
            @foreach ($data as $item)
            <tr>
                <td>{{ $item->name }}</td>
                <td>{{ $item->stock->stock }}</td>
            </tr>
            @endforeach
        </tbody>
    </table>
</body>
</html>

実際の表示

index.jpg

質問内容

質問したいのは、ビューの記述と動作についてです。
上では、値いを取り出すのに

item.blade.php
@foreach ($data as $item)
<tr>
    <td>{{ $item->name }}</td>
    <td>{{ $item->stock->stock }}</td>
</tr>
@endforeach

と記述してますが、以下の記述でも同様に値を取り出すことができます。

item.blade.php
@foreach ($data as $item)
<tr>
    <td>{{ $item['name'] }}</td>
    <td>{{ $item['stock']['stock'] }}</td>
</tr>
@endforeach

どちらの記述がよいのでしょうか?

また、$item->stock->stockはstockメソッドで、改めてリレーション先の値を取得しているという動作の認識なのですが、あってますでしょうか?

疑問に感じた理由

$item->stock->stockはstockメソッドで、改めてリレーション先の値を取得しているという認識でいるのですが、その場合このメソッドを使用するのは無駄ではないかと感じたからです。

dd($item) を使用し、値を確認したところ、

r2.jpg

attributesとoriginalにリレーション元の値、

r3.jpg

relationsにリレーション先の値があるのがわかります。
これは、コントローラで$data = Item::with('stock')->get()とし、両方の値を取得しているからだと思います。

そもそもコントローラで取得しているなら、改めてstock()メソッドで呼び出さなくてもいいのではないかと感じました。
この場合のstock()メソッドは改めて情報を取得しているのではなく、relationsから値を取得しているのでしょうか?

もし改めて取得しているなら、$item['stock']['stock']の方が処理が減るような気がします。

ご教授いただけたら幸いです。

0

3Answer

正確な事はわからないので、自分のなかのイメージになってしまいますが、、、


@foreach ($data as $item)
<tr>
    <td>{{ $item->name }}</td>
    <td>{{ $item->stock->stock }}</td>
</tr>
@endforeach

$item->stock でitemモデルオブジェクトのリレーションメソッドstock()が実行されます。
通常だと、その際に以下の様なSQLが実行され、検索結果をstockオブジェクトに格納して返却されます。(なので、ループの間、毎回SQLが実行されます)

SELECT * FROM stocks WHERE item_id = ? -- $item->idが入る

ですが、今回の場合は、おっしゃる通りコントローラ側で Item::with('stock')->get()をやって事前にstocksテーブルのデータは取得できているので、ループ中にSQLは実行されません。

$data = Item::with('stock')->get(); で、 $dataにはitemsテーブルのレコード数分のitemオブジェクト配列が入っています。
そして、それぞれのitemオブジェクトのメンバー変数に(リレーションされたデータを持っている)stockオブジェクトを持っている感じだと思います。

$item->stock->stockはstockメソッドで、改めてリレーション先の値を取得しているという動作の認識なのですが

なので、改めて取得(SQLの実行)はしていないと思います。
<td>{{ $item->stock->stock }}</td> では、

  1. $item->stock でitemオブジェクトからstockオブジェクトを取得して、
  2. stock->stock でstockオブジェクトが保持しているstockカラムの値を取得している

という感じだと思います。

どちらの記述がよいのでしょうか?

個人的には、 $item['stock']['stock'] よりも $item->stock->stock の方がlaravelっぽくて好きです。

0Like

youstr様

ご回答ありがとうございます。
その後1対多のリレーションを試してみたのですが、1対多の場合だと$item['stock']['stock']という記述だとうまく値が取り出せませんでした。

御説明いただいた通りsqlが行われていないようなら、laravelっぽいし、かつ1対1でも1対多でも対応できるので、$item->stock->stockの記述にしようと思います。

丁寧なご回答ありがとうございました。

0Like

@Charry

ご返信ありがとうございます。

その後1対多のリレーションを試してみたのですが、1対多の場合だと$item['stock']['stock']という記述だとうまく値が取り出せませんでした。

1対多の場合、$item->stock->stock でも値は取り出せないのではと思います。

というのも、1対多(hasMany)の場合、1つのitemレコードに対して複数のstockレコードが紐づくので、コントローラで $data = Item::with('stock')->get(); をやった場合、blade側の $item->stockで取得できるのは stockオブジェクトではなく、Illuminate\Database\Eloquent\Collectionオブジェクトになると思います。

なので、やるならば、

@foreach ($data as $item)
<tr>
    <td>{{ $item->name }}</td>
    <td>
        @foreach ($item->stock as $stock)
             {{ $stock->stock }}<br>
        @endforeach
    </td>
</tr>
@endforeach

みたいになると思います。

上記の記述は、全て実際には試していないので、間違っていたらすみません。

0Like

Comments

  1. @Charry

    Questioner

    丁寧なご回答ありがとうございます。
    試してみます。

Your answer might help someone💌