Laravel, return all data on single model row recursively through relationships

Given a model and an id. Get all $fillable data for that row. This is including data from relationships between models, so if there's a relationship with another model. It needs to fetch all fillable data from the related model too. And if that related model has relationships, we need to follow those as well.

I've tried lots of stuff so far but it's all following the same general thought process. What I have so far on my most recent attempt is below. Further information: Each model has protected $fillable and an array called $getPossibleRelations which has a list of relationship names used by that model. There are __get functions for fillable, and possible relations on the models.

    $item = $model->where('id',$id);

    function deepProcess($item) {
        $fillable = $item->find(1)->getFillable();
        $item = call_user_func_array(array($item, 'select'), $fillable);//select only fillable fields
        $possibleRelations = $item->find(1)->getPossibleRelations();

        foreach ($possibleRelations as $rel) {//eager load any possible relations
            $item = $item->with([
                $rel => function($query) {//reaches here ok, below recursion fails
                    $query = deepProcess($query);
                }
            ]);
        }
        return $item;
    }
    $item = deepProcess($item)->get()->toArray();
    dd($item);

Within the eager load the query needs to somehow loop back through the same function. And it needs to make sure it doesn't go back through a relation it has already been through (I used get_class() to check for that in a previous attempt).

I'm a bit lost as to how I should do this

Here's another attempt I made which is also flawed in many obvious ways.

    $item = $model->where('id',$id);
    $checkedModels = [];
    $result = [];
    function deepFetch($item,&$result,&$checkedModels,$className) {

        if (in_array($className,$checkedModels)) {
            return; //we've already added bits from this model
        }
        array_push($checkedModels,$className);
        if($className == 'Illuminate\Database\Eloquent\Collection') {
            dd($item);
        }
        var_dump('loop count');
        $fillable = $item->get()[0]->getFillable();
        $possibleRelations = $item->get()[0]->getPossibleRelations();

        foreach($item->select($fillable)->get() as $row) {
            array_push($result,$row);
        }

        foreach ($possibleRelations as $rel) {
            dd($item->get());
            $newItem = $item->get()->$rel;
            deepFetch($newItem,$result[$rel],$checkedModels,get_class($newItem));
        }
    }
    deepFetch($item,$result,$checkedModels,get_class($model));

1 answer

  • answered 2018-03-13 20:21 Shard

            $item = $model->where('id',$id);
    
            function deepProcess($item,$depth,$currentRel,$currentRelClasses) {
                foreach ($depth->first()->getPossibleRelations() as $rel) {//eager load any possible relations
                    $newRel = $rel;
                    if ($currentRel !== '') {
                        $newRel = $currentRel.'.'.$rel;// $newRel example, dog.owner.addresses
                    }
    
                    $futureItemCollection = $depth->first()->$rel->first();
                    if (!$futureItemCollection) {
                        continue; // no relationship found from $depth through $rel
                    }
                    array_push($currentRelClasses, get_class($futureItemCollection));//we need to check for the future relationship before the recursive call
                    if (max(array_count_values($currentRelClasses)) > 1) {//if we've hit the same relation more than once then skip current iteration
                        continue;
                    }
    
                    // $fillable = $futureItemCollection->getFillable();
                    // $item = $item->with([$newRel => function($query) use($fillable) {
                    //     call_user_func_array([$query, 'select'], $fillable);//select only fillable fields
                    // }]);
                    $item = $item->with($newRel);//selecting only fillable fields wasn't working, likely due to unexpected fields being required
                    $item = deepProcess($item, $depth->first()->$rel,$newRel,$currentRelClasses);
                }
                return $item;
            }
            $item = deepProcess($item,$item,'',[get_class($item->first())])->get()->toArray();
            dd($item);
    

    Mostly works, it's rough, and I couldn't get the select statement to work (which is why it's commented out and a plain with(relation) is used instead. It works for my purposes though