LoginSignup
13
6

More than 5 years have passed since last update.

【Laravel】 Eager Loading 応用: ピボット上のリレーションの Eager Loading

Last updated at Posted at 2018-04-06

ピボット上のリレーションの Eager Loading

定義

app/Concerns/Relations/EagerBelongsToMany.php
<?php

declare(strict_types=1);

namespace App\Concerns\Relations;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Collection;

/**
 * Class EagerBelongsToMany
 *
 * BelongsToMany と同一ですが,ピボット上のリレーションのEagerローディングに対応しています。
 */
class EagerBelongsToMany extends BelongsToMany
{
    /**
     * ロードしたいピボット上のリレーションを定義します。
     *
     * @var array
     */
    protected $pivotRelations = [];

    /**
     * 所有者側からみたピボットリレーションを生成しつつ,ピボット上のリレーションもロードします。
     *
     * @param Model[] $models
     */
    protected function hydratePivotRelation(array $models): void
    {
        parent::hydratePivotRelation($models);

        (new Collection($models))->map->{$this->accessor}->load($this->pivotRelations);
    }

    /**
     * ロードしたいピボット上のリレーションを定義します。
     *
     * @param $relations
     * @return $this
     */
    public function withPivotRelation($relations): self
    {
        $this->pivotRelations = array_merge(
            $this->pivotRelations, is_array($relations) ? $relations : func_get_args()
        );

        return $this;
    }

    /**
     * 除外したいピボット上のリレーションを定義します。
     *
     * @param $relations
     * @return $this
     */
    public function withoutPivotRelation($relations): self
    {
        $this->pivotRelations = array_values(array_diff(
            $this->pivotRelations, is_array($relations) ? $relations : func_get_args()
        ));

        return $this;
    }
}
app/Concerns/HasExtendedRelationShips.php
<?php

declare(strict_types=1);

namespace App\Concerns;

use App\Concerns\Relations\EagerBelongsToMany;
use Illuminate\Database\Eloquent\Model;

/**
 * Trait HasExtendedRelationships
 *
 * 拡張リレーションを定義します。
 *
 * @mixin Model
 */
trait HasExtendedRelationships
{
    /**
     * belongsToMany と同一ですが,ピボット上のリレーションのEagerローディングに対応しています。
     * コンストラクタコール部分以外はコピペです。
     *
     * @param  string             $related
     * @param  string             $table
     * @param  string             $foreignPivotKey
     * @param  string             $relatedPivotKey
     * @param  string             $parentKey
     * @param  string             $relatedKey
     * @param  string             $relation
     * @return EagerBelongsToMany
     */
    public function eagerBelongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null,
                                  $parentKey = null, $relatedKey = null, $relation = null): EagerBelongsToMany
    {
        // If no relationship name was passed, we will pull backtraces to get the
        // name of the calling function. We will use that function name as the
        // title of this relation since that is a great convention to apply.
        if ($relation === null) {
            $relation = $this->guessBelongsToManyRelation();
        }

        // First, we'll need to determine the foreign key and "other key" for the
        // relationship. Once we have determined the keys we'll make the query
        // instances as well as the relationship instances we need for this.
        $instance = $this->newRelatedInstance($related);

        $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey();

        $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey();

        // If no table name was provided, we can guess it by concatenating the two
        // models using underscores in alphabetical order. The two model names
        // are transformed to snake case from their default CamelCase also.
        if ($table === null) {
            $table = $this->joiningTable($related);
        }

        return new EagerBelongsToMany(
            $instance->newQuery(), $this, $table, $foreignPivotKey,
            $relatedPivotKey, $parentKey ?: $this->getKeyName(),
            $relatedKey ?: $instance->getKeyName(), $relation
        );
    }
}

利用例

app/Community.php
// コミュニティ
class Community extends Model {}
app/User.php
// ユーザ
class User extends Model
{
    use HasExtendedRelationShips;

    public function communities(): Builder
    {
        return $this->eagerBelongsToMany(Community::class)
            ->as('membership')
            ->using(Membership::class)// ピボットクラスの指定
            ->withPivot('registered_at')// ピボット上の読み込むカラムの指定
            ->withPivotRelation('payment');// ピボット上の読み込むリレーションの指定
    }
}
app/Membership.php
// ユーザは入会情報テーブルを通じてコミュニティに属している
// 更に入会情報テーブルから支払い情報にもリンクされている
class Membership extends Pivot
{
    protected $table = 'memberships';

    public function payment(): Builder
    {
        return $this->belongsTo(Payment::class);
    }
}
app/Payment.php
// 支払い情報
class Payment extends Model {}
$users = User::with('communities')->whereIn('id', [1, 2, 3])->get();

// これらの User が所持しているすべての Payment を取得
// Eager Loading により N+1 問題回避可能
dd($users->map->communities->map->membership->map->payment);
13
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
6