Перейти к содержанию
  • В связи с блокировкой банка QIWI оплата через сайт не возможна.
    Для оплаты и получения дополнений просьба писать в личные сообщения
    Admin*у
    После оплаты Вам так же будет доступно скачивание дополнений и обновлений на данном форуме

Разбираем класс прокси в OpenCart


Рекомендуемые сообщения

Чаще всего мы принимаем что-то как должное. Если что-то работает так, как ожидалось, мы не будем беспокоиться о его внутренней работе, чтобы понять лежащий в основе механизм. Или, говоря иначе, мы не копаемся в чем-то, пока не будем в беде!

Точно так же мне всегда было интересно узнать о нескольких концепциях OpenCart, которые использовались внутри фреймворка, и одним из них был класс Proxy. Мне потребовалось некоторое время, чтобы понять его, и я подумал, что с тобой приятно будет делиться, так как всегда интересно узнать что-то новое.

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

Хотя вы найдете различные материалы в Интернете, которые определяют термин прокси, определение из Википедии  довольно легко понять.

Прокси-сервер в его самой общей форме - это класс, функционирующий как интерфейс к чему-то другому.

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

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

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

В контексте 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. Давайте быстро взглянем на фрагмент метода model из system/engine/ oader.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.

Это одна часть истории.

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

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

Фрагмент $this->model_catalog_category пытается найти соответствие для ключа model_catalog_category в реестре. Если вам интересно, просто изучите определение класса Controller в файле system/engine/controller.php - он предоставляет магический метод __get(), который это делает.

Как мы только что обсуждали, это должно вернуть объект $proxy, назначенный этому конкретному ключу. Затем он пытается вызвать метод getCategory для этого объекта. Но класс Proxy не реализует такой метод, так как это будет работать?

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

Давайте рассмотрим его подробно, чтобы понять, что происходит. Откройте файл класса Proxy и обратите внимание на этот метод.

В $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/ oader.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;
 };
 ...

Поскольку это анонимная функция, она сохранила значения в виде переменных $register и $route, которые были переданы ранее методу callback. В этом случае значение переменной $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 events, который позволяет вам переопределять методы основных классов OpenCart. Используя это, вы можете переопределить любой метод основного класса и настроить его в соответствии с вашими потребностями. Но давайте сделаем в другой раз, так как это уже будет слишком много всего, чтобы вписаться в одну статью!

Так вот как это работает. Я надеюсь, что вы должны быть более уверенны в этих коротких вызовах OpenCart и об их внутренней работе.

Заключение

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

Ссылка на комментарий

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...