首页 > 解决方案 > 高效加载或缓存视图,即基于许多嵌套关系

问题描述

我有无限的类别,子类别,子子类别等。

结构:

main
--main1
-----main2 ( child of main1 )
-----main3 ( child of main1 )
--main4
test

我的代码:

Category.php model

public function categories()
{
    return $this->hasMany(Category::class);
}

public function childrenCategories()
{
    return $this->hasMany(Category::class);
}

index.blade.php

<select>
    @foreach ($categories as $category)
        <option value="{{ $category->id }}">{{ $category->category_name }}</option>

        @foreach ($cat->childrenCategories as $childCategory)
            @include('partials._child_categroy', ['child_category' => $childCategory])
        @endforeach

    @endforeach
</select>

在 _child_categroy.blade.php 中

<option value="{{ $child_category->id }}">--- {{ $child_category->category_name }}</option>

@if ($child_category->categories)
    @foreach ($child_category->categories as $childCategory)
        @include('partials._child_categroy', ['child_category' => $childCategory])
    @endforeach
@endif

到目前为止一切顺利,但是当我打开调试器时,每个级别的类别都有三个查询,并且有循环!这是我的问题,如果我的链越来越大,那么 foreach 将会增加,这对性能不利!

请问有什么建议吗?

标签: phplaravel

解决方案


这个问题及其潜在问题引起了我的兴趣,因此我想了解更多关于整个问题的信息。我自己创建了一个测试场景。

优化

首先对刀片模板的代码进行一些优化:

// index.blade.php
<select>
    @include('categories.options', ['categories' => $categories, 'level' => 0])
</select>

// options.blade.php
@foreach ($categories as $category)
    <option value="{{ $category->id }}">{{ str_repeat("--", $level) }} {{ $category->name }}</option>
    @include('categories.options', ['categories' => $category->categories, 'level' => $level+1])
@endforeach

然后我生成了一个包含大约 5000 个嵌套类别、8 级深的数据库来测试加载时间。我的假设是,如果您向类别模型添加即时加载,您可以优化加载时间:

// Category.php

// this eager loads ALL nested relations using levels + 1 queries
protected $with = ['categories'];
// this is not needed and doesn't make any difference
protected $with = ['categories.categories'];

结果如下:

                       Time  Queries  Memory
--------------------------------------------------
  No eager loading   12,81 s     5101   112MB
with eager loading    1,49 s        9    31MB
 2 x eager loading    1,54 s        9    31MB

Caching               0,08 s        0     4MB

(stats recorded mainly with debugbar)

因此,正如您所见,急切加载绝对是有意义的。在模型中放置一个也足够了$with = ['categories'],laravel 将立即加载所有嵌套的 realtionships - 整洁!


缓存

因此,唯一真正的解决方案是让网站尽可能快地加载(我能想到的)是缓存。

// create new job in console
php artisan make:job RenderCategoryView

// RenderCategoryView.php
public function handle() {
    // get all top level categories
    $categories = \App\Category::where('category_id', null)->get();
    $html = \View::make('categories.options', ['categories' => $categories, 'level' => 0])->render();
    file_put_contents(resource_path('views/categories/options_cache.blade.php'), $html);
    return true;
}

现在你可以像这样替换@include你的刀片模板:

// index.blade.php
@include('categories.options_cache')

要测试 options_cache 文件的生成,您可以执行以下操作:

php artisan tinker
\App\Jobs\RenderCategoryView::dispatchNow();

在返回索引视图之前,我还删除了现在不必要的数据库查询,新的加载时间为83 ms。并不奇怪,因为现在一切都被缓存了。

要在创建、编辑或删除类别后自动生成新的视图缓存,您应该将其包含在相应的控制器中:

\App\Jobs\RenderCategoryView::dispatch();

在Laravel 文档中阅读有关如何在队列等上调度作业的更多信息。

我的测试代码可以在github上找到。


推荐阅读