Laravel Eloquent "siblings" 作为关系?

Laravel Eloquent "siblings" as a relationship?

class PageRelation extends Eloquent
{
    public $incrementing = false;
    public $timestamps = false;
    protected $table = 'page_relation';
    protected $casts = [
            'parent' => 'int', // FK to page
            'child' => 'int',  // FK to page
            'lpc' => 'int',
        ];

    protected $fillable = [
            'lpc',
        ];

    public function children()
    {
        return $this->hasMany(Page::class, 'category_id', 'child');
    }

    public function parents()
    {
        return $this->hasMany(Page::class, 'category_id', 'parent');
    }

    public function siblings()
    {
        // ...  return $this->hasMany(Page::class ...
        // how do I define this relationship?
    }
}

在我的设计中,sibling 是(如您所料)共享相同 parent 但不共享自身(不包括当前 child)的记录。我怎样才能做到这一点?

这不是Laravel Eloquent Relationships for Siblings的重复,因为1)结构不同,2)我想return一个关系,而不是一个查询结果,我知道如何查询这个,但我想要 eager loader 的强大功能。

我不确定它是否适用于你的模型,这有点边缘,因为你将相同的 objects 与中间的 table 相关联。但是,

hasManyThrough()

可能是解决这个问题的方法。

"...通过 parent 有很多兄弟姐妹。"

https://laravel.com/docs/5.6/eloquent-relationships#has-many-through

我认为使用 Laravel 的内置关系无法做到这一点。我建议做的是创建你自己的扩展 HasMany 的关系类型并使用它。

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class HasManySiblings extends HasMany
{    
    public function addConstraints()
    {
        if (static::$constraints) {
            if (is_null($foreignKeyValue = $this->getParentKey())) {
                $this->query->whereNull($this->foreignKey);
            } else {
                $this->query->where($this->foreignKey, '=', $foreignKeyValue);
                $this->query->whereNotNull($this->foreignKey);
            }

            $this->query->where($this->localKey, '!=', $this->parent->getAttribute($this->localKey));
        }
    }

    public function getParentKey()
    {
        return $this->parent->getAttribute($this->foreignKey);
    }
}

通过扩展 HasMany class 并提供您自己的 addConstraints 实现,您可以控制添加到相关模型查询中的内容。通常,Laravel 在这里做的是添加 where parent_id = <your model ID>,但我在这里将其更改为添加 where parent_id = <your model PARENT ID>(如果您的模型的 parent_idnull,它将改为添加 where parent_id is null)。我还添加了一个额外的子句以确保调用模型不包含在生成的集合中:and id != <your model ID>.

您可以在您的 Page 模型中像这样使用它:

class Page extends Model
{
    public function siblings()
    {
        return new HasManySiblings(
            $this->newRelatedInstance(Page::class)->newQuery(), $this, 'parent_id', 'id'
        );
    }
}

现在您应该可以像这样加载兄弟姐妹了:

$page = Page::find(1);
dd($page->siblings);

不过请注意,我只针对检索相关模型进行了测试,当将关系用于其他目的(例如保存相关模型等)时,它可能无法正常工作。

此外,请注意,在我上面的示例中,我使用了 parent_id 而不是您问题中的 parent。不过应该直接交换。

这是off-topic,但请原谅我。我对你处理这些关系的方式有这样的建议。您不需要 PageRelation 模型,您可以直接在 Page 模型上定义 belongsToMany 关系。而且,你不需要额外的属性parent,这有点不一致,同时定义parent和child,只需要children就可以确定parents .因此,您可以在检索关系时反转键,而不是两个单独的列。让我用一个例子来告诉你我的意思:

pages:
keep this table intact

pages_relation:
- id
- page_id (foreign key to id on page)
- child_id (foreign key to id on page)

然后在模型中定义两个关系:

class Page extends Model
{
    public function children()
    {
        return $this->belongsToMany('App\Page', 'pages_relation', 'page_id', 'child_id');
    }

    public function parents()
    {
        return $this->belongsToMany('App\Page', 'pages_relation', 'child_id', 'page_id');
    }
}

你可以坚持任何你觉得好的事情。但是,我觉得这更一致。因为,只有单一的事实来源。 如果 A 是 B 的 child,那么 B 必须是 A 的 parent,很明显,只有 "A is child of B" 就足以说明 "B is a parent of A".

我已经测试过了,效果很好。

编辑
您可以扩展 BelongsToMany 关系以获得 BelongsToManySiblings 关系,并且只需覆盖 addWhereConstraints 方法。

class BelongsToManySiblings extends BelongsToMany
{
    protected function addWhereConstraints()
    {
        $parentIds = \DB::table($this->table)
            ->select($this->foreignPivotKey)
            ->where($this->relatedPivotKey, '=', $this->parent->{$this->parentKey})
            ->get()->pluck($this->foreignPivotKey)->toArray();

        $this->query->whereIn(
            $this->getQualifiedForeignPivotKeyName(),
            $parentIds
        )->where(
            $this->getQualifiedRelatedPivotKeyName(),
            '<>',
            $this->parent->{$this->parentKey}
        )->groupBy($this->getQualifiedRelatedPivotKeyName());

        return $this;
    }
}

然后你可以在你的Page模型上添加siblings关系方法:

public function siblings()
{
    return new BelongsToManySiblings(
        $this->newRelatedInstance(Page::class)->newQuery(),
        $this,
        'pages_relation',
        'parent_id',
        'child_id',
        'id',
        'id',
        $this->guessBelongsToManyRelation()
    );
}

注意: 这种情况不适用于预加载,预加载需要覆盖 matchaddEagerContraints 方法 BelongsToManySiblings class。您可以查看 laravel 源上的 BelongsToMany class 以查看它如何急切加载关系的示例。