【问题标题】:How to use Laravel's hasManyThrough across 4 tables如何跨 4 个表使用 Laravel 的 hasManyThrough
【发布时间】:2016-07-13 16:56:28
【问题描述】:

我有 4 个表,其结构和流程如下:

User
Accounts
Contacts
Orders

关系如下:

$user->hasMany('accounts')->hasMany('contacts')->hasMany('orders');

/** User Model **/
class User extend Eloquent {
    public function accounts(){
        return $this->hasMany('Accounts');
    }

    public function contacts(){
        return $this->hasManyThrough('Contact', 'Account', 'owner_id');
    }

    //how do I get this?
    public function orders(){

    }
}

/** Account Model */
class Account extends Eloquent {
    public function $user(){
        return $this->belongsTo('User');
    }
    public function contacts(){
        return $this->hasMany('Contact');
    }
}

/** Contact Model **/
class Contact extends Eloquent {
    public function account(){
        return $this->belongsTo('Account');
    }

    public function orders(){
        return $this->hasMany('Order');
    }
}

/** Order Model **/
class Order extends Eloquent {
    public function contact(){
        return $this->belongsTo('contact');
    }
}

经典的hasManyThrough 是不可能的,因为我们有 4 个表。

如何创建关系,以便单个用户可以访问其订单,而无需链接每个模型的方法,例如:

User::find(8)->orders;

【问题讨论】:

    标签: php laravel laravel-5 laravel-5.2


    【解决方案1】:

    一开始很难确定查询构建器是如何将事物分割在一起的。我知道我想如何使用原始 SQL 语句格式化查询,但考虑到其他已定义的关系,我想将 Eloquent 用于此特定任务。

    我能够通过重载属性并手动创建关系来解决这个问题。但是,实际的查询和 hasMany 也需要手动构建。让我们看看我是如何做到这一点的。请记住,我们的目标是通过 2 个 has-Many 关系将 Orders 与用户联系起来。

    首先,属性的重载。

    public function getOrdersAttribute()
    {
        if( ! array_key_exists('orders', $this->relations)) $this->orders();
    
        return $this->getRelation('orders');
    }
    

    上述函数中的想法是在调用延迟加载的->orders 时进行捕获。如$user->orders。这将检查orders 方法是否在现有User modelrelations 数组中。如果不是,则调用我们的 next 函数来填充关系,最后返回我们刚刚创建的关系。

    实际查询订单的函数如下所示:

    public function orders()
    {
        $orders = Order::join('contacts', 'orders.contact_id', '=', 'contacts.id')
            ->join('accounts', 'contacts.account_id', '=', 'accounts.id')
            ->where('accounts.owner_id', $this->getkey())
            ->get();
    
        $hasMany = new Illuminate\Database\Eloquent\Relations\HasMany(User::query(), $this, 'accounts.owner_id', 'id');
    
        $hasMany->matchMany(array($this), $orders, 'orders');
    
        return $this;
    }
    

    在上面,我们告诉 Orders 表加入它的联系人(在这种情况下,这是给定 belongsTo() 所有权的已建立路由)。然后从联系人中,我们加入帐户,然后通过将我们的owner_id 列与我们现有的$user->id 匹配,我们可以从我们的用户那里到达那里,所以我们不需要做任何进一步的事情。

    接下来,我们需要通过从Eloquent Relationship 构建器实例化hasMany 的实例来手动创建我们的关系。

    鉴于HasMany 方法实际上扩展了HasOneOrMany 抽象类,我们可以通过将参数直接传递给HasMany 来访问HasOneOrMany 抽象类,如下所示:

    $hasMany = new Illuminate\Database\Eloquent\Relations\HasMany(User::query(), $this, 'accounts.owner_id', 'id');
    

    HasOneOrMany 期望它的构造函数如下:

    Builder $query,
    Model $parent,
    $foreignKey, 
    $localKey
    

    所以对于我们的构建器查询,我们已经传递了一个我们希望与之建立关系的模型实例,第二个参数是我们模型的实例 ($this),第三个参数是来自的外键约束我们的 Current->2nd 模型,最后一个参数是我们当前模型与 Current->2nd 模型上的外键约束相匹配的列。

    一旦我们从上面的HasMany 声明中创建了Relation 实例,我们就需要将关系的结果与它们的许多父母相匹配。我们使用接受 3 个参数的 matchMany() 方法来做到这一点:

    array $models,
    Collection $results,
    $relation
    

    因此在这种情况下,模型数组将是我们当前 eloquent 模型(用户)的数组实例,可以将其包装在数组中以实现我们的效果。

    第二个参数将是我们在 orders() 函数中的初始 $orders 查询的结果。

    最后,第三个参数将是关系string,我们希望使用它来获取该关系的实例;对我们来说是order

    现在您可以正确使用 Eloquent 或延迟加载来为我们的用户获取订单。

    User::find(8)->orders();
    
    $user->orders;
    

    希望这对面临类似问题的其他人有所帮助。

    【讨论】:

    • 您也可以通过扩展HasMany 类并覆盖方法addConstraints 来做到这一点
    • 这是一个几乎可行的英雄解决方案,但由于某种原因,Order 模型在实例化时不会有正确的 ID。相反,它们的id 将与contact_id 相同。至少当我在 Laravel 5.5 上测试它时是这样。不过,我终其一生都无法弄清楚为什么。
    • @JeffPuckett 我实现这个已经有一段时间了。我几乎也用这个构建了一个包,因为有一些细微差别,让我重新审视一下,看看我是否能找出原因。
    【解决方案2】:

    您可以通过用户联系然后joinOrders

    public function orders(){
       return $this->hasManyThrough('Contact', 'Account', 'owner_id')->join('orders','contact.id','=','orders.contact_ID')->select('orders.*');
    }
    

    它在同样的情况下对我有用,欢迎所有反馈

    【讨论】:

    • 这种方法的问题是返回的模型将是Contact 的实例,具有Order 的所有属性
    【解决方案3】:

    我创建了一个无限级别的HasManyThrough 关系:Repository on GitHub

    安装后,可以这样使用:

    class User extends Model {
        use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
    
        public function orders() {
            return $this->hasManyDeep(Order::class, [Account::class, Contact::class]);
        }
    }
    

    【讨论】:

    • 不查看该项目的源代码,这是可以很容易添加到核心的东西吗?
    • 这是可能的,但我强烈怀疑它是否会发生。我没有计划推荐它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-10
    • 2015-12-14
    • 1970-01-01
    • 2018-01-01
    • 2019-01-11
    • 2014-09-19
    相关资源
    最近更新 更多