Декодування проксі-класу в OpenCart

18

Від автора: найчастіше ми приймаємо щось як належне. Якщо це щось працює, як треба, ми не будемо турбуватися про його внутрішній роботі і намагатися зрозуміти, що лежить в основі механізм. Кажучи іншими словами, ми не надаємо значення, поки не починаються проблеми! Мені завжди було цікаво дізнатися про декількох концепціях OpenCart, які використовувалися у фреймворку, і одним з них був проксі клас. Саме про проксі класи OpenCart ми сьогодні і поговоримо.

Що таке проксі-клас?

Ви знайдете різні матеріали в Інтернеті, які визначають термін проксі, але найкраще дає визначення цьому терміну Вікіпедія: Проксі-сервер в його самій загальній формі — це клас, що функціонує як інтерфейс до чогось іншого.

Таким чином, проксі делегує елемент керування об’єкта, який він має намір використовувати, і діє від імені фактичного класу, який був створений. Фактично, шаблон проксі-дизайну є дуже популярним шаблоном, який по мірі необхідності використовується фреймворками. Враховуючи, що обговорення проксі-методу само по собі є такою широкою темою і виходить за рамки цієї статті, я швидко поясню, чому він так часто використовується:

Дійте як обгортка, щоб забезпечити додаткову функціональність.

Затримує створення дорогих об’єктів так званої ледачою завантаженням.

Декодування проксі-класу в OpenCart

Інтернет-магазин на OpenCart!

Створити інтернет-магазин на самій популярної CMS OpenCart з нуля!

Приступити до створення

У контексті OpenCart можна сказати, що шаблон проксі використовується для додавання функціональності в базовий проксі-клас. Сам по собі базовий проксі-клас не дає нічого, крім декількох магічних методів! В наступному розділі ми побачимо, як проксі-клас збагачується під час виконання за рахунок додавання до нього якої-небудь властивості.

Перш ніж перейти до наступного розділу, давайте мигцем поглянемо на проксі-клас. Він знаходиться в system/engine/proxy.php.

{$key};
}
public function __set($key, $value) {
$this->{$key} = $value;
}
public function __call($key, $args) {
$arg_data = array();
$args = func_get_args();
foreach ($args as $arg) {
if ($arg instanceof Ref) {
$arg_data[] =& $arg->getRef();
} else {
$arg_data[] =& $arg;
}
}
if (isset($this->{$key})) {
return call_user_func_array($this->{$key}, $arg_data);
} else {
$trace = debug_backtrace();
exit(‘Notice: Undefined property: Proxy::’ . $key . ‘ in ‘ . $trace[1][‘file’] . ‘ on line ‘ . $trace[1][‘line’] . ‘‘);
}
}
}

Як ми бачимо, він реалізує методи: __get(), __set(), та __call(). Серед них реалізація методу __call() найбільш важлива, і досить скоро ми повернемося до неї.

Як працює проксі-клас з моделлю

У цьому розділі я поясню, як саме виклик $this->model_catalog_category->getCategory($category_id) працює з коробки. Фактично, історія починається з наступного затвердження.

$this->load->model(‘catalog/category’);

Під час початкового завантаження фреймворк OpenCart зберігає всі загальні об’єкти в об’єкті Registry, щоб за бажанням їх можна було отримати. В результаті цього $this->load виклик повертає Loader об’єкт з реєстру.

Клас Loader надає різні методи для завантаження різних компонентів, але те, що нас тут цікавить — це модель методу. Давайте візьмемо фрагмент model методу system/engine/loader.php.

public function model($route) {
// Sanitize the call
$route = preg_replace(‘/[^a-zA-Z0-9_\/]/’, «, (string)$route);
// Trigger the pre events
$this->registry->get(‘event’)->trigger(‘model/’ . $route . ‘/before’, array(&$route));
if (!$this->registry->has(‘model_’ . str_replace(array(‘/’, ‘-‘, ‘.’), array(‘_’, «, «), $route))) {
$file = DIR_APPLICATION . ‘model/’ . $route . ‘.php’;
$class = ‘Model’ . preg_replace(‘/[^a-zA-Z0-9]/’, «, $route);
if (is_file($file)) {
include_once($file);
$proxy = new Proxy();
foreach (get_class_methods($class) as $method) {
$proxy->{$method} = $this->callback($this->registry, $route . ‘/’ . $method);
}
$this->registry->set(‘model_’ . str_replace(array(‘/’, ‘-‘, ‘.’), array(‘_’, «, «), (string)$route), $proxy);
} else {
throw new \Exception(‘Error: Could not load model’ . $route . ‘!’);
}
}
// Trigger the post events
$this->registry->get(‘event’)->trigger(‘model/’ . $route . ‘/after’, array(&$route));
}

