约束嵌套的第三级关系
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;
}
我正在使用预先加载构建一个 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;
}