エンジニ屋.com(エンジニヤドットコム)

分かりやすくを意識して情報発信!

【Laravel】リレーション先のデータ取得・保存など しっかり理解 【検証】


本記事は、リレーション先のデータ取得や保存方法について、検証しながら理解深める内容になっています。

この記事を書く背景

学習者へアドバイスやサポートする立場のお仕事にも携わっているなかで、 このような疑問を持っている状況がありました。


前に、->(アロー演算子)でリレーション先のデータが取得できたのに、->でデータが取得できません
なぜだろう?

原因は
$user->posts();のようにリレーションメソッドのみでデータ取得しようとしていたからでした。
以前にデータを取得していたときは、->postsと括弧を省略したプロパティアクセスを使用していたからです。

なぜposts()のようなリレーションメソッドではだめなのか
どういう用途のときに使うのか

データを出力して検証しながら、しっかりと確認していきたいと思います。

確認のための準備

説明の為にサンプル用として、よく例題に出てくるusersテーブルとpostsテーブルを用意しました。

下のようにデータを格納して準備をしました。

<usersテーブル>

id name email
1 たなか tanaka@example.com
2 もりし enginiya@example.com

<postsテーブル>

id user_id body
1 2 おはよう
2 1 こんにちは
3 2 こんばんは
4 2 おやすみ

usersテーブルとposts テーブルは1対多の関係にあります。

User.phpのモデルファイルにリレーションを定義します。

use App\Models\Post;

class User extends Model
{
    public fuction posts(){
        return $this->hasMany(Post::class)
    }
}

Controllerに下記のようにuseでUser.phpを宣言しています。

use app/Models/User.php;

class TestController extends Controller
{
    public function index(){

        //ここに処理を書いていくとします。

    }
}

準備は整ったので、indexメソッド内に記述して確かめていこうと思います。

プロパティアクセスで取得

$user = User::find(2);

$posts = $user->posts;

dd($posts)

※dd関数とは「dump and die」の略でdump出力した後、そこで処理が終了します。

プロパティアクセスとは、->postsの部分となります。

後ほど解説するので、先ずはdumd出力結果を見ていきます。

Userテーブルのidが2に紐づくpostsテーブル内のデータが、Collectionクラスとして取得できました。

Collectionクラスは、配列items内にModelオブジェクトが入っているので、もしbodyの内容を1つ1つ表示したい場合はforeachを使って下記のように記述できます。

foreach ($posts as $post){
      $body = $post->body ;
      dump($body);
}

dump結果

おはよう
こんばんは
おやすみ

bodyカラムのデータが取得できました。

それでは、例えばwhere()とget()を使用した場合は、リレーション先のデータは取得できるのでしょうか。

User::find()でidを指定していたところを、変更してみます。

$user = User::where('id' , 2 )->get();

$posts = $user->posts;

dd($posts)

答えは、取得出来ません。

下記エラーが出力されます。

Property [posts] does not exist on this collection instance.

「posts というプロパティが存在しません」という意味になります。

これは、get()だとCollectionクラスで返されるので、postsというプロパティが存在しないためです。

find()でデータを取得した場合は、Modelクラスで返させるので、postsというプロパティにアクセス できたことになります。

get()やfind()の違いに関しては、過去の記事で詳しく解説しています。

enginiya.com

それでは、どこの記述でModelクラスにプロパティをセットしたのか?

それは、User.phpでリレーションメソッドとなります。

LaravelのEloquentが自動でリレーションメソッドの括弧を省略した形でプロパティとしてセットされ、リレーション先のテーブルデータを簡単に取得できるようにしてくれます。

確認の為に、リレーションメソッドを下記のように記述してみます。

use App\Models\Post;

