约束嵌套的第三级关系

Constraining a nested 3rd level relationship

我正在使用预先加载构建一个 api,因此我可以简单地 return 具有深层关系的用户模型,它会自动转换为 json。这是设置。

users
    id
    ..

clients
    id
    ..

user_clients
    id
    user_id
    client_id
    ..

campaigns
    id
    ..

client_campaigns
    id
    client_id
    campaign_id
    ..

campaign_activities
    id
    campaign_id
    ..

client_campaign_activity_templates
    id
    campaign_activity_id
    client_id *(templates are unique per client)*
    ..

我已经设置了模型的关系。

User
    public function clients() {
        return $this->belongsToMany('App\Client','user_clients');
    }

Client
    public function campaigns() {
        return $this->belongsToMany('App\Campaign','client_campaigns');
    }

Campaign
    public function activities() {
        return $this->hasMany('App\CampaignActivity');
    }

CampaignActivity
    public function templates() {
        return $this->hasMany('App\ClientCampaignActivityTemplate')
    }

我有一个简单的 api 端点来提供 JSON 用户对象,包括使用预加载的深层关系。

public function getLoggedInUser(Request $request) {
    return \App\User::with('clients.campaigns.activities.templates')->find($request->user()->id);
}

使用邮递员对此进行测试,我可以获得包含其深层关系的用户。

{
    "user": {
        "id": 1,
        "name": "user1",
        "clients": [
            {
                "id": 1,
                "name": "client1",
                "campaigns": [
                    {
                        "id": 1,
                        "name": "campaign1",
                        "activities": [
                            {
                                "id": 1,
                                "name": "activity1",
                                "templates": [
                                    {
                                        "id": 1,
                                        "name": "template1 for client1",
                                        "client_id": 1,
                                        "body": "this is a template.",
                                    }, {
                                        "id": 2,
                                        "name": "template1 for client2",
                                        "client_id": 2,
                                        "body": "This is a template for client2"
                                    }
                                ]
                            }, {
                                "id": 2,
                                "name": "activity2",
                                "templates": []
                            }, {
                                "id": 3,
                                "name": "activity3",
                                "templates": []
                            }
                        ]
                    }
                ]
            }
        ]
    }
}

但是,在 user->clients->campaigns->activities->templates 级别,它将列出该 activity 的所有模板。根据上面模型关系的代码,我知道它应该表现得像那样。

所以问题是 您将如何过滤模板以同时过滤 campaign_activity_id 和 client_id?

我一直在试验如何过滤模板,以便它只列出 activity 和那个客户的模板。我有一个可行的解决方案,但它是 N+1,如果可能的话,我更喜欢 eloquent 方法。我一直在为一个非常相似的问题搜索其他问题、答案和评论,但我没有运气,因此我发布了这个问题并征求你的想法。谢谢

我想你需要的是eager loading constraints

public function getLoggedInUser(Request $request) {
    return \App\User::with('clients.campaigns.activities.templates',
        function($query) use($request) {
            $client_ids = Client::whereHas('users', function($q) use($request){
                $q->where('id', $request->user()->id);
            })->pluck('id');
            $query->whereIn('templates.client_id', $client_ids);
        })->find($request->user()->id);
}

未测试,但应该只需要一个额外的查询。

我正在做的是:为您的预先加载定义一个约束,即只显示那些在具有关系的客户端 ID 列表 (pluck) 中具有 client_id 的模板给用户。

尝试使用闭包过滤相关模型:

$users = App\User::with([

'clients' => function ($query) {
    $query->where('id', $id);
},

'clients.campaigns' => function ($query) {
    $query->where('id', $id);
}

])->get();

这是我的工作解决方案,但如果你们有更好的方法,我仍然很感兴趣。

CampaignActivity模型上,我添加了一个public属性client_id,并将关系代码修改为

CampaignActivity

    public $client_id = 0

    public function templates() {
        return $this->hasMany('App\ClientCampaignActivityTemplate')->where('client_id', $this->client_id);
    }

并且在我的控制器上,仅将预加载限制为活动(实际上,在这种情况下使用预加载 [9] 执行的 sql 与仅迭代 [7] 相比更多,并且预加载也没有意义不再因为我们正在迭代大声笑)

public function getLoggedInUser(Request $request) {
    foreach ($user->clients as $client)
        foreach( $client->campaigns as $campaign)
            foreach ($campaign->activities as $activity) {
                $activity->client_id = $client->id;
                $activity->templates; //to load the values
            }
    return $user;
}