Розширюємо функціонал платформи для інтернет-магазинів ReadyScript

    Сьогодні кожен замовник бажає бачити в своєму інтернет-магазині унікальні фішки. Це можуть бути цікаві накопичувальні знижки, реферальні програми, нестандартні фільтри для пошуку певних товарів, і т.д. Все це вимагає індивідуальних доопрацювань функціонала CMS. У цій статті ми розповімо, які можливості пропонує ReadyScript для створення індивідуальних рішень.
 
 
 
Як і багато CMS, що підтримують автоматичне оновлення, ReadyScript не дозволяє грубо втручатися в код стандартних модулів і ядра, такі зміни будуть скасовані при наступному оновленні системи. Новий функціонал повинен вводитися в систему через нові файли, для цього передбачені відповідні механізми в CMS, про які йтиметься нижче.
 
 1. Обробка подій
Система подій — інструмент, який дозволяє змінювати стандартний хід роботи системи шляхом установки обробників для різних подій системи. Результат роботи обробників може враховуватися системою і коригувати загальний хід виконання програми. Розглянемо технічну частину подій:
 
Як тільки скрипт точки входу index.php отримує управління, він шукає у кожного модуля клас Config / MyHandlers або Config / Handlers. Якщо один з цих класів знайдений, то у нього викликається метод init, в якому кожен модуль повинен підписатися на події. Всі обробники подій також прийнято описувати в тому ж класі, в якому відбувається підписка. Таким чином, у модуля є єдине місце, в якому видно всю його «зовнішня» діяльність.
 
Після формування списку передплатників, скрипт продовжує своє виконання, в ході якого він генерує різні події, які обробляються модулями.
 
Виклик подій в ReadyScript відбувається в ключових точках системи, імена подій, як правило, складаються з префікса і змінюваною частини. Змінна частина зазвичай несе в собі уточнююче сенс події. Наприклад, всі операції запису в базу даних в ReadyScript відбуваються через ORM об'єкти, відповідно, в системі є подія, яка викликається перед записом даних ORM об'єкта. Ім'я таких подій формується таким чином:
 
 orm.beforewrite. & lt; коротке ім'я ORM об'єкта & gt; , де:
 
     
  • коротке ім'я ORM об'єкта — формується з повного імені класу шляхом нехитрої трансформації. Виключається частина Model \ Orm, а слеші замінюються на мінуси. Наприклад, якщо ім'я класу ORM об'єкта можна записати так: Catalog \ Model \ Orm \ Product, то його «коротке ім'я» виглядатиме так: catalog-product
  •  
В дану подію надходять параметри у вигляді масиву, ключами якого є:
 
     
  • orm — об'єкт, який записується в базу
  •  
  • flag — тип збереження об'єкта. може приймати значення: INSERT, UPDATE, REPLACE
  •  
Якщо всередині обробника викликана зупинка виконання події, то збереження об'єкта не буде вироблено. Напишемо приклад коду, який буде зупиняти оновлення товару, і відображати помилку: "Не хочу оновлювати товар!".
 
Для початку буде потрібно створити найпростіший модуль з наступною структурою:
 
 
test //Корневая папка модуля
    config //Папка для конфигурационных классов
        file.inc.php //Файл конфигурации модуля
        handlers.inc.php //Файл обработчиков событий

 
Файл file.inc.php може бути такого змісту:
 
 
<?php
namespace Test\Config;
/**
* Конфигурационный файл модуля
*/
class File extends \RS\Orm\ConfigObject
{
    /**
    * Возвращает значения свойств по-умолчанию
    * @return array
    */
    public static function getDefaultValues()
    {
        return array(
            'name' => 'Тест',
            'description' => 'Тестовый модуль',
            'version' => '1.0.0.0',
            'author' => 'ReadyScript lab.'
        );
    }      
}

 
Модуль повинен бути розміщений в папці / modules , встановлений в системі і включений в розділі Веб-сайт — & gt; Налаштування модулів.
Приклад файлу handlers.inc.php, який перехоплює подія збереження товару:
 
 
<?php
/**
* Данный файл принадлежит модулю Test. 
* Полный путь к текущему файлу /modules/test/config/handlers.inc.php
*/
namespace Test\Config;
 
class Handlers extends \RS\Event\HandlerAbstract
{
    function init()
    {
        $this->bind('orm.beforewrite.catalog-product');
    }
     
