セブではたらく(インターン)
セブではたらく(インターン)
2019.11.29
#55
バックエンドへの道

Laravelで、Eloquentインスタンスをリフレッシュしたいとき

エリカ

こんにちは、ディレクターのエリカです。

モデルのプロパティを変化させる場合は多々あると思います。そんなときデータベースの値は更新されているものの、その内容の比較などに、意図せず失敗してしまう場面に稀に遭遇してしまいます。たとえば、このようなケースです。

下記のようなテーブルがあったとします。

Schema::create('posts', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('title');
    $table->timestamps();
});
    
Schema::create('comments', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->bigInteger('post_id')->unsigned();
    $table->longText('comment');
    $table->timestamps();
});

対応するModelはこのようになっています。リレーションする Post モデルと、 Comment モデルです。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    public function post()
    {
        return $this->belongsTo(Post::class);
    }
}

そこに、以下のようにデータを作成します。

$post = App\Post::make();
$post->title = 'foo';
$post->save();

$comment = App\Comment::make();
$comment->comment = 'bar';

$post->comments()->save($comment);

この時点で、データベース上には二つのデータが関連づけられて正しく保存されます。
しかし、このまま次のようにしてしまうと、意図しない動作になってしまいます。

if($post->comments->count() === 1)
// 先のデータが正しく登録できているのでこうなるはず……

というのも、この時点では、下記のようになっているからです。

echo $post->comments->count();
// 0

これは、既存のモデルインスタンスと、データベースに保存されている内容に一時的に差異があるためです。たしかに、既存のモデルインスタンスを構築した時点ではこのデータは無かったので、これは当然の結果です。そこで、既存のモデルインスタンスを、データベースに保存されている内容に合わせるためにはどうしたらよいでしょうか?

たとえばプライマリーキーで、もう一度データを取得してしまえば、もちろん問題はありません。

$anotherone = Post::find($post->id);
echo $anotherone->comments->count();
// 1

ただ、再読み込みのような便利な専用メソッドがありそうですよね。

それが、 freshrefresh です。

この二つの使い方を見てみます。

freshの場合

既存のインスタンスには変更を加えず、データベースから取得したデータで新しいインスタンスを返してくれます。

$bitesthedust = $post->fresh();
echo $post->comments->count();
// 0
echo $bitesthedust->comments->count();
// 1

refreshの場合

こちらは、既存インスタンスを再構築します。

echo $post->comments->count();
// 0
$post->refresh();
echo $post->comments->count();
// 1

まとめ

実際は、このような一つのインスタンスを使っていろいろな変更を加えつつ、その中身も利用していくようなコードは、あまり書くことはないかもしれません。しかしテストを書く際にはよくこのようなケースがあったので、もっと綺麗に書ける方法はないかなと調べてみた結果、ちゃんと用意されていました。

Laravel、洗練されていますね。