Перейти к содержанию
View in the app

A better way to browse. Learn more.

Русскоязычное сообщество Opencart

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

В связи с блокировкой банка QIWI оплата через сайт не возможна.
Для оплаты и получения дополнений просьба писать в личные сообщения 
Admin*у
или в Телеграмм https://t.me/pascha_opencart
После оплаты Вам так же будет доступно скачивание дополнений и обновлений на данном форуме

Разбираем класс прокси в 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.

Последние посетители 0

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

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.