我如何预加载嵌套关系并在嵌套的 foreach 循环中遍历集合?
How do I eager-load nested relationships and go through the collection in nested foreach loops?
我正在尝试预先加载多个层次的集合,然后遍历该集合并让它忘记其中的一部分。
我的问题是这里的 foreach 循环根本不触发,dd() 在某些级别上只给我一个关系而不是集合元素。
我也知道我的方法可能不是做这样的事情的最佳方法。我很乐意学习更好的方法:)
所以我得到的是类似下面的数据:
MainChannel->GroupChannel->Channel->Activities
OnlineChannels->SocialMedia->Youtube->Activities
OnlineChannels->SocialMedia->Facebook->Activities
OnlineChannels->Webpages->Something.com->Activities
OfflineChannels->Face-To-Face->Congress->Activities
频道嵌套在树形结构中,可以深入 3 层。它们具有 lft rgt 和 depth 属性。
id | name | lft | rgt | depth | color | important_links | parent_id
所以首先我尝试预先加载整个东西:
$GroupChannels = Channel::where('depth', 2)->with(
'parent',
'children',
'children.activities'
);
并可能通过
过滤
if(isset($input['filtered_channels'])){
$Channels = $Channels->whereIn('id', $input['filtered_channels']);
}
$Channels = $Channels->get();
然后我想像这样完成它:
foreach($GroupChannels as $Channel){
foreach($Channel->children as $Subchannel){
foreach ($Subchannel->activities as $Activity){
$removeActivity = false;
// do some checks on the activity that will maybe turn the $removeActivity to true
if($removeActivity){
$Subchannel->forget($Activity);
}
}
//forget Subchannel if not Activities remain
}
//forget Channel if not Subchannels remain
}
这是我的模型:
class Channel extends Model
{
use CrudTrait;
use SoftDeletes;
use \Venturecraft\Revisionable\RevisionableTrait;
/*
|--------------------------------------------------------------------------
| GLOBAL VARIABLES
|--------------------------------------------------------------------------
*/
protected $table = 'channels';
protected $primaryKey = 'id';
protected $fillable = ['name', 'color', 'parent_id', 'lft', 'rgt', 'depth', 'important_links'];
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
/*
|--------------------------------------------------------------------------
| FUNCTIONS
|--------------------------------------------------------------------------
*/
public static function boot()
{
parent::boot();
}
/*
|--------------------------------------------------------------------------
| RELATIONS
|--------------------------------------------------------------------------
*/
public function activities()
{
return $this->belongsToMany(Activity::class);
}
public function children()
{
return $this->hasMany(Channel::class, 'parent_id', 'id');
}
public function parent()
{
return $this->hasOne(Channel::class, 'id', 'parent_id');
}
/*
|--------------------------------------------------------------------------
| ACCESORS
|--------------------------------------------------------------------------
*/
public function getChildrenAttribute(){
return $this->children()->orderBy('lft');
}
}
问题出现在二级
foreach($Channel->children 作为$Subchannel){
因为这不会触发。
当我在这里执行 dd($Subchannel) 时,它不会显示,如果我在 dd($Channel->children) 的那一行之上执行,我得到一个关系而不是一个集合。
HasMany {#872 ▼
#foreignKey: "channels.parent_id"
#localKey: "id"
#query: Builder {#831 ▶}
#parent: Channel {#840 ▶}
#related: Channel {#832 ▶}
}
所以我猜 eager-load 不会 return 嵌套集合,或者我不能像那样遍历该集合中的树。老实说,我不知道如何以正确的方式做到这一点。
我无法查看所有逻辑,但可以肯定的是,问题可能出在使用 getChildrenAttribute
上。你不应该创建与关系同名的 accessor/mutator 因为如果你写:
$channel->children
很难说它的作用:
是否启动关系(所以实际上它只是 $channel->children()->get()`
的快捷方式
或者它可能启动访问器,所以它是 运行 $channel->children()->orderBy('lft')
但实际上它没有从数据库中获取任何数据。
我认为问题出在这个访问器上,因为它没有 return 关系中的任何数据 - 它只是 return 关系,所以要真正获取数据,那么你应该使用这样的东西:
$channel->children->get()
但它不会重用急切加载的 children
关系。
对于排序,您应该使用不同名称的范围或关系。例如,您可以有 2 个关系:
public function children()
{
return $this->hasMany(Channel::class, 'parent_id', 'id');
}
public function orderedChildren()
{
return $this->children()->orderBy('lft')
}
然后在您的代码中,您可以像这样预先加载:
$GroupChannels = Channel::where('depth', 2)->with(
'parent',
'orderedChildren',
'orderedChildren.activities'
);
或者更简单的像这样预加载就足够了:
$GroupChannels = Channel::where('depth', 2)->with(
'parent',
'orderedChildren.activities'
);
我正在尝试预先加载多个层次的集合,然后遍历该集合并让它忘记其中的一部分。
我的问题是这里的 foreach 循环根本不触发,dd() 在某些级别上只给我一个关系而不是集合元素。
我也知道我的方法可能不是做这样的事情的最佳方法。我很乐意学习更好的方法:)
所以我得到的是类似下面的数据:
MainChannel->GroupChannel->Channel->Activities
OnlineChannels->SocialMedia->Youtube->Activities
OnlineChannels->SocialMedia->Facebook->Activities
OnlineChannels->Webpages->Something.com->Activities
OfflineChannels->Face-To-Face->Congress->Activities
频道嵌套在树形结构中,可以深入 3 层。它们具有 lft rgt 和 depth 属性。
id | name | lft | rgt | depth | color | important_links | parent_id
所以首先我尝试预先加载整个东西:
$GroupChannels = Channel::where('depth', 2)->with(
'parent',
'children',
'children.activities'
);
并可能通过
过滤 if(isset($input['filtered_channels'])){
$Channels = $Channels->whereIn('id', $input['filtered_channels']);
}
$Channels = $Channels->get();
然后我想像这样完成它:
foreach($GroupChannels as $Channel){
foreach($Channel->children as $Subchannel){
foreach ($Subchannel->activities as $Activity){
$removeActivity = false;
// do some checks on the activity that will maybe turn the $removeActivity to true
if($removeActivity){
$Subchannel->forget($Activity);
}
}
//forget Subchannel if not Activities remain
}
//forget Channel if not Subchannels remain
}
这是我的模型:
class Channel extends Model
{
use CrudTrait;
use SoftDeletes;
use \Venturecraft\Revisionable\RevisionableTrait;
/*
|--------------------------------------------------------------------------
| GLOBAL VARIABLES
|--------------------------------------------------------------------------
*/
protected $table = 'channels';
protected $primaryKey = 'id';
protected $fillable = ['name', 'color', 'parent_id', 'lft', 'rgt', 'depth', 'important_links'];
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
/*
|--------------------------------------------------------------------------
| FUNCTIONS
|--------------------------------------------------------------------------
*/
public static function boot()
{
parent::boot();
}
/*
|--------------------------------------------------------------------------
| RELATIONS
|--------------------------------------------------------------------------
*/
public function activities()
{
return $this->belongsToMany(Activity::class);
}
public function children()
{
return $this->hasMany(Channel::class, 'parent_id', 'id');
}
public function parent()
{
return $this->hasOne(Channel::class, 'id', 'parent_id');
}
/*
|--------------------------------------------------------------------------
| ACCESORS
|--------------------------------------------------------------------------
*/
public function getChildrenAttribute(){
return $this->children()->orderBy('lft');
}
}
问题出现在二级 foreach($Channel->children 作为$Subchannel){ 因为这不会触发。
当我在这里执行 dd($Subchannel) 时,它不会显示,如果我在 dd($Channel->children) 的那一行之上执行,我得到一个关系而不是一个集合。
HasMany {#872 ▼
#foreignKey: "channels.parent_id"
#localKey: "id"
#query: Builder {#831 ▶}
#parent: Channel {#840 ▶}
#related: Channel {#832 ▶}
}
所以我猜 eager-load 不会 return 嵌套集合,或者我不能像那样遍历该集合中的树。老实说,我不知道如何以正确的方式做到这一点。
我无法查看所有逻辑,但可以肯定的是,问题可能出在使用 getChildrenAttribute
上。你不应该创建与关系同名的 accessor/mutator 因为如果你写:
$channel->children
很难说它的作用:
是否启动关系(所以实际上它只是 $channel->children()->get()`
的快捷方式
或者它可能启动访问器,所以它是 运行
$channel->children()->orderBy('lft')
但实际上它没有从数据库中获取任何数据。
我认为问题出在这个访问器上,因为它没有 return 关系中的任何数据 - 它只是 return 关系,所以要真正获取数据,那么你应该使用这样的东西:
$channel->children->get()
但它不会重用急切加载的 children
关系。
对于排序,您应该使用不同名称的范围或关系。例如,您可以有 2 个关系:
public function children()
{
return $this->hasMany(Channel::class, 'parent_id', 'id');
}
public function orderedChildren()
{
return $this->children()->orderBy('lft')
}
然后在您的代码中,您可以像这样预先加载:
$GroupChannels = Channel::where('depth', 2)->with(
'parent',
'orderedChildren',
'orderedChildren.activities'
);
或者更简单的像这样预加载就足够了:
$GroupChannels = Channel::where('depth', 2)->with(
'parent',
'orderedChildren.activities'
);