admin Опубликовано 19 марта, 2022 Поделиться Опубликовано 19 марта, 2022 Чаще всего мы принимаем что-то как должное. Если что-то работает так, как ожидалось, мы не будем беспокоиться о его внутренней работе, чтобы понять лежащий в основе механизм. Или, говоря иначе, мы не копаемся в чем-то, пока не будем в беде! Точно так же мне всегда было интересно узнать о нескольких концепциях 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 Цитата Ссылка на комментарий Поделиться на другие сайты Поделиться
Рекомендуемые сообщения
Присоединяйтесь к обсуждению
Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.