Від автора: найчастіше ми приймаємо щось як належне. Якщо це щось працює, як треба, ми не будемо турбуватися про його внутрішній роботі і намагатися зрозуміти, що лежить в основі механізм. Кажучи іншими словами, ми не надаємо значення, поки не починаються проблеми! Мені завжди було цікаво дізнатися про декількох концепціях 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!
Створити інтернет-магазин на самій популярної 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!
Створити інтернет-магазин на самій популярної CMS OpenCart з нуля!
Приступити до створення