0

Товар успешно добавлен в корзину

Оформить заказ

Как правильно расширять функционал интернет-магазинов ReadyScript?

Некоторые сферы деятельности имеют столь уникальную специфику, что даже нашего набора функций может быть недостаточно для автоматизации процессов продаж. Однако, не зря мы называем ReadyScript «платформой» для интернет-магазинов, потому что в ней всё предусмотрено для модификации и расширения функционала разработчиками.

В этой статье мы расскажем, как изменять функционал системы и при этом сохранять возможность централизованного обновления скрипта с серверов ReadyScript.

Для проверки примеров, приведенных в данной статье, потребуется простой модуль со следующей структурой:

  • test //Корневая папка модуля
    • config //Папка для конфигурационных классов
      • file.inc.php //Файл конфигурации модуля
      • module.xml //Файл с описанием модуля
      • handlers.inc.php //Файл обработчиков событий

 

Модуль должен быть установлен в системе и включен. Примеры файла handlers.inc.php будут приведены ниже.
Файл file.inc.php может быть следующего содержания:

Файл module.xml может быть следующего содержания

Во время процедуры обновления системы, перезаписываются файлы, присутствующие в дистрибутиве, т.е. например если обновлению подлежит модуль «Каталог», то папка /modules/catalog/ будет скопирована в ваш проект с заменой всех имеющихся в ней файлов. Из этого следует, что изменения в файлах, находящихся в дистрибутиве, будут иметь смысл только до первого обновления. Выходом может быть только модификация возможностей системы через создание новых файлов, для этого есть следующие механизмы.

1. Обработка системных событий.

Система событий – инструмент, который позволяет изменять стандартный ход работы системы путем установки обработчиков для различных событий системы. Результат работы обработчиков может учитываться системой и корректировать общий ход выполнения программы. Рассмотрим техническую часть событий:

Как только скрипт точки входа index.php получает управление, он ищет у каждого модуля класс Config/MyHandlers или Config/Handlers. Если один из этих классов найден, то у него вызывается метод init, в котором каждый модуль должен подписаться на события. (Конечно, итоговый список подписчиков на события кэшируется и вся эта процедура не повторяется при каждом запуске скриптов). Все обработчики событий также принято описывать в том же классе, в котором происходит подписка. Таким образом, у модуля есть единое место, в котором видна вся «внешняя» деятельность модуля.

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

Вызов событий в ReadyScript происходит в ключевых точках системы, имена событий, как правило, состоят из префикса и изменяемой части. Изменяемая часть обычно несет в себе уточняющий смысл события. Например, все операции записи в базу данных в ReadyScript происходят через ORM объекты, соответственно в системе есть событие, которое вызывается перед записью данных ORM объекта. Имя таких событий формируется следующим образом:

orm.beforewrite.КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА, где:

  • КОРОТКОЕ_ИМЯ_ОБЪЕКТА – формируется путем исключения части Model\Orm из полного имени класса, включая NameSpace. И замены обратных слешей на минус с обрезкой минусов по краям. Например, если имя класса ORM объекта можно записать так: \Catalog\Model\Orm\Product, то его «короткое имя» будет выглядеть так: catalog-product

В данное событие поступают параметр в виде массива, ключами которого являются:

  • orm – объект, который записывается в базу
  • flag – тип сохранения объекта. может принимать значения: INSERT, UPDATE, REPLACE
  • on_duplicate_update_keys – поля, которые следует перезаписать, если такая запись в базе уже существует.

Если внутри обработчика вызвана остановка выполнения события, то сохранение объекта не будет произведено. Напишем пример кода, который будет останавливать обновление товара, и отображать ошибку: “Не хочу обновлять товар!”.

Наверное вы спросите: “А возможно ли с помощью событий расширить, например страницу карточки товара в административной панели и добавить колонку в таблицу товаров в БД?”. Ответ: возможно. И ниже я расскажу как.

Так как все взаимодействие с базой у нас происходит через ORM объекты, нам будет достаточно установить обработчик на событие инициализации ORM Объекта, а в обработчике добавить объекту дополнительные свойства. Напомню, что все свойства объектов задаются во время их первичной инициализации. Инициализация одного класса объектов происходит только один раз за время выполнения PHP скрипта.

Название события инициализации ORM объекта строится следующим образом:

orm.init.КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА

