Работая над созданием своего сайта, очень часто приходится разбивать вывод информации страницы на определенные блоки. Например меню, сайдбар, слайдер, форму подписки и тд. При этом, лучшим решением будет сделать такие блоки максимально автономными - со своим классом, шаблоном и тд. То есть сделать данные блоки отдельными виджетами, которые затем можно легко подключать в любом нужном шаблоне.
Это помогает разгрузить контроллер от дополнительного кода, сгруппировать файлы виджетов в одном месте, легко поддерживать их код и использовать одинаковый, простой синтаксис вызова нужного блока. Конечно, для этого, функционал виджетов должен строиться по одному общему образцу.
В связи с этим я предлагаю свое решение, не содержащее ничего лишнего, легко расширяемое и позволяющее с легкостью создавать и использовать виджеты.

Для тех, кого интересует готовое решение, предлагаю установить нижеизложенный код в виде пакета для Composer. Ссылка на GitHub тут.


Что это нам дает:

  • Удобный синтаксис - вызов любого виджета из шаблона с помощью простой директивы @widget, которая в качестве первого аргумента принимает название виджета, например:
@widget('menu')
В качестве второго аргумента, можно передать массив параметров необходимых для работы виджета.

  • Простые правила создания виджетов.
  • Создание объекта виджета только в случае непосредственного его запроса.
  • Ничего лишнего, только необходимый функционал, разработанный с учетом архитектуры Laravel-5.


Создание функционала для работы с виджетами.


В каталоге app создаем папку Widgets.
В ней создаем папку Contract с файлом контракта (интерфейса) для определения основного метода всех виджетов.

Файл app\Widgets\Contract\ContractWidget.php
<?php
namespace App\Widgets\Contract;

interface ContractWidget {
 /**
  * Основной метод любого виджета, который должен возвращать вывод шаблона:
  *  return view('Widgets::NameWidget', [
  *  'data' => $data
  *  ]);
  */
 public function execute();
}


В папке Widgets создаем основной файл для работы с виджетами app\Widgets\Widget.php
<?php
namespace App\Widgets;

class Widget{
 protected $widgets; //массив доступных виджетов config/widgets.php

 public function __construct(){
    $this->widgets = config('widgets');
 }

 public function show($obj, $data =[]){
    //Есть ли такой виджет
    if(isset($this->widgets[$obj])){
        //создаем его объект передавая параметры в конструктор
       $obj = \App::make($this->widgets[$obj], $data); 
        //возвращаем результат выполнения
       return $obj->execute();
    }
 }
}

Метод-конструктор данного класа, который будет выполнен при создании его объекта, сохраняет в свойство $widgets массив доступных виджетов. Для этого нужно создать данный файл конфигурации.

Файл config\widgets.php:
<?php
/*
 * Ключ: краткое название виджета для обращения к нему из шаблона
 * Значение: название класса виджета с пространством имен
 */
return [
 'menu' => 'App\Widgets\MenuWidget',
 'slider' => 'App\Widgets\SliderWidget',
];

В данном файле нужно указывать все создаваемые виджеты. Тут, для примера, это виджеты меню и слайдера. Позже рассмотрим создание непосредственно файла нужного виджета.

Метод show() основного класса виджетов Widget – это связывающее звено с пользовательскими виджетами. Код выполняется если в конфигурации указан запрашиваемый из шаблона виджет. А именно – создается экземпляр класса запрашиваемого виджета и его конструктору передаются аргументы (если они были переданы при запросе виджета из шаблона). Далее возвращается результат вывода пользовательского виджета.


Данный класс (Widget) нужно зарегистрировать в контейнере Laravel для того, чтобы получать к нему доступ из любой точки приложения. Заодно воспользуемся шаблоном проектирования синглтон, что позволит не создавать каждый раз объект данного класса, а использовать уже существующий его объект.

Регистрировать будем с помощью создания сервис-провайдера. Создаем файл app\Providers\WidgetServiceProvider.php
<?php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App;
use Blade;

class WidgetServiceProvider extends ServiceProvider
{
    public function boot()
    {
    /*
     * Регистрируется дирректива для шаблонизатора Blade
     * Пример обращаения к виджету: @widget('menu')
     * Можно передать параметры в виджет:
     * @widget('menu', [$data1,$data2...])
     */
    Blade::directive('widget', function ($name) {
        return "<?php echo app('widget')->show($name); ?>";
    });
    /*
     * Регистрируется (добавляем) каталог для хранения шаблонов виджетов
     * app\Widgets\view
     */
        $this->loadViewsFrom(app_path() .'/Widgets/view', 'Widgets');
    }

    public function register()
    {
    App::singleton('widget', function(){
    return new \App\Widgets\Widget();
    });
     }
}