Враховуючи вищенаведений приклад, значення $route аргументу catalog/category, значення змінної $route дезінфікується, і після цього викликає подія before, що дозволяє іншим слухачам модуля змінювати значення змінної $route.

Потім він перевіряє наявність потрібного об’єкта моделі в реєстрі. Якщо реєстр містить запитаний об’єкт, подальша обробка не потрібна.

Якщо об’єкт не знайдено, слід завантажити цікаву процедуру, і це той фрагмент, який ми шукаємо в контексті цієї статті. Для початку він готує шлях до файлу запитуваної моделі і завантажує її, якщо вона існує.


$file = DIR_APPLICATION . ‘model/’ . $route . ‘.php’;
$class = ‘Model’ . preg_replace(‘/[^a-zA-Z0-9]/’, «, $route);
if (is_file($file)) {
include_once($file);

}

Після цього він створює об’єкт Proxy.

$proxy = new Proxy();

Тепер зверніть увагу на наступний цикл for — він набагато більше, ніж здається.

foreach (get_class_methods($class) as $method) {
$proxy->{$method} = $this->callback($this->registry, $route . ‘/’ . $method);
}

В нашому випадку значення $class повинно бути ModelCatalogCategory. Фрагмент get_class_methods($class) завантажує через нього всі методи ModelCatalogCategory класу і циклів. Давайте уважно подивимося , що він робить в циклі.

У циклі він викликає метод callback того ж класу. Цікаво те, що метод зворотного виклику повертає викликану функцію, призначену об’єкту $proxy, з ключем в якості імені методу.

Звичайно, проксі-об’єкт не володіє такими властивостями; він буде створений на льоту, використовуючи магічний метод _set()!

Потім об’єкт $proxy додається до реєстру, щоб згодом його можна було отримати, коли це необхідно. Подивіться на ключовий компонент методу set. У нашому випадку це повинен бути model_catalog_category.

$this->registry->set(‘model_’ . str_replace(array(‘/’, ‘-‘, ‘.’),
array(‘_’, «, «), (string)$route), $proxy);

В кінці він викличе подія after, щоб інші модуля слухачі могли змінити значення змінної $route. Це одна частина історії. Давайте розглянемо, що відбувається, коли ви використовуєте у своєму контролері наступне.

Декодування проксі-класу в OpenCart

Інтернет-магазин на OpenCart!

Створити інтернет-магазин на самій популярної CMS OpenCart з нуля!

Приступити до створення

$this->model_catalog_category->getCategory($category_id);

Цей $this->model_catalog_category фрагмент намагається знайти відповідність для ключа model_catalog_category в реєстрі. Якщо вам цікаво, як це зробити, просто погляньте на Controller визначення класу в system/engine/controller.php файл — він надає магічний метод __get(), який це робить.

Як ми тільки що обговорювали, це має повернути $proxy об’єкт, призначений цього конкретного ключа. Потім він намагається викликати метод getCategory на цьому об’єкті. Але проксі-клас не реалізує такий метод, тоді як це буде працювати?

Магічний метод __call() приходить на допомогу! Всякий раз, коли ви викликаєте метод, якого не існує в класі, елемент керування передається методом __call().

Давайте розглянемо його докладно, щоб зрозуміти, що відбувається. Відкрийте файл проксі-класу та зверніть увагу на цей метод.

$key містить ім’я для функції, яка буде викликана — getCategory. З іншого боку, $args містить аргументи, передані методу, і він повинен бути масивом елемента, що містить ідентифікатор категорії, який буде переданий.

Далі, є масив $arg_data, в якому зберігаються посилання на аргументи. Чесно кажучи, я не впевнений, що код $arg instanceof Ref має якийсь сенс. Якщо хто-небудь знає, чому він там, розкажіть, я буду радий дізнатися.

Крім того, він намагається перевірити існування властивості $key в об’єкті $proxy, і це призводить до чогось на зразок цього.

