渴望加载相同的关系,不同的约束

时间:2018-03-29 14:24:43

标签: php laravel eloquent relationship eager-loading

在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);
        }
    ])

有没有人有更好的方法?

1 个答案:

答案 0 :(得分:1)

想出来。模拟类中用于解析as关键字的某些方法的覆盖就可以了。我将所有这些都填充到一个特性中,但它可以很容易地移动到所有模型扩展的基类,并且它本身扩展了模型:

/**
 * @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;
    }
}