в качестве параметра, в событие поступает инициализируемый ORM объект.

Пример расширения карточки товара:

После добавления такого обработчика в систему, нужно переустановить модуль каталог товаров (через административную панель), чтобы ReadyScript добавил новую колонку в таблицу БД объекта \Catalog\Model\Orm\Product.

В карточке товара новое поле будет располагаться на отдельной закладке, и выглядеть так:

Хочется привести еще один пример возможностей системы событий. Произведем добавление пункта в меню действий над одним товаров, которое располагается в таблице на странице Каталог товаров в административной панели.

Для этого нам потребуется перехватить событие, которое бросает CRUD-контроллер(\RS\Controller\Admin\Crud) после формирования хелпера внешнего вида и перед вызовом действия(action) контроллера. Название события:

controller.exec.КОРОТКОЕ_ИМЯ_КОНТРОЛЛЕРА.ИМЯ_ДЕЙСТВИЯ, где

  • КОРОТКОЕ_ИМЯ_КОНТРОЛЛЕРА - формируется путем исключения части \Controller из полного имени класса, включая NameSpace. И замены обратных слешей на минус с обрезкой минусов по краям.
  • ИМЯ_ДЕЙСТВИЯ – название действия, которое будет вызвано у контроллера(без префикса action)

Например, если вызывается контроллер \Catalog\Controller\Admin\Ctrl с действием Index, то полное название события будет выглядеть так: controller.exec.catalog-admin-ctrl.index

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

Результатом действия этого кода, будет следующий пункт в меню:

 

 

После написания такого обработчика, останется только реализовать класс контроллера \Test\Controller\Admin\Example с условным действием Action, чтобы обработать нажатие на ссылку, которую мы добавили с помощью события.

Полный список событий в ReadyScript с описаниями:

Идентификатор события Правила формирования идентификатора Тип параметра Когда происходит
start - null Перед вызовом диспетчера маршрутов в файле /index.php
stop - null Перед окончанием выполнения скрипта. Все данные уже отданы в output
getroute - array of \RS\Router\RouteAbstract. Массив маршрутов Вызывается в начале работы скрипта. Событие формирует список всех маршрутов, которые будет обрабатывать система
user.auth - array. Элементы:
  • user - объект пользователя \Users\Model\Orm\User
Вызывается после успешной авторизации пользователя
controller.beforewrapoutput - \RS\Controller\AbstractController. Объект текущего фронт-контроллера. Вызывается перед оборачивание сформированного HTML секцией BODY
controller.afterexec.КОРОТКОЕ_ИМЯ_КОНТРОЛЛЕРА КОРОТКОЕ_ИМЯ_КОНТРОЛЛЕРА - формируется из имени класса контроллера включая namesapce, путем замены обратных слешей на минусы, и исключения секции -controller. Минусы по краям удаляются. Вся строка приводится к нижнему регистру. Например, если имя контроллера: \Catalog\Controller\Front\ProductList, то его сокращенное имя будет: catalog-front-productlist string | object. Результат выполнения действия(action) контроллера. Вызывается после выполнения действия контроллера. Вывод еще не отправлен в output
controller.exec.КОРОТКОЕ_ИМЯ_КОНТРОЛЛЕРА.ИМЯ_ДЕЙСТВИЯ

КОРОТКОЕ_ИМЯ_КОНТРОЛЛЕРА - ИМЯ_МОДУЛЯ - аналогично событию controller.afterexec....

ИМЯ_ДЕЙСТВИЯ - имя действия контроллера, без префикса action.