if (isset($this->getCategory)) {

Нагадаємо, що раніше ми призначили всі методи класу ModelCatalogCategory як властивості об’єкта $proxy, використовуючи цикл for. Для вашої зручності я знову ставлю цей код.


foreach (get_class_methods($class) as $method) {
$proxy->{$method} = $this->callback($this->registry, $route . ‘/’ . $method);
}

Таким чином, він повинен бути там, і він також повинен повернути нам викликану функцію! Нарешті, він викликає цю функцію, яка викликається за допомогою call_user_func_array функції, передаючи саму викликану функцію і аргументи методу.

Тепер давайте звернемо нашу увагу на саме визначення викликається функцією. Я візьму фрагмент з callback методу, визначеного у system/engine/loader.php.


function($args) use($registry, &$route) {
static $model = array();
$output = null;
// Trigger the pre events
$result = $registry->get(‘event’)->trigger(‘model/’ . $route . ‘/before’, array(&$route, &$args, &$output));
if ($result) {
return $result;
}
// Store the object model
if (!isset($model[$route])) {
$file = DIR_APPLICATION . ‘model/’ . substr($route, 0, strrpos($route, ‘/’)) . ‘.php’;
$class = ‘Model’ . preg_replace(‘/[^a-zA-Z0-9]/’, «, substr($route, 0, strrpos($route, ‘/’)));
if (is_file($file)) {
include_once($file);
$model[$route] = new $class($registry);
} else {
throw new \Exception(‘Error: Could not load model’ . substr($route, 0, strrpos($route, ‘/’)) . ‘!’);
}
}
$method = substr($route, strrpos($route, ‘/’) + 1);
$callable = array($model[$route], $method);
if (is_callable($callable)) {
$output = call_user_func_array($callable, $args);
} else {
throw new \Exception(‘Error: Could not call model/’ . $route . ‘!’);
}
// Trigger the post events
$result = $registry->get(‘event’)->trigger(‘model/’ . $route . ‘/after’, array(&$route, &$args, &$output));
if ($result) {
return $result;
}
return $output;
};

Оскільки це анонімна функція, вона зберегла значення у формі змінних $registry і $route, які раніше були передані методом зворотного виклику. В цьому випадку значення змінної $route повинно бути catalog/category/getCategory.

Крім того, якщо ми подивимося на важливий фрагмент цієї функції, то побачимо, що він створює екземпляр об’єкта ModelCatalogCategory і зберігає його в статичному $model масиві.


// Store the object model
if (!isset($model[$route])) {
$file = DIR_APPLICATION . ‘model/’ . substr($route, 0, strrpos($route, ‘/’)) . ‘.php’;
$class = ‘Model’ . preg_replace(‘/[^a-zA-Z0-9]/’, «, substr($route, 0, strrpos($route, ‘/’)));
if (is_file($file)) {
include_once($file);
$model[$route] = new $class($registry);
} else {
throw new \Exception(‘Error: Could not load model’ . substr($route, 0, strrpos($route, ‘/’)) . ‘!’);
}
}

І ось фрагмент, захоплюючий ім’я методу, який потрібно викликати з допомогою $route змінної.

$method = substr($route, strrpos($route, ‘/’) + 1);

Таким чином, ми маємо посилання на об’єкт та ім’я методу, який дозволяє нам називати його з допомогою call_user_func_array функції. Саме це робить наступний об’єкт!


if (is_callable($callable)) {
$output = call_user_func_array($callable, $args);
} else {
throw new \Exception(‘Error: Could not call model/’ . $route . ‘!’);
}

В кінці методу отриманий результат повертається через $output змінну. І це ще одна частина історії!

Я навмисно ігнорував код подій pre і post, який дозволяє перевизначити методи основних класів OpenCart. Використовуючи його, можна змінити будь-який метод основного класу і налаштувати його відповідно до потреб. Але давайте зробимо це протягом дещо пізніше, так як це буде занадто багато, щоб вписатися в одну статтю!

Так ось як це взагалі працює. Я сподіваюся, що ви повинні бути більш впевнені у викликах OpenCart і їх внутрішньої роботи.

Висновок

Те, що ми тільки що обговорювали сьогодні, є однією з цікавих і неоднозначних концепцій в OpenCart: використання проксі-методу в рамках підтримки скорочених угод для виклику методів моделі. Сподіваюся, стаття була досить цікава і збагатила ваші знання OpenCart.

Декодування проксі-класу в OpenCart

Інтернет-магазин на OpenCart!

Створити інтернет-магазин на самій популярної CMS OpenCart з нуля!

Приступити до створення