Автономная навигация. Привязываем данные к шаблону и кэшируем.

ViewComposer в Ларавел

Задача: вывести на каждой странице сайта навигацию в трёх местах (шапка, подвал и мобильная версия). Можно по старинке, перед выводом страницы готовить навигацию вместе с остальными данными. А можно сделать в одном месте и забыть. В этом нам поможет View Composer.

Шаблон навигации

// resources/views/templates/menu.blade.php

@foreach($menu as $item)
<li @if($path == $item->url) class="active" @endif>
    <a href="{{ $item->url }}">{{ $item->name }}</a>
</li>
@endforeach

Мы вызываем его из разных мест разных шаблонов:

// resources/views/templates/header.blade.php

<nav class="menu">
    <div class="container">
        <div class="row">
            <div class="col">
                <ul>
                    @include('templates.menu')
                </ul>
            </div>
        </div>
    </div>
</nav>

// resources/views/templates/hamburger.blade.php

<div class="hamburger__inner">
    <ul class="hamburger__list">
        @include('templates.menu')
    </ul>
</div>

// resources/views/templates/footer.blade.php

<footer class="footer">
    <nav class="menu">
        <div class="container">
            <div class="row">
                <div class="col">
                    <ul>
                        @include('templates.menu')
                    </ul>
                </div>
            </div>
        </div>
    </nav>
</footer>

Давайте привяжем к шаблону навигации необходимые данные. Создадим каталог app/Http/ViewComposers, а внутри него файл MenuComposer.php

namespace App\Http\ViewComposers;

use App\Services\MemoryCache;
use Illuminate\View\View;
use App\Navigation;

class MenuComposer
{
    use MemoryCache;

    public $menu;
    public $path;

    /**
     * Create a menu composer.
     *
     * @return void
     */
    public function __construct()
    {
        $this->menu = $this->menu();
        $this->path = $this->path();
    }

    public function path()
    {
        return parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
    }

    public function menu()
    {
        // get from cache or database
        $item = $this->cache(function() {
            return Navigation::whereActive('1')->orderBy('position')->get();
        });

        return $item;
    }

    /**
     * Bind data to the view.
     *
     * @param  View  $view
     * @return void
     */
    public function compose(View $view)
    {
        $view->with('menu', $this->menu);
        $view->with('path', $this->path);
    }
}

Вы, вероятно, обратили внимание на трейт MemoryCache, но с ним разберемcя позже. А сейчас нам нужно зарегистрироваться в сервис-провайдере.

В каталоге app/Providers создаём файл ComposerServiceProvider.php и привязываем ‘templates.menu‘ к App\Http\ViewComposers\MenuComposer

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class ComposerServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        view()->composer(
            'templates.menu',
            'App\Http\ViewComposers\MenuComposer'
        );
    }

    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Давайте пропишем его в config/app.php в секции провайдеров:

'providers' => [

    ...
    
    App\Providers\ComposerServiceProvider::class,

],

Готово! Теперь при рендере шаблона menu будет срабатывать MenuComposer.php и создавать переменные $menu и $path.

Но меню у нас вызывается не один раз, зачем нагружать базу данных? Давайте кешировать. Для этого и служит трейт MemoryCache

// app/Services/MemoryCache.php

//  Author: jlem

namespace App\Services;

trait MemoryCache
{
    public static $cache = [];

    /**
     * Gets the cached result by key, 
     * or executes the closure if cache key does not exist
     * @param \Closure $closure
     * @return mixed
     */
    protected function cache(\Closure $closure)
    {
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT,2)[1];

        $class = $backtrace['class'];
        $method = $backtrace['function'];
        $args = $backtrace['args'];
        $key = $class . '::'. $method . '.' . md5(serialize($args));

        if (!array_key_exists($key, self::$cache)) {
            self::$cache[$key] = call_user_func_array($closure, $args);
        }

        return self::$cache[$key];
    }

    public static function clear()
    {
        self::$cache = [];
    }

}

Берем данные по ключу, либо, если ключа не существует, выполняем замыкание.

Теперь точно готово! Независимое меню, которое само себе генерирует данные и не нагружает базу данных.

Автор: Demiurge Ash