PHP
laravel
laravel5

LaravelでDB上はhasManyだけど1件だけ取りたいときはhasOneを使うといい

More than 1 year has passed since last update.

どういうことか

Userが複数のPostを持っていて、その最新の1件だけ表示したいとき、公式ドキュメントに従うと

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function posts()
    {
        return $this->hasMany('App\Post');
    }
}

とhasManyリレーションを定義して

$user->posts()->orderBy('created_at', 'desc')->first();

と書くのが一般的だと思いますが、最新の1件しか要らないことが予め分かっている場合

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function latestPost()
    {
        return $this->hasOne('App\Post')
            ->orderBy('created_at', 'desc');
    }
}

とhasOneで定義して

$user->latestPost;

と書くこともできます。

ほんとうか

HasManyクラスのソースHasOneクラスのソースを見ると、hasManyでは

Illuminate/Database/Eloquent/Relations/HasMany.php
public function getResults()
{
    return $this->query->get();
}

となっているのに対し、hasOneでは

Illuminate/Database/Eloquent/Relations/HasOne.php
public function getResults()
{
    return $this->query->first() ?: $this->getDefaultFor($this->parent);
}

としているだけですので、Query Builderの使い方としても正しいと言えます。

どうやって使い分けるか

今回のPostsの例でいうと、いずれにせよ一覧表示などを作ると思いますので、hasManyを定義したほうがいいです。
実際の業務で「有効期間内の最新のキャンペーンのみ適用したい」という場面があり、明らかに1件しか使わないのにDB上は古いキャンペーンが残ってしまうので、そのような場合hasOneで定義した方が有効だと思いました。