Метод register() нашего сервис-провайдера создает экземпляр основного класса виджетов и сохраняет его в сервис-контейнер Laravel для обеспечения быстрого доступа к нему.
В методе boot() создаем директиву @widget для получения доступа к виджетам из шаблонов Blade. Первым аргументом метода directive() указываем название будущей директивы, а в качестве второго параметра передаем анонимную функцию, которая описывает действие при вызове директивы. То есть выводится на экран результат выполнения метода show() экземпляра класса Widget (берем из сервис-контейнера).
В результате, из шаблона вызвать нужный виджет можно так:
@widget('menu')

Кроме того, переданные дополнительные аргументы в виде массива, будут доступны в методе show() и далее переданы классу нужного виджета на обработку, например:
@widget('slider', ['int'=>5])

Так же, в методе boot() добавляем свой каталог для хранения шаблонов виджетов.


После создания, сервис-провайдер нужно зарегистрировать. Для этого добавляем строку
App\Providers\WidgetServiceProvider::class,
в конец массива providers, из файла laravel1.loc\config\app.php
Теперь наш сервис-провайдер будет загружен при старте приложения. Он так же автоматически создаст экземпляр основного класса для работы с виджетами и сохранит его в сервис-контейнере с ключем «widget». Собственно от туда мы его и получаем в методе boot(), когда он понадобился, вызвав функцию-помошник app(), которая достает указанный объект из сервис-контейнера:
app('widget')



Создание виджета.

В качестве примера рассмотрим создание 2-х простых виджетов.

Файлы виджетов нужно создавать в папке app\Widgets.
Класс виджета должен иметь соответствующее пространство имен: namespace App\Widgets. Так же класс виджета должен включать интерфейс ContractWidget и реализовывать его метод execute().
Если виджет должен, для своей работы, получить какие-то данные из контроллера и тд. (передаются в шаблоне), то необходимо предусмотреть метод конструктор для класса виджета с получением аргумента в виде массива параметров.

1. Виджет выводящий меню, файл app\Widgets\MenuWidget.php
<?php
namespace App\Widgets;

use App\Widgets\Contract\ContractWidget;
use App\Menu;

class MenuWidget implements ContractWidget 
{
      public function execute(){
       $data = Menu::all();
        return view('Widgets::menu', [
            'data' => $data
        ]);
     }
}

Для данного виджета данные получаем из базы данных используя модель Menu (для примера). Для подключения шаблона виджета, в качестве первого аргумента функции-помошника view() передаем строку, в которой «Widgets» это название пути к каталогу шаблонов (указывали в методе boot() при создании сервис-провайдера), а после «::» передается название нужного шаблона. Остальные параметры передаются обычным способом как для любого контроллера.

Шаблоны нужно располагать в папке app\Widgets\view.
Файл шаблона app\Widgets\view\menu.blade.php:
@if($data)
<div class="menu classic">
 <ul id="nav" class="menu">
    @foreach($data as $item)
    <li>
        <a href="{{ $item->path }}">{{ $item->title }}</a>
    </li>
    @endforeach
 </ul>
</div>
@endif


2. Виджет выводящий слайдер (вместо слайдера, для примера, выведем текст и параметр полученный из шаблона)
Файл app\Widgets\SliderWidget.php
<?php
namespace App\Widgets;

use App\Widgets\Contract\ContractWidget;
use App\Menu;

class SliderWidget implements ContractWidget{
 protected $int = 0;

 public function __construct($data = []){
    if (isset($data['int'])){
        $this->int = $data['int'];
    } 
 } 

 public function execute(){
    $data = 'Слайдер №' . $this->int;
    return view('Widgets::slider', [
        'data' => $data
    ]);
 } 
}

в данном классе виджета используются дополнительные параметры, для получения которых используется метод-конструктор. Он принимает данные при создании объекта данного класса. В данном примере, конструктор ожидает получить значение из массива по ключу «int» и далее сохраняет его в свойстве $int.

В шаблоне выводим переданные значения, аналогично виджету меню.
Файл app\Widgets\view\slider.blade.php
@if($data)
<div class="slider">
 {{ $data }}
</div>
@endif


Использование.


Тут все очень просто. Для вывода виджета, в нужном месте вашего шаблона (в соответствующих секциях), нужно вставить директиву @widget после которой, в скобках, передать название нужного виджета, которое было указано в конфигурационном файле config\widgets.php. Дополнительно, можно передать параметры в виде массива.
Для рассмотренных выше примеров, вызов виджета вывода меню:
@widget('menu')

вызов виджета вывода слайдера:
@widget('slider', ['int'=>3])

Чтобы уж совсем было понятно с использованием, то допустим, нужно вывести в шаблоне resources\views\index.blade.php который вызывает текущий контроллер
{{--наследуем макет layout--}}
@extends('layouts.layout')
@section('menu')
 @widget('menu')
@endsection
@section('slider')
 @widget('slider', ['int'=>3])
@endsection

и, минималистичный код подключаемого макета, файл resources\views\layouts\layout.blade.php
<!DOCTYPE html>
<html>
 <head>
<meta charset="utf-8">
 </head>
 <body>
@yield('menu')
@yield('slider')
 </body>
</html>

Конечно, создавать отдельную секцию для каждого виджета не обязательно.
Все готово, можно пользоваться.