Вступление

Расширение — это инструмент, позволяющий развертывать не только незначительные модификации базовых функций, но и создавать независимые расширения на основе системы. В этой лекции мы подробно рассмотрим все элементы расширения и развернем их на практике в нашем первом расширении. Рекомендуем ознакомиться со всей статьей. Даже не обладая большими знаниями системы, вы рано или поздно сможете создать собственное расширение. Но следование общим принципам позволит большинству разработчиков быстро и легко разобраться в вашем коде.

Первый взгляд

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

 

Изображение 1

На изображении 1 (вверху) показана общая архитектурная схема составных частей расширения. Для удобства восприятия все части разделены на 4 категории.

Основополагающие элементы обозначены как Базовая функциональность. Это минимальный набор, который нам потребуется для выполнения простейших манипуляций внутри системы.

Прежде всего, функции — это статические методы, реализованные в основном классе расширения, или трейты, если ваше расширение это позволяет. Обычно методы не предназначены для прямого запроса, но при необходимости бэкэнд позволяет это сделать через специальные расширения контроллера?module=YourExtensionName&action=adminAction. Для фронтенда такой подход считается небезопасным и недоступен.

Следующим элементом основания являются крючки. Это точки доступа, позволяющие выполнять ваш код в разных местах. Хуки могут быть как в файле php, так и в шаблоне tpl. Как правило, подобные файлы размещаются в подкаталогах hooks и описываются в статическом методе getAdminHooks в основном классе или в классе установки, который мы рассмотрим позже. Вы всегда можете найти список доступных хуков в системе, проверив файл /lib/common/extensions/methodology.txt. И последний элемент первой части расширения — рендеринг. Это процессор класса шаблона. Мы не сделали его общим, чтобы обеспечить достаточную свободу для реализации в расширении. Стоит отметить, что обычно этот классРендер находится в корневой папке расширения. В этом случае файлы шаблона должны быть размещены в представлении каталога на одном уровне.

 

Изображение 2

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

Первый шаг

Предлагаем вам реализовать следующую идею: дополнительное поле на странице редактирования клиента для бэкенда. Для простоты пусть это будет текстовое поле. Администратор сможет ввести слово для определения клиента, например ранг. На данный момент нам достаточно.

Итак, приступим к выполнению этой задачи. Мы предполагаем, что наше новое поле будет называться Rank. Вся задача сводится к тому, чтобы добавить некоторую классификацию этих рангов, назовем наше расширение Customers rank.

Изучив архитектуру системы, вы должны быть знакомы с отправной точкой любого расширения. Это папка /lib/common/extensions/, где находятся все расширения независимо от их назначения. Давайте определим место для нашего нового расширения и создадим новую папку. Следует учитывать, что слова, начинающиеся с заглавной буквы и без пробелов в названии, так называемые CamelCase. Затем создаем папку CustomersRank. В этой папке мы создаем файл CustomersRank.php , содержимое которого будет выглядеть следующим образом:

<?php

namespace common\extensions\CustomersRank;

class CustomersRank extends \common\classes\modules\ModuleExtensions {

}

Важно помнить, что название папки, название основного файла и название класса всегда будут одинаковыми, всегда следует учитывать регистр букв. В нашем файле namespace указывает расширение location, то есть фактически путь к папке. Также необходимо обратить внимание на родительский класс, который мы наследуем. Для всех расширений он будет одинаковым и будет иметь набор готовых инструкций по работе с расширением. На практике это означает, что на данном этапе ваше расширение уже готово к установке и вы сможете увидеть его в списке неустановленных расширений:

 

Изображение 3

Вы можете установить расширение и включить его. Но так как мы не хотели, чтобы наше расширение что-либо делало, мы получаем соответствующий результат. Давайте добавим еще немного контента в наш основной файл. Для этого нам понадобятся элементы из раздела основной функционал. Прежде всего, функции. Это обычный статический метод, расположенный в основном классе. Мы действительно начинаем с этого элемента, так как его работоспособность можно проверить напрямую через контроллер, несмотря на то, что в дальнейшем запросом будет управлять второй элемент из нашего набора — хуки. Добавим в наш класс функцию рендеринга дополнительного поля клиента:

    public static function renderCustomerField() 
    {
        return 'I want render new field';
    }

Теперь, чтобы увидеть возвращенный текст, воспользуемся прямым запросом из бэкенда контроллера через URL-адрес браузера:

extensions?module=CustomersRank&action=renderCustomerField

Это означает, что если URL-адрес вашего магазина http://localhost/oscommerce/, то URL-адрес вашего бэкэнда — http://localhost/oscommerce/admin/, а прямой запрос от бэкэнда контроллера через URL-адрес браузера — http://localhost. /oscommerce/admin/ extensions?module=CustomersRank&action=renderCustomerField    

В зависимости от того, включаете вы расширение или отключаете, результат будет отличаться:

You have not rights for this extension: CustomersRank

или же

I want render new field

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

Следующим шагом, как и было обещано, будет подключение расширения к странице редактирования клиента. Список возможных значений можно увидеть в файле lib/common/extensions/methodology.txt в соответствующем разделе Хуки. Разберемся, что это за список и как понять, какое именно место нужно использовать. Откройте любую страницу редактирования клиента в бэкэнде и обратите внимание на URL-адрес страницы.

