【问题标题】:Laravel Eloquent "siblings" as a relationship?Laravel 雄辩的“兄弟姐妹”作为关系?
【发布时间】:2018-06-14 08:19:54
【问题描述】:
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)我想返回一个关系,而不是一个查询结果,我知道如何查询这个,但我想要eager loader的力量.

【问题讨论】:

  • 你能描述一下你们的关系吗,也许用不同的结构会更容易。我有点怀疑是否可以通过多态关系来实现。
  • @Ruman PageRelationPage 的数据透视表,我希望通过它的几列以及我已经定义的父母和子女关系非常清楚。我只想要共享相同父级的所有页面。至于多态关系:这确实是错误的解决方案,它们适用于从 1 个表到其他 N 个表的关系,使用同一列。
  • 好的,我在想别的事。

标签: laravel eloquent


【解决方案1】:

我认为你不能使用 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 类并提供您自己的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。不过应该是直接交换。

【讨论】:

    【解决方案2】:

    我不确定它是否适用于您的模型,这有点边缘化,因为您将相同的对象与中间表相关联。但是,

    hasManyThrough()
    

    可能是解决此问题的方法。

    “...通过父母有很多兄弟姐妹。”

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

    【讨论】:

      【解决方案3】:

      这是题外话,但我用这个暴露了我。对于你处理这些关系的方式,我有这个建议。您不需要PageRelation 模型,您可以直接在Page 模型上定义belongsToMany 关系。而且,你不需要额外的属性parent,这有点不一致,定义父母和孩子,只有孩子就足以确定父母。因此,您可以在检索关系时反转键,而不是两个单独的列。让我用一个例子告诉你我的意思:

      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的孩子,那么B必须是A的父母,很明显,只有“A是B的孩子”就足以说明“B是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()
          );
      }
      

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

      【讨论】:

      • 我看到你的亲戚了,但那是父母和孩子。你会怎么做兄弟姐妹?
      • 您可以使用 Jonathan 的逻辑,但在这种情况下您将扩展 BelongsToMany 关系。如果你愿意,我可以扩展我的答案。
      猜你喜欢
      • 2014-11-07
      • 2020-01-31
      • 1970-01-01
      • 2019-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-29
      相关资源
      最近更新 更多