OunknownO OunknownO - 3 months ago 17
PHP Question

Laravel one column sorting 1 column and infinitive nesting

I have categories,subcategories,categories of subcategories that are all in one table. Table looks like this

$table->increments('id');
$table->integer('parent_id')->nullable()->unsigned()->default(NULL);
$table->foreign('parent_id')->references('id')->on('categories')- >onDelete('cascade');
$table->string('name');
$table->timestamps();


controller

$projects = Projects::pluck('post','parent_id');


view

@if (count($projects) > 0)
<ul>
@foreach ($projects as $project)
@include('partials.project', $project)
@endforeach
</ul>
@endif


partials.project

<li>{{ $project['name'] }}</li>
@if (count($project['children']) > 0)
<ul>
@foreach($project['children'] as $project)
@include('partials.project', $project)
@endforeach
</ul>
@endif


I marked children where I would want that subcategories and categories from them go. How can I pull this off

Answer

Assuming you have defined the relation:

// Project.php
public function children()
{
    return $this->hasMany(self::class, 'parent_id');
}

you can call the view as

// ProjectController.php
public function showNested()
{
    $max_level = 3; // the max nesting level you will support

    $eager_load = implode('.', array_map(function () { return 'children'; }, range(1, $max_level)));

    $projects = Project::where('parent_id', null)->with($eager_load)->get();

    return view('projects', compact('projects', 'max_level'));
}

parent view:

// projects.blade.php
<ul>
    @foreach($projects as $project)
        @include('partials.project', [
            'project' => $project,
            'level' => 1,
            'max_level' => $max_level
        ])
    @endforeach
</ul>

partial view:

// partials/project.blade.php
<li>{{ $project->name }}</li>

@if ($level < $max_level && $project->children->count())
    <ul>
       @foreach($project->children as $child)
           @include('partials.project', [
               'project' => $child,
               'level' => $level + 1,
               'max_level' => $max_level
           ])
       @endforeach
    </ul>
@endif

(Update)

Explanation on

$eager_load = implode('.', array_map(function () { return 'children'; }, range(1, $max_level)));

You know what eager loading is, right? Basically, what I wanted is to pre-fetch from the database all nested projects, up to the selected level. So, if you do ->with('children') it will get the projects and their direct children. If you do ->with('children.children') it will get the top-level projects and 2 levels of children.

So, breaking it down:

$max_level = 3;
// variable

range(1, $max_level);
// gives [1, 2, 3]

array_map(
  function () { return 'children'; },
  range(1, $max_level)
);
// gives ['children', 'children', 'children']

implode('.', array_map(function () { return 'children'; }, range(1, $max_level)));
// gives 'children.children.children'