【问题标题】:Eager-load Same Relationship, Different ConstraintsEager-load 相同的关系,不同的约束
【发布时间】:2018-09-08 13:28:39
【问题描述】:

在 Eloquent 中,我尝试两次延迟加载相同的关系,但具有不同的约束。这里的目标是了解有关员工时间表的两件事。一个是他们去年工作的所有时间。另一个是他们工作的第一次约会。

第一个关系约束是这样的:

$employeeTimeSheets = app(Timesheet::class)
    ->with([
        'punches' => function (Relation $query) {
            $query->where('punch_date', '>=', Carbon::now()->subYear());
        }
    ])

第二个是:

$employeeTimeSheets = app(Timesheet::class)
    ->with([
        'punches' => function (Relation $query) {
            $query
                ->whereNotNull('punch_date')
                ->orderBy('punch_date')
                ->limit(1);
        }
    ])

问题当然是“拳”只允许一次。并且将有几千名员工被拉到这里,所以为了提高性能,能够急切加载这些数据对我来说很重要。

这些是一种任意的条件,只在它们出现的整个系统中放置一次。所以我不确定是否需要向 Timesheet 模型添加全新的关系方法。而且我也不希望所有为每个员工全力以赴,只是为了提取最后一年和事后的最小值,因为那将是一大堆数据。

如果不覆盖 Eloquent 处理关系的方式以在名称中添加对 as 关键字的支持,我不确定这样做是否可行。但我真正想要的是这样的感觉:

$employeeTimeSheets = app(Timesheet::class)
    ->with([
        'punches as last_year' => function (Relation $query) {
            $query->where('punch_date', '>=', Carbon::now()->subYear());
        },
        'punches as first_punch' => function (Relation $query) {
            $query
                ->whereNotNull('punch_date')
                ->orderBy('punch_date')
                ->limit(1);
        }
    ])

谁有更好的办法?

【问题讨论】:

  • 我不认为为单个用例添加关系是一件坏事。我认为很少有关系被用在两个或三个以上的地方。
  • 我想让人感觉不舒服的原因并不是它只服务于一个用例。它可以 服务于一个用例。通常关系方法能够服务于通用目的,即使在设置时碰巧只有一件事使用它。但是添加一个具有像这样非常具体的行为的模型可以有效地将模型与实现结合起来,这种方式虽然是功能性的,但感觉就像是一种反模式。
  • 您的punches as last_year包含punches as first_punch中查询的最新打卡的可能性有多大?因为如果您可以期望在最近一年为每位员工提供一拳,那么您实际上并不需要第二个关系。第一个已经包含后者作为子集。顺便说一句,如果您不需要同时需要这两个关系,您还可以为所有关系取消设置带有unset($model->relation)$child->setRelations([]) 的加载关系。这也节省了内存。
  • last_year 集包含从一年前到今天的所有拳头。而“first_punch”将展示员工的第一次轮班,即使是 5 年前。因此,它们极有可能包含不同的数据。我确实同时需要两者。我可以简单地使用两个单独的查询来完成,方法是从第一个中提取 Id 以在第二个中使用,因此不必重新查询低效的搜索条件。然后将结果与setRelations 连接到一个大集合中。但是下面的解决方案感觉更方便。

标签: php laravel eloquent relationship eager-loading


【解决方案1】:

想通了。覆盖 Model 类中的一些方法来解析 as 关键字就可以了。我把所有这些都塞进了一个 trait 中,但它可以很容易地移动到一个由所有模型扩展的基类,并且它本身扩展了 Model:

/**
 * @uses \Illuminate\Database\Eloquent\Model
 * @uses \Illuminate\Database\Eloquent\Concerns\HasAttributes
 * @uses \Illuminate\Database\Eloquent\Concerns\HasRelationships
 *
 * Trait RelationAlias
 */
trait RelationAlias
{
    protected $validOperators = [
        'as'
    ];

    /**
     * @param string $method
     * @param array $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        if ($key = $this->parseKey($method)) {
            $method = $key['concrete'];
            if (method_exists($this, $method)) {
                return $this->$method(...$parameters);
            }
        }

        return parent::__call($method, $parameters);
    }

    /**
     * @return array
     */
    protected function getArrayableRelations()
    {
        $arrayableRelations = parent::getArrayableRelations();
        foreach ($arrayableRelations as $key => $value) {
            if ($aliased = $this->parseKey($key)) {
                $arrayableRelations[$aliased['alias']] = $value;
                unset($arrayableRelations[$key]);
            }
        }

        return $arrayableRelations;
    }

    /**
     * @param $key
     * @return mixed
     */
    public function getRelationValue($key)
    {
        if ($found = parent::getRelationValue($key)) {
            return $found;
        }

        $relations = array_keys($this->relations);
        foreach ($relations as $relation) {
            $aliased = $this->parseKey($relation);
            if ($aliased && $aliased['alias'] == $key) {
                if ($this->relationLoaded($relation)) {
                    return $this->relations[$relation];
                }

                if (method_exists($this, $aliased['concrete'])) {
                    return $this->getRelationshipFromMethod($key);
                }
            }
        }
    }

    /**
     * @param $key
     * @return array|null
     */
    protected function parseKey($key)
    {
        $concrete = $operator = $alias = null;
        foreach ($this->validOperators as $operator) {
            if (preg_match("/.+ $operator .+/i", $key)) {
                list($concrete, $operator, $alias) = explode(' ', $key);
                break;
            }
        }

        return $alias ? compact('concrete', 'operator', 'alias') : null;
    }
}

【讨论】:

    猜你喜欢
    • 2014-12-19
    • 1970-01-01
    • 2013-06-20
    • 2017-01-11
    • 1970-01-01
    • 2017-01-21
    • 2016-10-29
    • 1970-01-01
    • 2015-07-17
    相关资源
    最近更新 更多