Laravel 属于具有条件和急切加载

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/37289373/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 13:49:42  来源:igfitidea点击:

Laravel belongsTo with condition and eager load

laraveleager-loadingbelongs-to

提问by lkartono

I have a Postmodel associated to a Sectionmodel, which depend on an extra condition to work:

我有一个与Section模型相关联的Post模型,它依赖于一个额外的条件来工作:

<?php
class Post extends Base
{
    public function section()
    {
        return $this->belongsTo('App\Models\Section', 'id_cat')->where('website', $this->website);
    }
}

When I want to retrieve a Post and get it's associated section, I can do it as:

当我想检索一个帖子并获取它的关联部分时,我可以这样做:

$post = Post::first();
echo $post->section->name; // Output the section's name

However, when trying to get the section using an eager load:

但是,当尝试使用急切加载获取该部分时:

Post::with(['section'])->chunk(1000, function ($posts) {
    echo $post->section->name;
});

Laravel throw the following exception :

Laravel 抛出以下异常:

PHP error:  Trying to get property of non-object

When I do a debug of a Post object returned by the above eager load query, I notice that the sectionrelationship is null. Note that it is working fine if I remove the condition from the belongsToassociation.

当我对上述急切加载查询返回的 Post 对象进行调试时,我注意到section关系是null. 请注意,如果我从belongsTo关联中删除条件,它工作正常。

Do you guys have any ideas why it's happening?

你们知道为什么会这样吗?

回答by mpyw

You can achieve it by defining custom relationship.

您可以通过定义自定义关系来实现它。

BelongsToWith.php

BelongsToWith.php

<?php

declare(strict_types=1);

namespace App\Database\Eloquent\Relations;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class BelongsToWith extends BelongsTo
{
    /**
     * @var array [$foreignColumn => $ownerColumn, ...] assoc or [$column, ...] array
     */
    protected $conditions = [];

    public function __construct(array $conditions, Builder $query, Model $child, string $foreignKey, string $ownerKey, string $relation)
    {
        $this->conditions = $conditions;

        parent::__construct($query, $child, $foreignKey, $ownerKey, $relation);
    }

    public function addConstraints()
    {
        if (static::$constraints) {
            // Add base constraints
            parent::addConstraints();

            // Add extra constraints
            foreach ($this->conditions as $key => $value) {
                if (is_int($key)) {
                    $key = $value;
                }
                $this->getQuery()->where($this->related->getTable() . '.' . $value, '=', $this->child->{$key});
            }
        }
    }

    public function addEagerConstraints(array $models)
    {
        // Ignore empty models
        if ([null] === $this->getEagerModelKeys($models)) {
            parent::addEagerConstraints($models);
            return;
        }

        $this->getQuery()->where(function (Builder $query) use ($models) {
            foreach ($models as $model) {
                $query->orWhere(function (Builder $query) use ($model) {
                    // Add base constraints
                    $query->where($this->related->getTable() . '.' . $this->ownerKey, $model->getAttribute($this->foreignKey));

                    // Add extra constraints
                    foreach ($this->conditions as $key => $value) {
                        if (is_int($key)) {
                            $key = $value;
                        }
                        $query->where($this->related->getTable() . '.' . $value, $model->getAttribute($key));
                    }
                });
            }
        });
    }

    public function match(array $models, Collection $results, $relation)
    {
        $dictionary = [];

        foreach ($results as $result) {
            // Base constraints
            $keys = [$result->getAttribute($this->ownerKey)];

            // Extra constraints
            foreach ($this->conditions as $key => $value) {
                $keys[] = $result->getAttribute($value);
            }

            // Build nested dictionary
            $current = &$dictionary;
            foreach ($keys as $key) {
                $current = &$current[$key];
            }
            $current = $result;
            unset($current);
        }

        foreach ($models as $model) {
            $current = $dictionary;

            // Base constraints
            if (!isset($current[$model->{$this->foreignKey}])) {
                continue;
            }
            $current = $current[$model->{$this->foreignKey}];

            // Extra constraints
            foreach ($this->conditions as $key => $value) {
                if (is_int($key)) {
                    $key = $value;
                }
                if (!isset($current[$model->{$key}])) {
                    continue 2;
                }
                $current = $current[$model->{$key}];
            }

            // Set passed result
            $model->setRelation($relation, $current);
        }

        return $models;
    }
}

HasExtendedRelationships.php

HasExtendedRelationships.php

<?php

declare(strict_types=1);

namespace App\Database\Eloquent\Concerns;

use App\Database\Eloquent\Relations\BelongsToWith;
use Illuminate\Support\Str;

trait HasExtendedRelationships
{
    public function belongsToWith(array $conditions, $related, $foreignKey = null, $ownerKey = null, $relation = null): BelongsToWith
    {
        if ($relation === null) {
            $relation = $this->guessBelongsToRelation();
        }

        $instance = $this->newRelatedInstance($related);

        if ($foreignKey === null) {
            $foreignKey = Str::snake($relation) . '_' . $instance->getKeyName();
        }

        $ownerKey = $ownerKey ?: $instance->getKeyName();

        return new BelongsToWith($conditions, $instance->newQuery(), $this, $foreignKey, $ownerKey, $relation);
    }
}

Then:

然后:

class Post extends Base
{
    use HasExtendedRelationships;

    public function section(): BelongsToWith
    {
        return $this->belongsToWith(['website'], App\Models\Section::class, 'id_cat');
    }
}

 

 

$posts = Post::with('section')->find([1, 2]);

Your Eager Loading query will be like:

您的 Eager Loading 查询将类似于:

select * from `sections`
where (
    (
        `sections`.`id` = {$posts[0]->id_cat}
        and `sections`.`website` = {$posts[0]->website}
    )
    or
    (
        `sections`.`id` = {$posts[1]->id_cat}
        and `sections`.`website` = {$posts[1]->website}
    )
)

回答by Vishal Sharma

As mentioned in my comment, whereshouldn't be used in the relationship definition. Hence, your relation definition is good with just

正如我在评论中提到的,where不应在关系定义中使用。因此,您的关系定义仅适用于

public function section()
{
    return $this->belongsTo('App\Models\Section', 'id_cat');
}

and you can eager load in this way (not giving out the exact query with chunk etc)

并且您可以通过这种方式预先加载(不使用块等给出确切的查询)

Post::with(['section' => function ($query) use ($request) {
    $query->where('website', $request['website'])
}])->get()->first();

i.e. when you pass the variable websitein request or else use any other variable in a similar way.

即当您website在请求中传递变量或以类似方式使用任何其他变量时。

I hope that explains. Please add comments if anything is unclear.

我希望这能解释。如果有任何不清楚的地方,请添加评论。