admin/customers/customeredit?customers_id=XXX

А теперь обратим внимание на список запросов. Тот, который нас интересует, будет похож на адрес страницы. Выберем те, которые соответствуют нашему требованию:

'customers/customeredit', ''
'customers/customeredit/before-render', ''
'customers/customeredit', 'personal-block'
'customers/customeredit', 'left-column'
'customers/customeredit', 'right-column'

Мы поняли первую ключевую часть, но как понять, что такое второй ключ? Это очень просто! Если второй параметр отсутствует, то это контроллер в момент поста. Мы будем использовать его для сохранения данных в будущем. Остальные четыре значения указывают расположение на странице. Обязательно попробуйте их все, но в нашем примере мы пробуем персональный блок , который соответствует блоку с названием Личные данные. Итак, мы знаем, какой ключ использовать, давайте разберемся, как его использовать. Все точки запроса описываются в функции getAdminHooks как массив. Добавьте новую функцию в свой основной класс:

public static function getAdminHooks() 
    {
        $path = \Yii::getAlias('@common'). DIRECTORY_SEPARATOR. 'extensions'. DIRECTORY_SEPARATOR. 'CustomersRank'. DIRECTORY_SEPARATOR. 'hooks'. DIRECTORY_SEPARATOR;
        return [
            [
                'sort_order' => 10,
                'page_name' => 'customers/customeredit',
                'page_area' => 'personal-block',
                'extension_file' => $path. 'customers.customeredit.personal-block.tpl',
            ],
        ];
    }

Данные о точке перехвата передаются в виде массива. Мы выбрали название файла не случайно, а объединили заголовки страницы и блока. Расширение файла указывает на то, что это шаблон и Smarty используется для обработки этих файлов. Вы можете найти более подробную информацию о синтаксисе и функциях в официальной документации Smarty. Это позволит избежать путаницы в будущем, когда подобных перехватов будет много. Хуки подкатегории обычно выбираются как место для хранения наших файлов перехвата . Давайте создадим каталог и файл в нем. Содержимое файла будет выглядеть следующим образом:

{if $ext = \common\helpers\Acl::checkExtensionAllowed('CustomersRank', 'allowed')}
    {$ext::renderCustomerField()}
{/if}

Мы все сделали правильно, но после обновления страницы мы не видим наш текст. Это происходит потому, что хуки атрибутируются в процессе установки, и нам придется очищать их вручную или переустанавливать расширение. К счастью, очистку кеша можно легко выполнить, и вы будете часто использовать эту страницу в процессе разработки, так как система кэширует много данных. Например, системный кэш кэширует константы и даже структуру таблицы, smarty кэширует предварительно скомпилированные шаблоны, opcache кэширует php-файлы. Теперь нас интересует очистка кеша от хуков :

 

Изображение 4

Нажимаем Flush , ждем всплывающее окно с сообщением об успешной очистке и возвращаемся на страницу редактирования клиента. В нижней части блока мы увидим наш текст:

 

Изображение 5

Но мы еще не достигли нашей цели. Нам придется использовать третий элемент из раздела рендеринга. Это также очень важная часть расширения, которая позволяет использовать смарт-шаблоны для вывода контента. В отличие от функции это отдельный класс, обычно мы размещаем его на одном уровне с основным классом. Создайте файл Render.php в корневой папке расширения.

<?php

namespace common\extensions\CustomersRank;

class Render extends \common\classes\extended\Widget {

    public $params;
    public $template;

    public function run() {
        return $this->render($this->template, $this->params);
    }
}

Поскольку файл и основной файл находятся в одном месте, наше пространство имен остается прежним. Мы унаследуем его от виджета, объявим пару параметров, которые будем рендерить и простенькую функцию рендеринга контента. Также создайте папку с титровальными представлениями там же, где находится файл Render.php. В этой папке мы будем делать наш первый шаблон. Создайте файл со следующим содержимым:

{use class="\yii\helpers\Html"}
<div class="w-line-row w-line-row-1">
    <div class="wl-td">
        <label>Rank</label>
        {Html::input('text', 'rank', $rank, ['class' => 'form-control'])}
    </div>
</div>

Помимо обычного html мы будем использовать фреймворковую функцию yii. Подробнее ознакомиться с работой с полями в yii можно в официальной документации. И, наконец, последнее действие — запрос рендеринга вместо возврата текста в нашей функции renderCustomerField. Давайте изменим его на следующее:

public static function renderCustomerField() 
    {
        $rank = 'I want render new field';
        return \common\extensions\CustomersRank\Render::widget(['template' => 'customer-field', 'params' => ['rank' => $rank]]);
    }

Если вы используете кэширование файлов php, возможно, вам потребуется очистить OPcache, как было описано выше:

 

Изображение 6

Вы можете понять, почему мы используем те или иные классы, проверяя подготовленный элемент формы:

 

Изображение 7

Подведем итоги. На данный момент наше расширение не может сохранять данные. Мы объясним, как это сделать в будущем. Но уже сейчас мы можем создать основной класс, определяемый системой как расширение и позволяющий выполнять установку/удаление. Также мы Ð