
Работая над созданием своего сайта, очень часто приходится разбивать вывод информации страницы на определенные блоки. Например меню, сайдбар, слайдер, форму подписки и тд. При этом, лучшим решением будет сделать такие блоки максимально автономными - со своим классом, шаблоном и тд. То есть сделать данные блоки отдельными виджетами, которые затем можно легко подключать в любом нужном шаблоне.
Это помогает разгрузить контроллер от дополнительного кода, сгруппировать файлы виджетов в одном месте, легко поддерживать их код и использовать одинаковый, простой синтаксис вызова нужного блока. Конечно, для этого, функционал виджетов должен строиться по одному общему образцу.
В связи с этим я предлагаю свое решение, не содержащее ничего лишнего, легко расширяемое и позволяющее с легкостью создавать и использовать виджеты.
Для тех, кого интересует готовое решение, предлагаю установить нижеизложенный код в виде пакета для 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>
Конечно, создавать отдельную секцию для каждого виджета не обязательно.
Все готово, можно пользоваться.
Успехов.
На гите было бы не плохо дать больше примеров и описаний, что бы не искать на других ресурсах.
на
Для того что бы работала инъекция зависимостей
И ещё мне кажется, более логичней хранить шаблоны виджетов в resources->views->widgets, а не в папке App
ответ на комментарий Ярослав от 26.06.2018
Касательно места хранения шаблонов виджетов - делал как в фреймворке yii2 - все в одной папке.