mixed. Результат выполнения метода helperИМЯ_ДЕЙСТВИЯ. Для системных контроллеров - объект \RS\Controller\Admin\Helper\CrudCollection Вызывается классом \RS\Controller\Admin\Crud после выполнения метода helperИМЯ_ДЕЙСТВИЯ. Данное событие используется для изменения внешнего вида разделов административной панели.
module.install.ИМЯ_МОДУЛЯ ИМЯ_МОДУЛЯ - соответствует имени папки, в которой находится модуль \RS\Module\Item Объект установленного модуля. Вызывается сразу после установки модуля
module.beforeuninstall.ИМЯ_МОДУЛЯ ИМЯ_МОДУЛЯ - аналогично событию module.install.... \RS\Module\Item Объект удаляемого модуля. Вызывается перед удалением модуля
module.afteruninstall.ИМЯ_МОДУЛЯ ИМЯ_МОДУЛЯ - аналогично событию module.install.... \RS\Module\Item Объект удаляемого модуля. Вызывается после удаления модуля
orm.init.КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА - формируется из имени класса контроллера включая namesapce, путем замены обратных слешей на минусы, и исключения секции -model-orm. Минусы по краям удаляются. Вся строка приводится к нижнему регистру. Например, если имя контроллера: \Catalog\Model\Orm\Product, то его сокращенное имя будет: catalog-product \RS\ORM\AbstractObject Инициализируемый ORM объект. Вызывается после вызова метода _init у ORM объектов. Данное событие может использоваться для изменения структуры полей ORM объекта
orm.beforewrite.КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА - аналогично событию orm.init.... array. Элементы:
  • orm - ORM объект
  • flag - флаг опреации. Может принимать значения: insert, update, delete
  • on_duplicate_update_keys - поля, которые необходимо обновить в случае если запись уже существует
Вызывается перед записью ORM объекта в базу данных
orm.afterwrite.КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА - аналогично событию orm.init.... array. Элементы:
  • orm - ORM объект
  • flag - флаг опреации. Может принимать значения: insert, update, delete
  • on_duplicate_update_keys - поля, которые необходимо обновить в случае если запись уже существует
Вызывается после записи ORM объекта в базу данных
orm.delete.КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА КОРОТКОЕ_ИМЯ_ORM_ОБЪЕКТА - аналогично событию orm.init.... array. Элементы:
  • orm - ORM объект
Вызывается перед удалением ORM объекта из базы данных
theme.getcontextlist - array of string. Массив контекстов. Вызывается для формирования списка Контекстов, присутствующих в системе. Контекст - определяет отличное отображение темы оформления.
alerts.getsmssenders - array of \Alerts\Model\Sms\AbstractSender. Массив объектов, через которые осуществляется отправка SMS. Вызывается для формирования списка Сервисов, осуществляющих отправку уведомлений по СМС.
export.gettypes - array of \Export\Model\ExportType\AbstractType. Массив объектов, осуществляющих экспорт данных. Вызывается для формирования списка Сервисов, осуществляющих экспорт товаров.
install.complete - - Вызывается сразу после установки копии ReadyScript
cart.addproduct.before - array. Элементы:
  • cart - объект Shop\Model\Cart
  • product_id - ID добавляемого товара
  • amount - количество
  • offer - комплектация товара
Вызывается перед добавлением товара в корзину
cart.addproduct.after - array. Элементы:
  • cart - объект Shop\Model\Cart
  • product_id - ID добавляемого товара
  • amount - количество
  • offer - комплектация товара
Вызывается после добавления товара в корзину
cart.getcartdata - array. Элементы:
  • cart - объект Shop\Model\Cart
  • cart_result - массив со всеми сведениями о корзине
Вызывается перед отдачей результата методом getCartData. Данный метод возвращает все вседения о позициях в корзине.
delivery.gettypes - array of \Shop\Model\DeliveryType\AbstractType. Массив объектов, предоставляющих расчетный класс доставки. Вызывается для формирования списка расчетных классов доставки в системе
payment.gettypes - array of \Shop\Model\PaymentType\AbstractType. Массив объектов, предоставляющих способы оплаты. Вызывается для формирования списка объектов оплаты в системе
order.change - array. Элементы:
  • order_before - \Shop\Model\Orm\Order заказ до изменения
  • order - \Shop\Model\Orm\Order объект текущего заказа
Вызывается при создании и изменении заказа.
printform.getlist - array of Shop\Model\PrintForm\AbstractPrintForm. Массив объектов, предоставляющих сервис печати документов для заказа. Вызывается для формирования списка печатных форм для заказа
getpages - array. Массив массивов с элементами, присутствующими в sitemap:
  • loc - URL страницы
  • lastmod - дата последнего изменения страницы
  • ...
Вызывается для формирования списка страниц, которые нужно включить в sitemap

 

Актуальный список событий всегда представлен в документации для разработчиков

В ReadyScript имеются следующие правила возврата результата функциями-обработчиками события:

  • Функция-обработчик может вернуть null, это будет означать, что в параметры не было внесено изменений, и следующий обработчик получит те же параметры, что и получил текущий.
  • Функция-обработчик может вернуть результат, того же типа что и параметр, это будет означать, что следующий обработчик получит в качестве параметра результат выполнения текущего обработчика.
  • Другой результат выполнения функции-обработчика вызовет исключение.

