Laravel-通过带有急切负载的数据透视表一对一关系

时间:2019-12-11 11:57:46

标签: laravel eager-loading

我有这种关系

  • 运动可以有多个步骤
  • 一个步骤可以属于多个动作

因此a必须创建一个数据透视表和一个belongsToMany关系,但是我的数据透视表具有一些额外的列,例如finishedorder

我想有两种关系,一种是从动作中获取所有步骤,另一种是从动作中获取当前步骤(最后完成的步骤)

我知道如何获得所有步骤

public function steps()
{
    return $this->belongsToMany(MovementStep::class, 'movement_movement_steps')
        ->withPivot('order', 'finished')
        ->orderBy('pivot_order');
}

但是当前步骤如何?我需要这种关系,但是只返回一条记录并能够急于加载它,因为我将其传递给vue.js

public function current_step()
{
    return $this->belongsToMany(MovementStep::class, 'movement_movement_steps')
        ->withPivot('order', 'finished')
        ->where('finished', true)
        ->orderBy('pivot_order', 'desc');
}

注意,我想在没有附加程序包的情况下做到这一点

替代解决方案,但带有附加软件包: Laravel hasOne through a pivot table(不是标记为正确答案,而是来自@cbaconnier的答案)

2 个答案:

答案 0 :(得分:2)

与@mrhn提供的答案不同的方法是创建自定义关系。 Spatie的Brent对此做了excellent article

尽管我的答案将执行与staudenmeir's package提供的查询完全相同的查询,但它使我意识到,无论您使用软件包,此答案还是@mrhn答案,都可以避免n + 1条查询,但是您可能仍然会导致大量水合模型。

在这种情况下,我认为不可能避免一种或另一种方法。缓存可能是一个答案。

由于我不确定您的架构,因此将使用previous answer中的users-photos示例提供解决方案。

User.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{

    public function photos()
    {
        return $this->belongsToMany(Photo::class);
    }

    public function latestPhoto()
    {
        return new \App\Relations\LatestPhotoRelation($this);
    }
}

LastestPhotoRelation.php

<?php


namespace App\Relations;

use App\Models\User;
use App\Models\Photo;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\Relation;

class LatestPhotoRelation extends Relation
{

    /** @var Photo|Builder */
    protected $query;

    /** @var User */
    protected $user;

    public function __construct(User $user)
    {
        parent::__construct(Photo::query(), $user);
    }

    /**
     * @inheritDoc
     */
    public function addConstraints()
    {
        $this->query
            ->join(
                'user_photo',
                'user_photo.photo_id',
                '=',
                'photos.id'
            )->latest();

            // if you have an ambiguous column name error you can use
            // `->latest('movement_movement_steps.created_at');`
    }

    /**
     * @inheritDoc
     */
    public function addEagerConstraints(array $users)
    {
        $this->query
            ->whereIn(
                'user_photo.user_id',
                collect($users)->pluck('id')
            );
    }

    /**
     * @inheritDoc
     */
    public function initRelation(array $users, $relation)
    {
        foreach ($users as $user) {
            $user->setRelation(
                $relation,
                null
            );
        }
        return $users;
    }

    /**
     * @inheritDoc
     */
    public function match(array $users, Collection $photos, $relation)
    {
        if ($photos->isEmpty()) {
            return $users;
        }

        foreach ($users as $user) {
            $user->setRelation(
                $relation,
                $photos->filter(function (Photo $photo) use ($user) {
                    return $photo->user_id === $user->id;  // `user_id` came with the `join` on `user_photo`
                })->first() // Photos are already DESC ordered from the query
            );
        }

        return $users;
    }

    /**
     * @inheritDoc
     */
    public function getResults()
    {
        return $this->query->get();
    }
}

用法

$users = \App\Models\User::with('latestPhoto')->limit(5)->get();

与布伦特(Brent)文章的主要区别在于,我们不使用Collection,而是返回最新的照片Model

答案 1 :(得分:0)

Laravel具有create getters and setters的作用方式,其作用类似于数据库中的列。这些可以完美地解决您的问题,您可以将它们附加到序列化中。

因此,您的current_step将成为访问器(获取程序)。该函数的语法为getCurrentStepAttribute(),这将使其可以在current_step属性上访问。为了避免N + 1,当您使用with('steps')方法检索模型时,急于加载步骤。这比将它作为查询运行更好,因为它将始终执行N次。

public function getCurrentStepAttribute() {
    return $this->steps
        ->where('finished', true)
        ->sortByDesc('pivot_order')
        ->first();
}

现在,您可以在Movement.php类上使用append属性,以包括口才的访问者。

protected $appends = ['current_step'];