    /**
    * Обработчик события "сохранение товара".
    * Имя метода обработчика должно соответствовать событию. 
    * В имени вырезаются все недопустимые для имен функций символы(точки и минусы).
    * Обработчик вызывается без инстанса класса Handlers, поэтому он должен быть статическим.
    * 
    * @param mixed $params - параметры события
    * @param \RS\Event\Event $event - объект текущего события
    * @return void
    */
    public static function ormBeforewriteCatalogProduct(array $params, \RS\Event\Event $event)
    {
        if ($params['flag'] == \RS\Orm\AbstractObject::UPDATE_FLAG) { //Если это обновление товара
            /**
            * Получаем из параметра ORM объект
            * @var \Catalog\Model\Orm\Product
            */
            $product = $params['orm'];
            $product->addError('Не хочу обновлять товар!');
            $event->stopPropagation();
        }
    }    
}

 
Такий модуль заблокує можливість збереження товару у всій системі. Аналогічним чином можна перехоплювати події збереження замовлення, користувача і всіх інших об'єктів в системі, так як всі вони є ORM-об'єктами.
 
"Чи можливо за допомогою подій розширити, наприклад, сторінку картки товару в адміністративній панелі і додати колонку в таблицю товарів в БД? ". Відповідь: можливо. Нижче я розповім як.
 
Так як все взаємодія з базою в системі відбувається через ORM об'єкти, буде досить встановити обробник на подію ініціалізації ORM об'єкта, а в обробнику додати об'єкту додаткові властивості. Нагадаю, що всі властивості об'єктів задаються під час їх первинної ініціалізації. Ініціалізація одного класу об'єктів відбувається тільки один раз за час виконання PHP скрипта.
 
Назва події ініціалізації ORM об'єкта будується наступним чином:
 orm.init. & lt; коротке ім'я ORM об'єкта & gt;
 
як параметр в подія надходить ініціалізіруемих ORM об'єкт. Приклад коду обробника, котрий розширює картку товару:
 
namespace Test\Config;
 
class Handlers extends \RS\Event\HandlerAbstract
{
    function init()
    {
        $this->bind('orm.init.catalog-product');
    }
     
    /**
    * Обработчик события "Инициализация ORM объекта Товар".
    * Не забудьте переустановить модуль Каталог через меню Веб-сайт->Настройка модулей. Каталог товаров -> переустановить
    *
    * @param \Catalog\Model\Orm\Product
    * @return void
    */
    public static function ormInitCatalogProduct(\Catalog\Model\Orm\Product $orm_product)
    {
        $orm_product->getPropertyIterator()->append(array( //Добавляем свойства к объекту
            'Новая закладка', //Закладка. Появится в форме редактирования товара
             
                'test_property' => new \RS\Orm\Type\Integer(array( //Тип поля. Задает тип в базе INT
                    'maxLength' => 1, // Длина поля в базе будет INT(1)
                    'description' => 'Тестовый флаг', //Название поля
                    'checkboxView' => array(1,0), //1 - значение отмеченного checkbox, 0 - для неотмеченного
                    //также здесь можно:
                    //- задавать произвольный шаблон для отображения данного свойства. см. методы \RS\Orm\Type\Integer
                ))
        ));
    }
}

 
Після додавання такого обробника в систему, потрібно перевстановити модуль «каталог товарів» (через адміністративну панель, розділ Веб-сайт- & gt; Налаштування модулей- & gt; Каталог товарів), щоб ReadyScript додав нову колонку в таблицю БД об'єкта \ Catalog \ Model \ Orm \ Product.
 
У картці товару нове поле буде розташовуватися на окремій закладці і виглядати так:
 
 
 
Розглянемо ще один приклад можливостей системи подій. Припустимо, що виникла задача додати пункт в меню дій над одним товаром, яке розташовується в розділі «каталог товарів» в адміністративній панелі. Для вирішення завдання буде потрібно перехопити подія, яка кидає CRUD-контролер (\ RS \ Controller \ Admin \ Crud) після формування хелпера зовнішнього вигляду і перед викликом дії (action) контролера. Назва події
 
 controller.exec. & lt; коротке ім'я контролера & gt;. & lt; ім'я дії & gt; , де
 
     
  • коротке ім'я контролера — формується шляхом виключення частини \ Controller з повного імені класу, включаючи NameSpace. І заміни зворотних слешів на мінус.
  •  
  • ім'я дії — назва дії, яке буде викликано у контролерів (без префікса action)
  •  
