Декодирование прокси-класса в OpenCart

Декодирование прокси-класса в OpenCart

От автора: чаще всего мы принимаем что-то как должное. Если это что-то работает, как нужно, мы не будем беспокоиться о его внутренней работе и пытаться понять лежащий в основе механизм. Говоря другими словами, мы не придаём значения, пока не начинаются проблемы! Мне всегда было интересно узнать о нескольких концепциях OpenCart, которые использовались во фреймворке, и одним из них был прокси класс. Именно про прокси классы OpenCart мы сегодня и поговорим.

Что такое прокси-класс?

Вы найдете различные материалы в Интернете, которые определяют термин прокси, но лучше всего даёт определение этому термину Википедия: Прокси-сервер в его самой общей форме — это класс, функционирующий как интерфейс к чему-то другому.

Таким образом, прокси делегирует элемент управления объекту, который он намеревается использовать, и действует от имени фактического класса, который был создан. Фактически, шаблон прокси-дизайна является очень популярным шаблоном, который по мере необходимости используется фреймворками. Учитывая, что обсуждение прокси-метода само по себе является такой широкой темой и выходит за рамки этой статьи, я быстро объясню, почему он так часто используется:

Действуйте как обертка, чтобы обеспечить дополнительную функциональность.

Задерживает создание дорогостоящих объектов так называемой ленивой загрузкой.

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

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

Приступить к созданию

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

Прежде чем перейти к следующему разделу, давайте мельком взглянем на прокси-класс. Он находится в system/engine/proxy.php.

<?php
class Proxy {
 public function __get($key) {
 return $this->{$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('<b>Notice</b>:  Undefined property: Proxy::' . $key . ' in <b>' . $trace[1]['file'] . '</b> on line <b>' . $trace[1]['line'] . '</b>');
 }
 }
}

Как мы видим, он реализует методы: __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 model object
 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 model object
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.

Автор: Sajal Soni

Источник: http://code.tutsplus.com/

Редакция: Команда webformyself.

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

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

Приступить к созданию
Самые свежие новости IT и веб-разработки на нашем Telegram-канале

Интернет-магазин на OpenCart

Создание магазина с уникальным шаблоном на CMS OpenCart

Научиться

Метки:

Похожие статьи:

Комментарии Вконтакте:

Комментарии Facebook:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Я не робот.

Spam Protection by WP-SpamFree