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_id
是 null
,它将改为添加 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()
);
}
注意: 这种情况不适用于预加载,预加载需要覆盖 match
和 addEagerContraints
方法 BelongsToManySiblings
class。您可以查看 laravel 源上的 BelongsToMany
class 以查看它如何急切加载关系的示例。
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_id
是 null
,它将改为添加 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()
);
}
注意: 这种情况不适用于预加载,预加载需要覆盖 match
和 addEagerContraints
方法 BelongsToManySiblings
class。您可以查看 laravel 源上的 BelongsToMany
class 以查看它如何急切加载关系的示例。