class User extends Model
{
    public fuction hoge(){
       return $this->hasMany(Post::class)
}

コントローラーで->postsの部分をhogeにして同じようにデータが取得できます。

$user = User::find(2);

$posts = $user->hoge;

それでは、もし全てのユーザーのポストテーブルのデータを取得したい場合はどうするのか。

下記のようにforeachで回すことで取得ができます。

$users = User::get();

foreach ( $users as $user){
    foreach ( $user->posts as $post){
                dump($post->body);
            }
 }

dump結果

こんにちは
おはよう
こんばんは
おやすみ

ユーザーごとに全てのbodyの値が取得できました。

補足的な話ですが

上記のように関連するpostsテーブルから全ての行を取得するときなどは、with()メソッドを使用してEagerLoadingをしましょう。

EagarLoadingのことを細かく解説すると本題とはずれしてまうので、簡単な説明に留めておくと、データベースへのアクセス回数を減らし、パフォーマンスを向上させることができます。

では、実際にwith()メソッドを使用する場合と使用しない場合のクエリ数を確認していきます。

$users = User::with('posts')->get();

foreach ( $users as $user){
 foreach ( $user->posts as $post){
      dump($post->body);
   }
 }

with()メソッドの引数にリレーションメソッド名を渡します。

他は変わりありません。取得できるデータにも変わりありません。

では、SQLがどのように発行されたかを見ていきます。

with()を使用した場合の発行されたクエリ文

select * from `users`
select * from `posts` where `posts`.`user_id` in (1, 2)

where in が使用されてpostsテーブルからの取得は1クエリとなります。

では、with()を使用しない場合は、どうであったか見ていきます。

select * from `users`
select * from `posts` where `posts`.`user_id` = 1 and `posts`.`user_id` is not null
select * from `posts` where `posts`.`user_id` = 2 and `posts`.`user_id` is not null

ユーザーテーブルのidの数だけクエリが発行されます。

ここでは、ユーザーidが2つしか保存していないので、特に気にする必要はありませんが、 本来はユーザーは100,1000と増えていくので考慮すべき点となります。

因みにクエリ文は、デバッグバーという便利な専用のデバッガーで簡単に確認できるので、まだ使用したことが無い方は、ぜひ調べてみてください。

では、話を戻して冒頭で話があったリレーションメソッドはどういう場合に使用するか見ていきます。

リレーションメソッド

User.phpでリレーション定義しているposts()メソッドを指します。

では、postsプロパティアクセスをposts()メソッドに変更した場合を見ていきます。

$users = User::find(2);
$posts = $users->posts();
dd($posts);

dump出力結果

左を上を見るとEloquentのHasManyクラス(リレーションクラスの1つ)が返されているのが分かります。

foreignKey(外部キー)などposts(リレーションテーブル)の情報や、親モデル(ユーザーテーブル)のデータも確認できます。

ただ、この記述だけではデータの取得はできません。

エラーにもならず何も起こりません。

HasManyクラスは、Modelクラスを継承しているため、Modelクラスのメソッドを使用することもできます。

試しにget() を使用してどうなるか見ていきます。

$users = User::find(2);
$posts = $users->posts()->get();

foreach ($posts as $post){
    $body = $post->body;
    dump($body);
}

dump結果

おはよう
こんばんは
おやすみ

最初に->postsとプロパティアクセスした際と全く同じ結果になりました。

では、どのように使い分けるかというとリレーション先のテーブルに関する操作を行う場合などです。

例えば、saveMany()やcreateMany()など、複数の関連レコードをまとめて保存するためのメソッドがあります。

createMany()を使う場合は、下記となります。

$users = User::find(2);
$data = [
    ['body' => 'hello!'],
    ['body' => 'good!']
 ];
$posts = $users->posts()- >createMany($data);
dd($posts);

createManyの引数には二次元配列を渡します。

postsテーブルを確認します。

<postsテーブル>

id user_id body
1 1 おはよう
2 2 こんにちは
3 1 こんばんは
4 1 おやすみ
5 2 hello!
6 2 good!

user_idが2として保存されています。

因みに$postsの返却値を見ると下記のように保存されたレコードがCollectionで取得できます。

もし保存された情報を表示させる必要があるときなどは、返却値を使用できますね。

以上となります。

プロパティアクセスとリレーションメソッドについて理解が深められたのであれば幸いです。

リレーションクラスには他にもメソッドがあるので必要に応じて調べる必要がありますが、基礎を理解して抑えておくことが、とても大事な部分だなと感じました!

お疲れ様でした!

最新の記事を公開する際には、X(旧Twitter)でもお知らせしています。
ご興味のある方は、ぜひフォローをお願いいたします!