8 мая 2016 г.

В одной из тем, посвященной оптимизации ПО OpenCart, делался акцент на доработку функционала магазина под конкретные задачи через модификацию системных файлов. Это необходимо, поскольку применение модулей в стандартном исполнении не может решить вопросы, связанные с обработкой данных в ключевых контроллерах. Стоит помнить, что все модули запускаются после исполнения кода ведущего контроллера, в результате чего невозможно повлиять на выводимый контент по умолчанию. Приходится использовать модули на основе Ajax или практиковать модули с применением vQmod.

Очевидно, что в первом случае выполняются, фактически, избыточные обращения к серверу. Например, для модулей фильтрации, где сначала будет получен стандартный список товаров, а затем отфильтрованный, т.е. как минимум двойное обращение к БД по типовым запросам. Эту проблему можно решить вторым вариантом, когда содержимое системных файлов изменяется  в реальном времени таким образом, чтобы список товаров был отфильтрован с первого обращения к серверу. К недостатку данного варианта стоит отнести необходимость создания профайла vQmod для каждого модуля; корректировки его содержимого под конкретные версии OpenCart, а также трудоемкость согласовывания структуры кода, в случае непосредственной правки системных файлов.

Казалось бы, можно забыть об этом, если речь идёт о правке всей программной модели OpenCart. Однако, модульность магазина, по крайней мере для его пользовательской части, важна с точки зрения технической поддержки и оптимизации затрат на ресурсы, когда тот или иной модуль можно отключить, обновить или удалить без ущерба работоспособности магазина в целом. В данной теме будут представлены небольшие системные доработки, которые позволят использовать модули на новом качественном уровне, в частности, позволить влиять им на содержимое контента ключевых контроллеров при сохранении базовой модели ПО OpenCart 1.5+.
* * *

Начнём с моделей запросов к БД, которые загружаются строками вида $this->load->model('catalog/product'); Если посмотреть код файла, то это структура класса ModelCatalogProduct, наследующий структуру абстрактного класса Model. Почему бы модулю фильтрации не расширить класс ModelCatalogProduct и не переопределить такие его функции как getProducts() в рамках ООП PHP? Для этого модуль должен быть, как минимум, загружен в память до вызова модели catalog/product.

В индексном файле ./index.php пользовательской части магазина после строки $controller = new Front($registry); добавляем следующий код:

$query = $db->query("SELECT * FROM " . DB_PREFIX . "extension WHERE startup <> '' ORDER BY priority");

foreach ($query->rows as $result) { $controller->addPreAction(new Action($result['type'] . '/' . $result['code'] . '/' . $result['startup'])); }


Соответственно, в БД для таблицы extension должны быть добавлены новые поля:

ALTER TABLE `extension` ADD COLUMN `startup` VARCHAR(32) NOT NULL AFTER `code`;
ALTER TABLE `extension` ADD COLUMN `priority` INT(3) NOT NULL AFTER `startup`;


Поле startup содержит имя функции модуля (класса его контроллера), которая должна быть выполнена после загрузки модуля, а поле priority отвечает за приоритет загрузки модулей (целочисленные значения). Эти поля запоняются разработчиком модуля при помощи запросов к БД через стандартный метод модуля install() (выполняется при установке модуля через административную панель).

Предварительная загрузка модулей готова, теперь необходимо распространить расширение классов на программную модель OpenCart. Известно, что основой для обмена данными служит реестр registry. Именно через него осуществляется доступ ко всем объектам в OpenCart, в том числе и загрузка моделей запросов к БД. В нашем случае, чтобы строка $this->load->model('catalog/product'); представляла расширенный класс модуля вместо стандартного, в файл ./system/engine/registry.php добавим следующие строки кода:

private $decoration = array();