Наприклад, якщо викликається контролер \ Catalog \ Controller \ Admin \ Ctrl з дією Index, то повна назва події виглядатиме так: controller.exec.catalog-admin-ctrl.index
 
Нижче наведено приклад обробника цієї події:
 
 
<?php
namespace Test\Config;
class Handlers extends \RS\Event\HandlerAbstract
{
    function init()
    {
        $this->bind('controller.exec.catalog-admin-ctrl.index');
    }
     
    /**
    * Обработчик события
    *
    * @param \RS\Controller\Admin\Helper\CrudCollection $helper - Хелпер визульной части админ. панели
    * @return void
    */
    public static function controllerExecCatalogAdminCtrlIndex(\RS\Controller\Admin\Helper\CrudCollection $helper)
    {
        /**
        * @var \RS\Html\Table\Control - объект: Менеджер таблицы
        */
        $table_control = $helper['table'];
        $columns = $table_control->getTable()->getColumns(); //Получаем колонки таблицы
        foreach($columns as $column) {
            if ($column instanceof \RS\Html\Table\Type\Actions) { //Ищем колонку с действиями
                //в $column - колонка с инструментами для работы с одной записью
                //перебираем инструменты и находим выпадающий список
                foreach($column->getActions() as $action) {
                    if ($action instanceof \RS\Html\Table\Type\Action\DropDown) {
                        //Добавляем свой пункт в выпадающий список действий над
                        //одним товаром в разделе Каталог Товаров в админ. панели
                        $action->addItem(array(
                            'title' => 'ТЕСТОВОЕ ДЕЙСТВИЕ!', //текст ссылки
                            'attr' => array( //атрибуты ссылки
                                //воспользуемся микроформатом crud-get, который выполнит ссылку через ajax и перезагрузит страницу
                                'class' => 'crud-get',
                                //crud-get будет использовать атрибут data-confirm-text, чтобы выдать диалог подтверждения, перед выполнением операции
                                'data-confirm-text' => 'Вы действтельно хотите выполнить ТЕСТОВОЕ ДЕЙСТВИЕ(безопасно)?',
                                //@ перед именем атрибута, означает что это динамический атрибут, т.е. он будет разный для каждой строки
                                //в значение динамического атрибута будут подставляться значения полей из объекта строки.
                                //например, в такой строке: ?id=@id&do=action, будет заменен @id, на значение поля id объекта строки
                                //В данном примере getAdminPattern сформирует следующую ссылку /admin/test-example/?do=Action&id=@id
                                '@href' => \RS\Router\Manager::obj()->getAdminPattern('Action', array(':id' => '@id'), 'test-example'),
                            ),
                        ));
                    }
                }
            }
        }
    }
}

 
Результатом дії цього коду, буде наступний пункт в меню:
 
 
 
Повний список подій в ReadyScript з описами можна знайти в офіційній документації тут .
 
 2. Перевантаження маршрутів
Перевантаження маршрутів дозволяє передати певні адреси (URL) сайту на обробку неродному контролеру.
Припустимо, у нас виникла потреба внести зміни в контролер оформлення замовлення. Як це краще зробити?
 
Щоб відповісти на питання, розглянемо ситуацію цілком. Управління контролеру передає маршрут. Саме він встановлює зв'язок між URL / checkout /… і контролером \ Shop \ Controller \ Front \ Checkout. Інші модулі і шаблони використовують id цього маршруту (shop-front-checkout) для формування посилань на оформлення замовлення. Чи не спроста id маршруту є коротким ім'ям контролера , так як якщо у маршруту явно не заданий контролер-обробник, якому буде переходити управління, то шукається контролер, коротке ім'я якого одно id маршруту.
 
Відповідно, рішенням нашої задачі могло б стати створення маршруту з тим же id, що обробляють ті ж URL, але який буде передавати управління іншому контролеру. А вже в іншому контролері ми можемо реалізувати все що завгодно.
 
Виникає задача — відключити реєстрований чужим модулем маршрут і створити маршрут з таким же id своїм модулем. В цьому нам допоможе можливість підсистеми подій задавати пріоритет оброблювачу подій.
 
Так як всі маршрути вводяться в систему за допомогою події getroute, досить встановити в нашому модулі такий пріоритет обробника, щоб він зголосився в самому кінці. Тут вже спрацює правило: при додаванні двох маршрутів з однаковим id, в системі залишається останній. Розглянемо код:
 
 
<?php
namespace Test\Config;
 
