如何跨 4 个表使用 Laravel 的 hasManyThrough

How to use Laravel's hasManyThrough across 4 tables

我有 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;

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

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

首先,属性的重载。

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

    return $this->getRelation('orders');
}

上述函数的思路是在调用惰性加载 ->orders 时进行捕获。比如$user->orders。这将检查 orders 方法是否在现有 User modelrelations 数组中。如果不是,那么它会调用我们的 next 函数来填充关系,最后 returns 我们刚刚创建的关系。

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

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;
}

在上面,我们告诉订单 table 加入它的联系人(在这种情况下,这是给定 belongsTo() 所有权的既定路线)。然后我们从联系人中加入帐户,然后从帐户中我们可以通过将我们的 owner_id 列与我们现有的 $user->id 相匹配来从我们的用户那里获得,所以我们不需要做任何进一步的事情。

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

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

$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;

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

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

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

对我来说同样有效,欢迎大家反馈

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

安装完成后可以这样使用:

class User extends Model {
    use \Staudenmeir\EloquentHasManyDeep\HasRelationships;

    public function orders() {
        return $this->hasManyDeep(Order::class, [Account::class, Contact::class]);
    }
}