В этой статье подробно рассказывается о создании плагина для приложения Webasyst на примере плагина, подобного плагину «Бренды» для Shop-Script.
При разработке плагина рекомендуем учитывать требования, предъявляемые к публикации программных продуктов в магазине Webasyst.
0. Режим разработки
Перед началом разработки убедитесь, что в вашей установке фреймворка Webasyst включен режим разработки. В этом режиме отключается всяческое кеширование, что облегчает отладку программного кода. Режим разработки можно включить либо в настройках приложения «Инсталлер», либо в файле wa-config/config.php
, как показано ниже:
'debug' => true
1. Файловая структура
Прежде всего необходимо придумать идентификатор для своего плагина. Для создания идентификатора можно использовать латинские буквы, цифры и символы подчеркивания. Для плагина с названием «Бренды» возьмем идентификатор brands
. Идентификатор будет использоваться для формирования имен файлов и классов плагина.
Исходный код плагина хранится в отдельной поддиректории внутри директории wa-apps/[app_id]/plugins/
. В данном случае файлы плагина «Бренды» для Shop-Script необходимо размещать в директории wa-apps/shop/plugins/brands/
. Пути к различным директориям и файлам далее будут указываться относительно этой базовой директории.
В директории плагина должны присутствовать следующие обязательные элементы:
- файл
lib/config/plugin.php
— в этом файле указываются различные параметры конфигурации плагина: наименование, описание, идентификатор разработчика, номер версии и другие; - директория (например,
brands/img/
) с файлом изображения-иконки плагина.
Другие файлы и директории являются дополнительными и создаются разработчиком по мере необходимости в зависимости от функциональности плагина.
2. Конфигурация плагина
В файле lib/config/plugin.php
нужно указать следующие обязательные параметры:
<?php return array( 'name' => 'Brands', // название плагина 'img' => 'img/brands.png' // относительный путь к файлу иконки плагина (16*16px), обычно в поддиректории img/ );
Иконка вместе с названием отображается в разделе «Плагины» бекенда Shop-Script.
Кроме этих 2 параметров, файл конфигурации плагина может содержать и другие данные:
description
: описание плагина, отображается в бекенде Shop-Scriptvendor
: идентификатор разработчикаversion
: номер версии плагинаfrontend
: флаг (true/false
), обозначающий, предназначен ли плагин для обработки запросов от пользователей фронтенда (в данном случае витрины интернет-магазина)
Подключение плагина
Для того чтобы приложение «узнало» о наличии плагина, добавьте запись о нем в конфигурационный файл wa-config/apps/[app_id]/plugins.php
, как показано в примере:
<?php return array( 'import' => true, );
3. Функциональность
Наши дальнейшие действия зависят от того, что должен уметь делать плагин. Есть 2 вида действий, выполняемых плагинами:
- Добавить новое или изменить существующее содержимое страниц бекенда или фронтенда приложения.
- Обрабатывать запросы от пользователей (GET/POST/AJAX).
Пусть плагин «Бренды» отображает на витрине интернет-магазина список брендов в виде ссылок, а покупатели магазина смогут щелкать по каждому названию бренда, чтобы посмотреть список товаров, которые ему соответствуют. Здесь будут задействованы оба вида функциональности плагина: 1) добавить новое содержимое на витрину (список брендов в панели навигации) и 2) обрабатывать запросы от пользователей (формировать страницы со списком товаров в ответ на переход по ссылке с названием бренда).
1. Добавление содержимого (списка брендов)
Для добавления дополнительного содержимого на страницы приложения рекомендуется использовать хуки — специальные места в исходном коде приложения, к которым может «подключиться» плагин. Список хуков Shop-Script опубликован в документации. Например, чтобы добавить список брендов в панель навигации витрины лучше всего использовать хук frontend_nav
.
Для подключения плагина к хуку нужно в основном конфигурационном файле плагина lib/config/plugin.php
добавить параметр 'handlers'
с массивом хуков, к которым необходимо подключить плагин:
'handlers' => array( 'frontend_nav' => 'frontendNav', ),
В ходе разработки после каждого изменения этого массива очищайте кеш, чтобы срабатывали обновлённые обработчики хуков.
В нашем примере плагин будет подключен всего к одному хуку — frontend_nav
.
В качестве ключей массива указываются названия хуков. В качестве значения элемента массива (в правой части) указывается имя метода (в этом примере frontendNav
) основного класса плагина, который необходимо создать по адресу вида lib/[app_id][Plugin_id].plugin.php
. Следовательно, для плагина «Бренды» файл основного класса должен находиться по адресу lib/shopBrands.plugin.php
. В этом файле необходимо описать класс с именем shopBrandsPlugin
, расширяющий базовый класс shopPlugin
.
На самом деле можно было бы наследовать основной класс плагина и от системного классаwaPlugin
, но для Shop-Script рекомендуется выполнять наследование от объявленного в приложении базового классаshopPlugin
, добавляющего дополнительную удобную функциональность для плагинов интернет-магазина.
В классе нужно описать публичный метод, указанный в конфигурационном файле плагина (в данном примере это должен быть метод с именем frontendNav
).
Для именования методов основного класса плагина нет отдельных правил или требований, кроме общесистемных. В частности, необязательно делать имя метода похожим на название хука, к которому он подключен. Решающее значение имеет только следующее правило: имя метода, указанное в конфигурационном файле plugin.php
, должно точно соответствовать имени метода, описанного в основном классе плагина.
Содержимое основного класса плагина «Бренды» с методом frontendNav
, подключенным к хуку frontend_nav
, может иметь следующий вид:
<?php class shopBrandsPlugin extends shopPlugin { public function frontendNav() { $html = ...; //логика формирования HTML-кода для отображения списка брендов return $html; } }
Возврат значений методами плагина
Методы, подключенные к хукам Shop-Script, могут возвращать значения по-разному в зависимости от используемого хука:
- вернуть единичное значение (например, строку HTML-кода, как в последнем примере)
- вернуть массив значений (если хук допускает добавление нескольких фрагментов данных в разных частях страницы)
- не возвращать никакого значения (например, если при обработке хука достаточно только выполнить запрос к базе данных или добавить запись в лог-файл, ничего не отображая на веб-странице).
Ниже показаны примеры методов для каждого из этих случаев:
<?php class shopBrandsPlugin extends shopPlugin { //1) возврат единичного значения (например, при подключении к хуку frontend_nav) public function frontendNav() { $html = ...; //один фрагмент HTML-кода для отображения на веб-странице return $html; } //2) возврат массива значений (например, при подключении к хуку frontend_product) public function frontendProduct() { //логика формирования массива фрагментов HTML-кода для отображения на веб-странице //каждый из этих фрагментов будет вставлен в место, предусмотренное для него темой дизайна (если метод подключен к хуку фронтенда — например, к хуку frontend_product) или шаблоном бекенда $data = array( 'menu' => '...', //фрагмент HTML-кода 'cart' => '...', //фрагмент HTML-кода 'block' => '...', //фрагмент HTML-кода 'block_aux' => '...', //фрагмент HTML-кода ); return $data; } //3) значение не возвращается (например, при подключении к хуку category_delete) public function categoryDelete() { //в таком методе можно выполнить какие-то действия (например, выполнить запрос к базе данных), не возвращая никакого результата, т. к. такой хук (например, category_delete) не предусматривает возврат значения } }
В данном примере мы для простоты имеем дело только с 1-м случаем: возврат единичного значения (т. е. фрагмента HTML-кода).
Добавление содержимого без использования хуков
Описанный выше способ добавления нового содержимого на веб-страницу задействует механизм (интерфейсных) хуков и является рекомендуемым. Интерфейсные хуки предусматривают добавление содержимого в строго определенных местах фронтенда и бекенда.
Если же вам необходимо добавить дополнительное содержимое в других местах, то хуки использовать не удастся. В этом случае можно использовать вызовы статических методов классов (например, основного класса) плагина в теме дизайна. К примеру, если необходимо отобразить в произвольном месте страницы товара на витрине информацию о бренде этого товара, то можно добавить в основном классе плагина статический публичный метод:
<?php class shopBrandsPlugin extends shopPlugin { public function frontendNav() { ... } public static function getProductBrand($product_id) { $brand_name = ...; //логика получения названия бренда для товара с указанным id return $brand_name; } }
Для отображения нужного значения на витрине необходимо вызывать его в коде тем дизайна следующим образом:
{shopBrandsPlugin::getProductBrand($product_id)}
Такой способ добавления дополнительного содержимого на страницы фронтенда не является рекомендуемым, потому что требует от пользователя вручную вносить изменения в код темы дизайна (добавлять вызов статического метода в нужное место шаблона). Поскольку многие пользователи не обладают требуемыми для этого навыками, то рекомендуется воздерживаться от такого способа и стараться реализовать нужную функциональность только путем подключения плагина к хукам приложения.
Например, в некоторых случаях такую функциональность можно реализовать при использовании хуков, позволяющих подключать к страницам витрины дополнительные файлы JavaScript-кода, и добавлять нужное содержимое средствами JavaScript.
Нужно помнить о том, что добавлять вызовы статических методов можно только в шаблоны темы дизайна (фронтенда). Это значит, что изменять содержимое бекенда таким образом не удастся, т. к. шаблоны бекенда недоступны для редактирования средствами интерфейса администрирования Shop-Script.
2. Обработка запросов (формирование страниц со списком товаров)
Запросы пользователей фронтенда и бекенда фреймворка Webasyst обрабатываются специальными PHP-классами — контроллерами. Запросы, отправляемые на разные URL, могут обрабатываться разными либо несколькими общими контроллерами.
Нужный контроллер для обработки запросов к бекенду определяется фреймворком в соответствии с правилами, описанными в документации. Отдельное замечание для плагинов: в URL запроса к бекенду плагина необходимо обязательно указать параметр вида plugin=[plugin_id]
. Например, URL некоторого запроса к бекенду плагина «Бренды» может иметь вид http://yourdomain.ru/webasyst/shop/?plugin=brands&action=someaction
.
Если же необходимо (как в данном примере) обрабатывать запросы от пользователей фронтенда, т. е. витрины интернет-магазина, то правила определения нужного контроллера необходимо указать самостоятельно в отдельном конфигурационном файле lib/config/routing.php
.
Сначала определим, как должны выглядеть URL страниц со списком товаров, соответствующих выбранному бренду. Пусть URL таких страниц имеет вид http://URL_ВИТРИНЫ/brand/<имя бренда>/
, где часть, показанная в угловых скобках, будет динамической, т. е. индивидуальной для каждого бренда. Для этого добавляем в файл routing.php
такую запись:
<?php return array( 'brand/<brand>/' => 'frontend/brand', //'относительный URL страниц витрины' => 'сочетание модуля/экшена' '.../' => '.../...', //другие правила маршрутизации для этого же плагина, если они необходимы );
В ключе элемента массива вместо динамической части URL указано имя параметра (<brand>
), с помощью которого плагин получит имя бренда из адресной строки. В качестве значения элемента массива указано сочетание модуля и экшена, которые должны обрабатывать запросы по адресам такого вида.
В ключах массива с правилами маршрутизации необходимо указывать относительные URL без начального символа косой черты! Это означает, что URL запроса будет отсчитываться от основного URL текущего поселения (витрины) Shop-Script.
Теперь нужно создать PHP-класс экшена, соответствующий указанному сочетанию frontend/brand
, по правилу lib/actions/[app_id][Plugin_id]Plugin[Module][Action].action.php
. В данном примере создадим файл lib/actions/shopBrandsPluginFrontendBrand.action.php
. В файле объявляем класс-наследник базового класса shopFrontendAction
(если необходимо обеспечить единообразие работы экшена плагина с остальными экшенами фронтенда магазина; в противном случае можно наследовать класс экшена от системного класса waViewAction
), с соответствующим именем shopBrandsPluginFrontendBrandAction
и в методе execute
этого класса описываем логику формирования страницы со списком товаров:
<?php class shopBrandsPluginFrontendBrandAction extends shopFrontendAction { public function execute() { $brand = waRequest::param('brand'); //в коде класса можно получить содержимое динамической части ULR, указанной вместо <brand>, с помощью вызова метода waRequest::param($param_name) //значения других динамических параметров, если они указаны в правиле маршрутизации, можно получить в коде экшена аналогичным способом ...//далее формируем список товаров, соответствующих значению динамического параметра, полученного из URL } }
Обработка запросов без использования экшенов
Обрабатывать запросы пользователей плагином можно не только с помощью экшенов, но и с помощью контроллеров, например, при использовании AJAX-запросов.
Допустим, при щелчке по ссылке с названием бренда на витрине нужно только показывать всплывающее окно со списком товаров, а не открывать новую страницу. Для этого средствами JavaScript отправляем GET-запрос по адресу вида http://URL_ВИТРИНЫ/brands/<brand>/
и используем полученный от сервера ответ для отображения списка товаров в отдельной области на странице.
Для того чтобы запрос обрабатывался не экшеном-наследником waViewAction
(или shopFrontendAction
), а контроллером с произвольной логикой, то вместо файла lib/actions/shopBrandsPluginFrontendBrand.action.php
создаем файл lib/actions/shopBrandsPluginFrontendBrand.controller.php
. В файле описываем класс-наследник системного класса waJsonController
с именем shopBrandsPluginFrontendBrandController
и в методе execute
этого класса описываем логику обработки AJAX-запроса в соответствии с документацией, например:
<?php class shopBrandsPluginFrontendBrandController extends waJsonController { public function execute() { $brand = waRequest::param('brand'); //получаем название бренда из URL запроса ... //формируем список товаров, соответствующих полученному названию бренда ... //формируем HTML-код для отображения списка товаров на витрине либо массив данных, визуальное отображение которых на витрине будет задано средствами JavaScript-кода $this->response = array( 'result' => ... //возвращаем в ответ на AJAX-запрос информацию о товарах (HTML-код либо массив данных — в зависимости от особенностей работы вашего плагина) ); } }
4. Шаблоны, локализация, CLI
Плагин, как и приложение, может использовать собственные шаблоны для генерации HTML-кода, собственные строки локализации для корректного отображения на страницах с разными локалями и предоставлять CLI-методы для запуска из командной строки (для настройки cron-заданий). Подробнее читайте об этом в статье «Основы разработки плагинов».