開発ブログ

株式会社Nextatのスタッフがお送りする技術コラムメインのブログ。

電話でのお問合わせ 075-744-6842 ([月]-[金] 10:00〜17:00)

  1. top >
  2. 開発ブログ >
  3. PHP >
  4. Laravel >
  5. 【Laravel】空のリレーションを手軽に設定する方法
no-image

【Laravel】空のリレーションを手軽に設定する方法

こんにちは、スズキです。

今回は空のリレーションを手軽に設定する方法を紹介します。
 

前振り


以前、このような記事を書いたのを覚えている方はいますでしょうか。
上記記事では、「逆方向のリレーションを動的に設定する方法」を紹介していますが、
そもそもの目的は「ぬるぽエラーを回避するために、空のリレーションを設定したい」というものです。

つまり、以下のようなアクセスを行った際に、
このままでは「$parent->child」がNULLの場合、「$parent->child->name」にアクセスしようとするとエラーが発生してしまいます。
$parent = Parent::query()->first();
$childName = $parent->child->name;

そこで、
// Parent情報取得
$parent = Parent::query()->first();

// Childのレコード情報が登録されていない場合
if(!$parent->child)
{
    // 空のChild情報を設定
    $emptyChild = new Child();
    $parent->setRelation('child', $emptyChild);
}

// レコード情報が無い場合には空のリレーションを設定しているので、
// 本来は「$parent->child === null」だったとしても、
// 以下の処理の際にエラーにならない
$childName = $parent->child->name;
のように空のリレーションを設定する処理を行うことで、「$parent->child」がNULLの場合でも、「$parent->child->name」にアクセスしてもエラーにならないようにする、
というのが前回記事の本来の目的だったわけです。



また、先日開発していたときの話なのですが、
(上記例ではHasOne系のリレーションの話をしましたが、)
HasMany系のリレーションに対し、
$childId = 999;

$query = Parent::query();
$query->with([
    'children' => function ($query) use ($childId) {
        $query->where('children.id', '=', $childId);
    },
]);
$parent = $query->first();
$childName = $parent->children->first()->name;
というように、with句を設定してchildrenを1件のみに絞った後に、
$parent->children->first()->name;
という形で最初の1件のみ利用する、というアクセスの仕方をすることになりました。

上記の場合、「$parent->children->first()->name;」とアクセスすると、childrenが0件の場合にエラーになってしまいます。
そのため、HasOne系のリレーションのときと同様に、
「childrenが0件の場合は空のChildモデルを$parent->childrenに追加する」
という処理が必要だな〜となったわけです。

で、このあたりの空のリレーションを設定する処理を簡単に1行で記述できるようなメソッドを作ったのですが、
今回はこのメソッドを紹介します。
 

本題


はい、というわけで、作成した空リレーション設定用の処理は以下になります。

空リレーション設定用のTrait
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;

trait SetRelationTrait
{
    /**
     * 空のリレーションを設定します。(HasOne系リレーション用)
     * @param $relationName
     * @param null $reverseRelationName
     */
    public function setHasOneDummyRelation($relationName, $reverseRelationName = null)
    {
        /**
         * @var Model $emptyModel
         */
        /**
         * @var Model $this
         */

        // 空モデル取得
        $emptyModel = $this->$relationName()->getQuery()->getModel()->replicate();

        // 逆リレーションを設定する場合
        if ($reverseRelationName) {
            // 逆リーレション設定
            $emptyModel->setRelation($reverseRelationName, $this);
        }
        // リレーション設定
        $this->setRelation($relationName, $emptyModel);
    }

    /**
     * 空のリレーションを設定します。(HasMany系リレーション用)
     * @param $relationName
     * @param null $reverseRelationName
     */
    public function setHasManyDummyRelation($relationName, $reverseRelationName = null)
    {
        /**
         * @var Model $emptyModel
         */
        /**
         * @var Model $this
         */

        $emptyModels = new Collection();

        // 空モデル取得
        $emptyModel = $this->$relationName()->getQuery()->getModel()->replicate();
        $emptyModels->add($emptyModel);

        // 逆リレーションを設定する場合
        if ($reverseRelationName) {
            foreach ($emptyModels as $key => $value) {
                // 逆リーレション設定
                $emptyModels[$key]->setRelation($reverseRelationName, $this);
            }
        }

        // リレーション設定
        $this->setRelation($relationName, $emptyModels);
    }
}

上記処理では
$emptyModel = $this->$relationName()->getQuery()->getModel()->replicate();
の部分で目的のモデルクラスのインスタンスをリレーション名から自動で取得できるようにしています。

そのため、利用する際は以下のように、実にシンプルな形で空リレーションの設定を実現することができます。


空リレーション設定用メソッドを利用するモデルクラス
class Parent extends Model
{
    use SetRelationTrait;

    public function grandparent()
    {
        return $this->belongsTo(Grandparent::class);
    }

    public function children()
    {
        return $this->hasMany(Child::class);
    }
}

●利用方法
$childId = 999;

$query = Parent::query();
$query->with([
    'grandparent',
    'children' => function ($query) use ($childId) {
        $query->where('children.id', '=', $childId);
    },
]);
$parent = $query->first();

// 祖父情報がNULLの場合
if(!$parent->grandparent) {
    $parent->setHasOneDummyRelation('grandparent', 'parent');
}

// 子情報が0件の場合
if(!count($parent->children)) {
    $parent->setHasManyDummyRelation('children', 'parent');
}

// grandparentがNULLの場合も以下の処理でエラーにならない
$grandparentName = $parent->grandparent->name;

// chldrenが0件の場合も以下の処理でエラーにならない
$childName = $parent->children->first()->name;
TOPに戻る