Задача: вывести на каждой странице сайта навигацию в трёх местах (шапка, подвал и мобильная версия). Можно по старинке, перед выводом страницы готовить навигацию вместе с остальными данными. А можно сделать в одном месте и забыть. В этом нам поможет 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 = [];
}
}
Берем данные по ключу, либо, если ключа не существует, выполняем замыкание.
Теперь точно готово! Независимое меню, которое само себе генерирует данные и не нагружает базу данных.
Автор: Алексей Широков
Наш Телеграм-канал — следите за новостями о Laravel.
