Разработка плагина для Shop-Script

Уроки » №2. Пошаговое руководство по разработке плагина для Shop-Script на примере плагина «Бренды»

В этой статье подробно рассказывается о создании плагина для приложения 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-Script
  • vendor: идентификатор разработчика
  • version: номер версии плагина
  • frontend: флаг (true/false), обозначающий, предназначен ли плагин для обработки запросов от пользователей фронтенда (в данном случае витрины интернет-магазина)

Подключение плагина

Для того чтобы приложение «узнало» о наличии плагина, добавьте запись о нем в конфигурационный файл wa-config/apps/[app_id]/plugins.php, как показано в примере:

<?php

return array(
    'import' => true,
);

3. Функциональность

Наши дальнейшие действия зависят от того, что должен уметь делать плагин. Есть 2 вида действий, выполняемых плагинами:

  1. Добавить новое или изменить существующее содержимое страниц бекенда или фронтенда приложения.
  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, могут возвращать значения по-разному в зависимости от используемого хука:

  1. вернуть единичное значение (например, строку HTML-кода, как в последнем примере)
  2. вернуть массив значений (если хук допускает добавление нескольких фрагментов данных в разных частях страницы)
  3. не возвращать никакого значения (например, если при обработке хука достаточно только выполнить запрос к базе данных или добавить запись в лог-файл, ничего не отображая на веб-странице).

Ниже показаны примеры методов для каждого из этих случаев:

<?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-заданий). Подробнее читайте об этом в статье «Основы разработки плагинов».