class Handlers extends \RS\Event\HandlerAbstract
{
    function init()
    {
        $this->bind('getroute',
                    null, //callback_class - по умолчанию $this
                    null, //callback_method - по умолчанию формируется из названия события
                    0 //Приоритет. Чем ниже приоритет, тем позже выполняется событие. Приоритет по-умолчанию - 10
        );
    }
     
    /**
    * Возвращает маршруты данного модуля
    */
    public static function getRoute(array $routes)
    {       
        //Добавляем маршрут в систему
        $routes[] = new \RS\Router\Route(
            'shop-front-checkout', //ID маршрута
            array('/checkout/{Act}/', '/checkout/'), //Перечень обрабатываемых URL
            array('controller' => 'test-front-examplecontroller'),  //Параметры по-умолчанию. Направляем запросы на другой контроллер
            'Оформление заказа' //Название маршрута для админ. панели
        );
         
        return $routes;
    }
}

 
Наведений вище обробник направить всі запити на контролер з класом \ Test \ Controller \ Front \ ExampleController, який вже за бажанням розробника можна успадкувати від \ Shop \ Controller \ Front \ Checkout, і перевантажити якісь обрані методи, або реалізувати контролер повністю з нуля.
 
 3. Заміна стандартних обробників подій
Якщо є незгода з тим, як той чи інший модуль обробляє події, ReadyScript дозволяє перевантажити «чужий» файл обробки подій. Як вже було сказано вище, під час ініціалізації системи подій, у модулів шукається спершу клас MyHandlers, а потім Handlers, щоб викликати у першого знайденого метод init.
 
Досить задекларувати у чужого модуля в просторі імен \ імя_модуля \ Config клас MyHandlers, щоб вказати в ньому власні обробники подій.
 
Якщо не потрібно глобальних змін, можна створити клас MyHandlers, успадкований від Handlers і перевантажити тільки деякі методи.
 
 4. Підміна класів. (НЕ рекомендований спосіб)
Весь функціонал в ReadyScript реалізований в класах. Всі класи модулів і ядра довантажуються за допомогою autoload'а. В правилах підключення скриптів закладено така умова: спершу в папці, відповідної namespace'у класу шукаються файли ІМ'Я КЛАССА.my.inc.php і якщо такого не існує, то проводиться пошук і підключення файлу ІМ'Я КЛАССА.inc.php
 
Всі класи, присутні в дистрибутиві, мають розширення .inc.php, відповідно, будь-які файли з розширенням .my.inc.php перезаписуватися при оновленні не будуть, в них можна реалізовувати змінений функціонал стандартних класів.
 
Проте, даний спосіб є НЕ рекомендованим, але про нього повинні знати розробники. При оновленні в «оригінальних» класах можуть змінюватися методи, які можуть використовувати інші модулі, що в цілому може позначитися на стабільності системи.
 
 5. Можливості тем оформлення
Розширення функціоналу не завжди пов'язане з серверної стороною, і часто може торкатися лише фронтенд, наприклад, якщо потрібно додати власний JavaScript на сторінку картки товару.
 
За зовнішній вигляд сайту відповідає тема оформлення. В темі оформлення закладена можливість перевантажувати дефолтні шаблони модулів, для цього служить папка moduleview. Щоб перевантажити шаблон картки товару /modules/catalog/view/product.tpl достатньо скопіювати його за наступним шляхом /templates/{ВАША_ТЕМА_ОФОРМЛЕНИЯ}/moduleview/catalog/product.tpl, після чого можна правити новий файл. Варто зауважити, що всі CSS і JS файли підключаються в шаблонах, це дозволяє перевантажити необхідний шаблон і прописати в ньому нові конструкції підключення стилів і скриптів вже по нових шляхах.
 
Зміни в стандартних шаблонах будуть затерті в процесі централізованого оновлення системи, тому рекомендується заздалегідь створювати власну тему (звичайним клонуванням вмісту папки однієї із стандартних тем) з метою її подальшої модифікації.
 
 Висновок
Можливості платформи ReadyScript дозволяють розробникам втілювати найвитонченіші задумки замовників. Архітектура системи підштовхує до розширення функціоналу системи за допомогою додаткових модулів, що надалі дозволяє повторно використовувати написаний код.
    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.