public function override($parent, $child) {
if (isset($this->data[$parent]) && isset($this->data[$child])) { 
if (is_subclass_of($this->data[$child], get_class($this->data[$parent]))) {
if (isset($this->decoration[$parent])) {$parent = $this->decoration[$parent];
$this->decoration[$parent] = $child;}}}

В этом же файле код функции get() заменим на следующий:

public function get($key) {if (isset($this->data[$key])) {
if (isset($this->decoration[$key])) {return $this->data[$this->decoration[$key]];
} return $this->data[$key]; } }

Готово, теперь возвращаемся к модулю и объявляем в нем функцию, например startup(), код которой будет выглядеть следующим образом:

public function startup() {
$this->load->model('catalog/product'); $this->load->model('catalog/module');
$this->registry->override('model_catalog_product', 'model_catalog_module');}

Примечание: Обе модели должны быть загружены в память до вызова метода $this->registry->override(). Имя функции startup() закрепляется в таблице extension за одноименным полем разработчиком модуля (см. выше).

Конструкция выше переопределяет стандартную модель  catalog/product на  catalog/module, код которой должен быть следующего вида:

class ModelCatalogModule extends ModelCatalogProduct {
public function getProducts($data = array()) {
// Do stuff...
return parent::getProducts($data);
}

где можно модифицировать входные данные фильтра $data и передать их дальше родительской модели или полностью выполнить свой уникальный запрос к БД.

Кстати, подобный функционал может быть распротранен не только на модели, но и на любой другой объект, объявленный в реестре registry. Например, можно расширить контроллер корзины:

class ExtendedCart extend Cart {
// Do stuff...
}
public function startup() {
$this->registry->set('extended_cart', new ExtendedCart($this->registry));
$this->registry->override('cart', 'extended_cart');}

* * *

Однако доработки, рассмотренной выше, очевидно недостаточно для непосредственного воздействия на контент ведущего контроллера. Хотелось бы вносить некоторые изменения в пользовательский интерфейс, например модификацию текста элементов интерфейса или текста информационных сообщений. Другими словами, необходим, как минимум, доступ к массиву данных контроллера $data, прежде чем произойдет его дамп в шаблон.

В файл ./system/engine/registry.php добавляем следующий блок кода:

private $controller_data= array();

public function set_controller_data($controller, $data) {
$this->controller_data[$controller] = $data;}

public function get_controller_data($controller) {
if (isset($this->controller_data[$controller])) {
return $this->controller_data[$controller];}}

Затем в файле ./system/engine/controller.php в теле функции __construct() добавим две строки:

$this->data = new ArrayObject();
$registry->set_controller_data(get_class($this), $this->data);

В этом же файле находим строку extract($this->data); и заменяем на extract((array)$this->data);

На этом все. Теперь, чтобы воспользоваться новой возможностью в том или ином модуле (предварительная загрузка модуля не требуется), достаточно выполнить следующие строки (для категории):

if (class_exists('ControllerProductCategory')) {
$controller_data = $this->registry->get_controller_data('ControllerProductCategory');}

Перебирая содержимое массива $controller_data можно изменять, удалять или добавлять переменные контента для класса ControllerProductCategory (категории) напрямую (см. Objects and references). Например, обработать изображения товаров, добавить подсказки к ним и т.п. Здесь уже дело за разработчиком.

Если ведущий контроллер необходимо защитить от доступа к его данным, то в теле его класса достаточно добавить инициализацию по умолчанию:

public function __construct($registry) {$this->registry = $registry;}


* * *

В данной статье мы рассмотрели два независимых решения, которые позволяют значительно расширить возможности модулей в OpenCart не прибегая к таким излишествам как vQmod или Ajax. Конечно подход к поставленной перед модулем задаче зависит уже от знаний и навыков разработчика.

Условия пользования: Без каких либо ограничений, кроме распространяемых сборок OpenCart и его клонов, где должна присутствовать ссылка на блог автора решения.

1 комментарий:

  1. Ответ на вопрос, который задают уже не первый раз: Нет, доработка не повлияет, ни на текущую работоспособность OpenCart, ни на существующие модули в системе.

    ОтветитьУдалить

  • RSS
  • Twitter
  • Youtube