Эти правила позволяют передавать в событие пустой массив, а на выходе получать наполненный значениями модулей массив. Это удобно для формирования списков.

Функция обработчик может остановить последующую обработку события, путем вызова метода stopPropagation(), у объекта Event, который передается в функцию обработчик вторым аргументом. Это воспринимается системой, как сигнал к отмене действия, перед которым вызвано событие.

Важно: Список событий в системе не является окончательным и будет расширяться, по мере того, как мы будем находить новые ключевые места, в которых может пригодиться событие. Если Вы не смогли реализовать какой-то функционал, из-за отсутствия какого-либо события, просим написать нам в поддержку.

2. Перегрузка маршрутов

Допустим, у нас возникла потребность внести изменения в контроллер оформления заказа. Как это лучше сделать?

Чтобы ответить на вопрос, рассмотрим ситуацию целиком. Управление нашему контроллеру передает маршрут. Именно он устанавливает связь между URL /checkout/... и нашим контроллером \Shop\Controller\Front\Checkout. Другие модули и шаблоны используют id этого маршрута(shop-front-checkout) для формирования ссылок на оформление заказа. Не спроста id маршрута является коротким именем контроллера, так как если у маршрута явно не задан контроллер-обработчик, которому будет переходить управление, то ищется контроллер, короткое имя которого равно id маршрута.

Соответственно решением нашей задачи могло бы стать создание маршрута с тем же id, обрабатывающим те же URL, но который будет передавать управление другому контроллеру. А уже в другом контроллере мы можем реализовать все что угодно.

Встает задача: "Как отключить регистрируемый чужим модулем маршрут и создать маршрут с таким же id своим модулем?". В этом нам поможет возможность подсистемы событий задавать приоритет обработчику событий.

Так как все маршруты вводятся в систему с помощью события getroute, нам достаточно установить в нашем модуле такой приоритет обработчика, чтобы он вызвался в самом конце. Тут уже сработает правило: при добавлении двух маршрутов с одинаковым id, в системе остается последний. Рассмотрим код:

Приведенный выше обработчик направит все запросы на контроллер с классом \Test\Controller\Front\ExampleController, который уже по желанию разработчика можно унаследовать от \Shop\Controller\Front\Checkout, и перегрузить какие-то избранные методы, либо реализовать контроллер полностью с нуля.

P.S. Мы часто используем данный метод при написании индивидуальных контроллеров для клиентов.

3. Замена стандартных обработчиков событий

Если Вы не согласны с тем, как тот или иной модуль обрабатывает события, ReadyScript позволяет перегрузить "чужой" файл обработки событий. Как уже было сказано выше, во время инициализации системы событий, у модулей ищется сперва класс MyHandlers, а затем Handlers, чтобы вызвать у первого найденного метод init.

Так вот, достаточно задекларировать у чужого модуля в пространстве имен \ИМЯ_МОДУЛЯ\Config класс MyHandlers, чтобы указать в нем собственные обработчики событий.

Если не требуется глобальных изменений, можно создать класс MyHandlers, унаследованный от Handlers и перегрузить только некоторые методы.

4. Подмена классов. (НЕ рекомендуемый способ)

Как известно весь функционал в ReadyScript реализован в классах. Все классы модулей и ядра подгружаются с помощью autoload’а. Так вот в правилах подключения скриптов в ReadyScript заложено следующее условие: сперва в папке, соответствующей namespace’у класса ищутся файлы ИМЯ КЛАССА.my.inc.php и если такого не существует, то производится поиск и подключение файла ИМЯ КЛАССА.inc.php

Все классы, присутствующие в дистрибутиве, имеют расширение .inc.php, соответственно любые файлы с расширением .my.inc.php перезаписываться при обновлении не будут, в них можно реализовывать измененный функционал стандартных классов.

Тем не менее, данный способ является НЕ рекомендуемым, но о нем должны знать разработчики. При обновлении в «оригинальных» классах могут изменяться методы, которые могут использовать другие модули, что в целом может привести систему в неработоспособное состояние.


24 ноября 2013 17:51, Артем Полторанин
Рассказать друзьям: