我如何预加载嵌套关系并在嵌套的 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'
                );