MODX. Схема работы компонентов / дополнений MODX
Отредактировано: 26 Июня 2019
Среди множества статей по созданию компонентов MODX, многие устарели, а некоторые привязаны к необязательным знаниям, таким как: работа с удаленными репозиториями, или в конкретных необязательных программах.
Тут я постараюсь собрать схемы, по которым работают компоненты в текущей версии MODX (на момент написания — версия 2.7) с помощью которых можно научиться понимать их структуру, и писать свои, без привязки к чужим статьям. Если увидели ошибку, или есть какой-либо иной комментарий по схеме работы компонента, пожалуйста, отправьте свое виденье через форму обратной связи.
Итак, компоненты в MODX распространяются транспортными пакетами (zip файлами). При установке они могут совершать различные действия: создавать таблицы, менять системные настройки, копировать файлы, создавать элементы и т.п.
Из чего состоит компонент? Компонент это набор файлов с определенной логикой. В него могут входить js, css, php и другие файлы, скомпанованные в определенную структуру, и вот пример организации этой структуры (количество файлов и папок может меняться в зависимости от необходимости наличия в компоненте, но структуру и стиль наименования стоит соблюдать, для нормальной работы и последующей понимания другими разработчиками):
Базовая структура компонентов MODX Revolution
- assets — файлы, которые должны быть доступны снаружи, из интернета.
- components — каталог в котором хранятся файлы компонентов, которые должны быть доступны из браузера.
- имя_компонента
- connector.php — файл обеспечивающий связь между внешними файлами и теми что обрабатываются на сервере.
- файлы и каталоги компонента — чаще всего встречаются: каталоги js, css, img. Если компонент содержит в себе панель управления менеджера, то в структуру добавится следующая конструкция:
- js — каталог содержащий js файлы.
- mgr — каталог содержащий файлы связанные с панелью управления менеджера.
- имя_компонента.js — файл, в котором инициализируется главный объект страниц менеджера компонента.
- sections — каталог, в котором лежат файлы, связанные со страницами компонента в панели менеджера.
- home.js — файл скрипта который рендерит главную страницу компонента.
- widgets — каталог, в котором лежат файлы, связанные с виджетами, которые подключаются на страницах панели менеджера компонента. Чьобы узнать, какие основные стандартные ExtJS-объекты есть в MODX, можно посмотреть в папку /manager/assets/modext/widgets/
- home.panel.js — файл скрипта виджета «Панель», который содержит в себе заголовок, описание и табы (вкладки).
- _____.grid.js — файл скрипта виджета работающего с таблицой элементов modExtra. Основной рабочий файл, который выводит записи из БД и позволяет редактировать их во всплывающих окошках.
- mgr — каталог содержащий файлы связанные с панелью управления менеджера.
- js — каталог содержащий js файлы.
- имя_компонента
- components — каталог в котором хранятся файлы компонентов, которые должны быть доступны из браузера.
- core — файлы, составляющие ядро MODX.
- components — каталог в котором хранятся файлы компонентов, которые должны быть скрыты для доступа из браузера.
- имя_компонента
- файлы и каталоги компонента — есть определенный устоявшийся набор каталогов компонента:
- controllers — файлы для подготовки страниц админки (контроллеры). Загружают нужные скрипты и стили.
- docs — история изменений, инструкция и лицензия. Эти файлы участвуют в описании пакета с компонентом.
- elements — устанавливаемые чанки, сниппеты и прочие возможные наследники modElement.
- lexicon — словари компонента, обычно только en и ru. Подробнее о словарях.
- model — директория с объектами компонента и моделями таблиц для баз данных, обычно только для MySql. Здесь же находится и основной рабочий класс компонента.
- schema — тут хранятся XML схемы компонента, формирующие связи с БД.
- имя_компонента.mysql.schema.xml — XML схема компонента для связи с БД MySql.
- имя_компонента.mssql.schema.xml — XML схема компонента для связи с БД MsSql (другие делаются по аналогии).
- имя_компонента
- mysql — каталог, в котором хранятся файлы обеспечивающие связь с базой MySql (чтобы добавить связи с другими типами баз, надо создать соответствующие каталоги с аналогичными файлами). При создании компонента с помощью ModExtra, файлы внутри этого каталога перезаписываются при каждой генерации модели по новой схеме.
- имя_объекта.class.php — расширение объекта класса, для связи с БД MySql.
- имя_объекта.map.inc.php — карта объекта, которая используется для MySql. В ней прописываются все поля, индексы и связи, которые задаются в схеме XML, которая находится по адресу model/schema/имя_компонента.mysql.schema.xml
- metadata.mysql.php — общая информация о том, какие обхекты есть в компоненте. Этот представление используется для xPDO, создаётся один общий объект, который расширяется при работе в определённой системе.
- имя_объекта.class.php — объекты определенного класса компонента, и все его основные методы.
- mysql — каталог, в котором хранятся файлы обеспечивающие связь с базой MySql (чтобы добавить связи с другими типами баз, надо создать соответствующие каталоги с аналогичными файлами). При создании компонента с помощью ModExtra, файлы внутри этого каталога перезаписываются при каждой генерации модели по новой схеме.
- schema — тут хранятся XML схемы компонента, формирующие связи с БД.
- processors — файлы, выполняющие какую-то одну небольшую пользовательскую функцию. Служат, как правило, для обработки запросов от админки.
- index.class.php — сюда выносится адрес коннектора, особенно если в дополнении используется множество js файлов.
- файлы и каталоги компонента — есть определенный устоявшийся набор каталогов компонента:
- имя_компонента
- components — каталог в котором хранятся файлы компонентов, которые должны быть скрыты для доступа из браузера.
- manager — иногда файлы относящиеся к админ панели /manager/ размещают здесь.
- components — каталог в котором могут храниться файлы компонентов, которые должны быть доступны в админ панели.
- имя_компонента
- файлы и каталоги компонента — все как в предыдущих примерах...
- имя_компонента
- components — каталог в котором могут храниться файлы компонентов, которые должны быть доступны в админ панели.
Пространство имен
Любой компонент начинается с создания «пространства имен» (namespaces). Оно создается в системном меню, в одноименном пункте.
В статье будет рассмотрен компонент с названием Sample. Итак:
- Нажимаем кнопку «создать новое»;
- В строке Core Path прописываем путь к ядру: {core_path}components/sample/
- В строке Assets Path прописываем путь к активам: {assets_path}components/sample/
Если разработка ведется с использованием других адресов, соответственно указывайте их, выше указаны стандартные адреса.
Файл connector.php
Файл assets/components/sample/connector.php служит мостом для объединения логики внешних и внутренних файлов, из открытого каталога assets и закрытого каталога core. Пример стандартного файла коннектора:
// Добавляем если надо отловить ошибки Ajax
ini_set('display_errors', 1);
ini_set('error_reporting', -1);
// Подключаем MODX
require_once dirname(dirname(dirname(dirname(__FILE__)))).'/config.core.php';
require_once MODX_CORE_PATH.'config/'.MODX_CONFIG_KEY.'.inc.php';
require_once MODX_CONNECTORS_PATH.'index.php';
// Получаем настройки, подключаем и инициализируем класс
$corePath = $modx->getOption('sample.core_path',null,$modx->getOption('core_path').'components/sample/');
require_once $corePath.'/model/sample/sample.class.php';
$modx->sample = new sample($modx);
// Получаем лексические значения
$modx->lexicon->load('sample:default');
// Указываем путь к процессорам и запускаем обработчик
$path = $modx->getOption('processorsPath',$modx->sample->config,$corePath.'processors/');
$modx->request->handleRequest(array(
'processors_path' => $path,
'location' => '',
));
Если нужна возможность подгружать конфиги из каталога в корне, заменяем код подключения:
if (file_exists(dirname(dirname(dirname(dirname(__FILE__)))) . '/config.core.php')) {
require_once dirname(dirname(dirname(dirname(__FILE__)))) . '/config.core.php';
}
else {
require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/config.core.php';
}
Методы подключения файлов с классами разнятся от разработчика к разработчику, и во многом зависят от личных предпочтений. Узнать чуть больше об их различиях можно в статье посвященной методам loadClass(), getService(), addPackage() и addExtensionPackage().
Основной класс компонента
Основной класс компонента находится по адресу: core/components/sample/model/sample/sample.class.php
В нем содержатся методы необходимые компоненту, в каждом случае свои, но, как правило, присутствует метод __construct(), в котором задается переменная Sample::config. Доступ к переменной $sample->config.
Типовой набор для метода __construct():
private $modx; // Объявляем переменную, которая будет содержать ссылку на объект modx
function __construct(modX &$modx, array $config = array()) {
$this->modx =& $modx;
$corePath = $this->modx->getOption('sample_core_path', $config, $this->modx->getOption('core_path') . 'components/sample/');
$assetsUrl = $this->modx->getOption('sample_assets_url', $config, $this->modx->getOption('assets_url') . 'components/sample/');
$connectorUrl = $assetsUrl . 'connector.php';
$this->config = array_merge(array(
'assetsUrl' => $assetsUrl,
'cssUrl' => $assetsUrl . 'css/',
'jsUrl' => $assetsUrl . 'js/',
'imagesUrl' => $assetsUrl . 'images/',
'connectorUrl' => $connectorUrl,
'corePath' => $corePath,
'modelPath' => $corePath . 'model/',
'chunksPath' => $corePath . 'elements/chunks/',
'templatesPath' => $corePath . 'elements/templates/',
'chunkSuffix' => '.chunk.tpl',
'snippetsPath' => $corePath . 'elements/snippets/',
'processorsPath' => $corePath . 'processors/'
), $config);
$this->modx->addPackage('sample', $this->config['modelPath']);
$this->modx->lexicon->load('sample:default');
}
Работа компонента в web
Чтобы при отладке видеть, есть ли php ошибки, добавьте в системный файл /index.php следующие строки:
ini_set('display_errors', 1);
ini_set('error_reporting', -1);
Если необходимо реализовать доступ к разным процессорам исходя из указанных параметров, можно реализовать распределение подобным образом:
if (empty($_REQUEST['action'])) {
die('Access denied');
} else {
$action = $_REQUEST['action'];
}
switch ($action) {
case 'web/vote':
// нужное действие
break;
case 'web/save':
// другое действие
break;
default:
$message = $_REQUEST['action'] != $action
? 'sample_err_register_globals'
: 'sample_err_unknown';
$data = json_encode(array(
'success' => false,
'message' => $modx->lexicon($message),
));
}
Работа компонента в админ. панели (CMP — Custom Manager Pages)
Документация по MODExt, расширению ExtJS, предоставляющему дополнительные функции для работы с админкой MODX.
Чтобы при отладке видеть, есть ли php ошибки, добавьте в системный файл /manager/index.php следующие строки:
ini_set('display_errors', 1);
ini_set('error_reporting', -1);
Контроллеры CMP
Контроллер — это специальный php файл, лежащий в директории core компонента, и наследующий класс modManagerController. Его назначение — подготовить нужные файлы для вывода страницы. Иными словами, нажатие на пункт меню какого-нибудь приложения, вызывает соответствующий контроллер, который запускает ряд указанных в нем действий.
Устоявшиеся названия файлов контроллеров: index.class.php или controller_name.class.php, но в более ранних версиях MODX, или других особых случаях они могут отличаться.
К контроллеру можно обратиться по адресу:
/manager/?a={controller}&namespace={namespace}
- {controller} — имя контроллера (php файла, например index (index.class.php)). У компонента может быть несколько контроллеров, к примеру основной index в корне, и пара дополнительных в папке controllers.
- {namespace} — пространство имен созданное для компонента.
При этом, система будет искать внутри контроллера класс {namespace}{controller}ManagerController. В случае если этого класса в файле нет, появится ошибка.
До версии MODX 2.3 использовалась другая система, основанная на modAction, при этом, ссылка на страницу компонента выглядела так: manager/?a=число, и система искала в контроллере следующую конструкцию:
// Подключаем контроллер по умолчанию, из папки controllers
class IndexManagerController extends modExtraManagerController {
public static function getDefaultController() {
return 'home'; // указание имени файла, который подключается по умолчанию
}
}
С новой системой, такая схема не сработает.
Основные методы контроллеров
- getPageTitle — выводит текст для тега title страницы CMP;
- getTemplateFile — метод отдаёт html шаблон страницы, который будет распарсен Smarty. Учитывая, что вся админка сделана на ExtJS, метод просто выводит тег, который будет использован для отрисовки компонента ExtJs. Файл-шаблон /core/components/component_name/elements/templates/home.tpl выглядит так:
<div id="sendex-panel-home-div"></div>
-
getLanguageTopics — метод возвращает массив словарей, которые будут использованы в работе компонента;
-
checkPermissions — проверять или нет права на доступ к странице. Можно указать их в настройке меню, чтобы закрыть страницу от кого-то. Например, можно указать save_document, чтобы пускать только юзеров с правом редактирования документов.
-
loadCustomCssJs — основной метод контроллера, именно он загружает все нужные скрипты и стили для работы страницы.
Отладка при работе с контроллерами
Проверить срабатывают ли изменения при синхронизации проекта с сервером можно вставив сразу после <?php следующую строку:
<?php
echo 'Hello world';die;
Добавление страницы компонента в меню Extras
Начиная с версии MODX 2.3 изменился способ создания меню. Из админ панели меню можно создать в одноименном пункте, в системном меню.
- Нажимаем создать новое.
- В строке Parent указываем — Extras.
- Заполняем Lexicon Key — sample.menu_title (предварительно добавив такой ключ в лексические файлы core/components/sample/lexicon/ ).
- В строке Action указываем ключ коннектора, к которому должен обращаться пункт меню. В нашем случае index.
- В строке Namespace указываем пространство имен компонента. В нашем случае sample.
В результате в меню появится пункт, с адресом /manager/?a=index&namespace=sample, увидев этот адрес, MODX будет искать коннектор с именем index.class.php в каталоге sample.
Описание строк окна меню:
- parent — родительский пункт меню (по умолчанию components).
- lexicon key — текст или лексическое ключ, который должен выводиться в меню.
- action — имя коннектора, к которому должен обращаться пункт меню.
- description — описание пункта меню.
- parameters — дополнительные параметры, которые должны быть добавлены к url.
- handler — если установлен, то вместо обращения к конектору, будет выполнен указанный JavaScript код.
- namespace — пространство имен, к которому привязан пункт.
- permission — указывается уровень доступа, при котором виден этот пункт.
- icon — html символы, отвечающие за вид вывода меню. Может указываться как иконка так и текст.
Порядок отображения устанавливается путем перетаскивания пунктов в списке.
ExtJs и каталог mgr
ExtJS — это javascript фреймфорк, куда более мощный чем jQuery, он не требует HTML верстки, а генерирует ее сам, на лету. Но, применение этого фреймворка в качестве языка для интерфейса админ-панели cms зарекомендовало себя не лучшим образом, по большей части из-за редкости применения, поэтому в сообществах стабильно возникает вопрос о переписывании этой части админки. Тем не менее, на данный момент используется именно этот фреймворк.
Основные моменты для понимания ExtJS:
- Все элементы ExtJS являются объектами javascript, со своими свойствами. Принцип похож на Объектно-Ориентированное Программирование (ООП) в PHP. Когда вы пишите какой-то свой виджет, нужно просто унаследовать и расширить уже готовый от авторов ExtJS или MODX. Например:
- пишите свою таблицу — расширяете MODx.grid.Grid
- в таблицах будут всплывающие окошки — пишете виджет, расширяющий MODx.Window
- Практически все видежты или уже работают, или умеют работать через Ajax. Все свои данные они отправляют на сервер без перезагрузки страницы, и ответ получают в формате JSON.
Логику для панели менеджера можно написать и в одном js файле, но, ниже показана структура, которая позволяет писать легко модифицируемые и дополняемые компоненты, без повторения блоков кода.
assets/components/component_name/js/mgr/component_name.js — тут объявляется объект компонента.
// В этом файле происходит создание главного объекта, его привязка к xtype и группам
// Создаём компонент - главный объект
var Sample = function(config) { // Создаём объект Sample
config = config || {}; // Определяем настройки объекта
Sample.superclass.constructor.call(this,config); // запускаем конструктор родительского класса
};
// Расширяем объект MODx.Component нашим объектом
Ext.extend(Sample,Ext.Component,{
page:{},window:{},grid:{},tree:{},panel:{},combo:{},config: {},view: {} // Перечисляем группы, внутрь которых будем "складывать" объекты
});
// Регистрируем xtype для нашего объекта
Ext.reg('sample',Sample);
// Cоздаем экземпляр нашего класса,
// чтобы можно было обращатсья к его свойствам
Sample = new Sample();
Далее переходим в файл assets/components/component_name/js/mgr/section/index.js, для настройки вывода панели на странице:
// В этом файле происходит определение блоков, которые должны выводиться на странице,
// и добавление xtype, для вывода на экран.
Ext.onReady(function() {
MODx.add({ // Выводим xtype страницы на странице. Ниже создаем его.
xtype: 'sample-page-home'
// можно указать имя дива, который указан в шаблоне, загружаемом контроллером.
//,renderTo: 'sample-panel-home-div'
});
});
// Устанавливаем параметры для объекта page.home
Sample.page.Home = function(config) {
config = config || {};
Ext.applyIf(config,{
components: [{
xtype: 'sample-panel-home' // указываем xtype виджета, который будем выводить. Его создадим в отдельном файле.
}]
});
Sample.page.Home.superclass.constructor.call(this,config); // запускаем конструктор родительского класса
};
Ext.extend(Sample.page.Home,MODx.Component); // Объект страницы расширяет объект MODx.Component
Ext.reg('sample-page-home',Sample.page.Home); // Регистрируем новый xtype для страницы
Далее переходим в файл assets/components/component_name/js/mgr/widgets/home.panel.js
// В этом файле происходит настройка блока с информацией, в данном случае - панели home.
// Подобное деление на отдельные виджеты необходимо для гибкой настройки отображения информации на страницах,
// К примеру один и тот же виджет можно использовать на разных страницах, либо использовать несколько виджетов на странице.
// Создаём внутри компонента главную панель
// (через точку в JS обозначется вложенность массива, то есть мы создаём
// объект Home внутри panel, который, в свою очередь, находится в Sample)
Sample.panel.Home = function (config) {
config = config || {};
Ext.apply(config, {
// внутри MODx.Panel, параметры которого унаследуются, xtype уже указан,
// поэтому его можно пропустить, если, конечно, вы не хотите его изменить
items: [{
html: '<h2>Sample</h2>'
} ]
});
Sample.panel.Home.superclass.constructor.call(this, config); // запускаем конструктор родительского класса для вложенности Sample.panel.Home
};
Ext.extend(Sample.panel.Home, MODx.Panel); // Панель расширяет объект MODX.Panel
Ext.reg('sample-panel-home', Sample.panel.Home); // Регистрируем новый xtype для панели
assets/components/component_name/js/mgr/widgets/component_name.grid.js — виджет с таблицой предметов modExtra. Файл, который выводит записи из БД и позволяет редактировать их во всплывающих окошках.
Sample.grid.Names = function (config) { // Придумываем название, например, «Names»
config = config || {};
Ext.apply(config, {
// Сюда перемещаем все свойства нашей таблички
columns: [
{dataIndex: 'id', width: 330, header: 'ID'},
{dataIndex: 'name', width: 670, header: 'Name'}
],
autoHeight: true,
viewConfig: {
forceFit: true,
scrollOffset: 0
},
store: new Ext.data.ArrayStore({
fields: ['id','name'],
data: [
[1, 'Pencil'],
[2, 'Umbrella'],
[3, 'Ball'],
]
})
});
Sample.grid.Names.superclass.constructor.call(this, config); // запускаем конструктор родительского класса
}
Ext.extend(Sample.grid.Names, Ext.grid.GridPanel); // Табличка расширяет GridPanel, значит данные беруться из параметра store
Ext.reg('sample-grid-names', Sample.grid.Names); // Регистрируем новый xtype
Ext.extend(Sample.panel.Home, MODx.Panel); // Если панель расширяет объект MODX.Panel, значит данные запрашиваются у сервера.
Для того чтобы получать данные с сервера, вмето параметра store, надо указать адрес коннектора, процессора и нужные поля.
// ...
url: Sample.config.connector_url,
action: 'mgr/sample/getlist',
fields: ['id','name']
// ...
// Расширение объекта MODx.grid.Grid подразумевает что данные тянуться с сервера.
Ext.extend(Sample.grid.Names, MODx.grid.Grid);
Список реализованных в MODX стандартных ExtJS-объектов можно увидеть по адресу /manager/assets/modext/widgets/.
Процессоры
Процессоры компонента лежат в каталоге process/ Процессор это php файл, который содержит класс, наследующий modProcessor и возвращающий своё имя. В MODX уже есть несколько готовых классов процессоров, на разные случаи жизни, которые можно наследовать для своих целей (modProcessor и эти классы можно найти по адресу core/model/modx/modprocessor.class.php):
- modObjectGetProcessor — получает и возвращает указанный объект.
- modObjectGetListProcessor — выводит массив объектов.
- modObjectCreateProcessor — создаёт новый объект.
- modObjectUpdateProcessor — обновляет существующий объект.
- modObjectDuplicateProcessor — делает копию существующего объекта.
- modObjectRemoveProcessor — удаляет объект.
- modObjectSoftRemoveProcessor — безопасное удаление объекта.
- modObjectExportProcessor — экспорт объектов.
- modObjectImportProcessor — импорт объектов.
Эти классы наследуют modObjectProcessor, который наследует modProcessor.
Методы класса modProcessor
- setPath($path) — устанавливает путь к процессору.
- checkPermissions() — проверяет доступ к вызываемому процессору.
- initialize() — инициализирует процессор.
- getLanguageTopics() — указывается массив словарей, которые нужно будет инициализировать перед выполнением процессора.
- success($msg = '',$object = null) — возвращает успешный ответ выполнения процессора (параметр success в ответе при этом содержит true).
- failure($msg = '',$object = null) — возвращает ошибку.
- hasErrors() — проверяет есть ли ошибка в процессоре или нет. Поскольку у процессоров нет собственного объекта обработки ошибок, если какой-либо процессор выдал ошибку, все последующие процессоры при проверке $this->hasErrors() будут тоже возвращать ошибку. Чтобы этого избежать, можно после вызова процессора выполнять сброс ошибок $modx->error->reset().
- addFieldError($key,$message = '') — добавляет множественное сообщение об ошибке (удобно, к примеру, при проверке нескольких полей формы).
- process() — абстрактный метод, для описания логики работы процессора. Если ваш процессор наследует непосредственно modProcessor, или класс в котором не переопределяется данный метод, то обязательно надо прописать собственный метод process().
- getProperty($k,$default = null) — получает свойство процессора.
- setProperty($k,$v) — устанавливает свойства процессора, добавляя в массив $this->properties переменные со своими значениями (за один вызов одно значение).
- unsetProperty($key) — удаляет свойство процессора.
- setProperties($properties) — устанавливает сразу несколько свойств процессора.
- getProperties() — получает все параметры процессора возвращая массив $this->properties.
- setDefaultProperties(array $properties = array()) — устанавливает параметры «по умолчанию». Имеет меньший приоритет чем setProperties.
- run() — метод в котором описывается порядок работы методов класса modProcessor. Как правило не переопределяется.
- setCheckbox($k,$force = false) — вспомогательный метод для обработки чекбоксов.
- outputArray(array $array,$count = false) — возвращает массив объектов, конвертированный в JSON.
- toJSON($data) — преобразует PHP в JSON.
- _encodeLiterals(&$value, $key) — кодирует определенные литеральные строки JavaScript для последующего декодирования.
- _decodeLiterals($string) — декодирует строки, закодированные _encodeLiterals, для восстановления литералов JavaScript.
- processEventResponse($response,$separator = "\n") — обрабатывает ответ от вызова события плагина.
- getInstance(modX &$modx,$className,$properties = array()) — возвращает настоящий экземпляр производного класса. Это может быть использовано для переопределения того, как MODX загружает процессор класса; например, при обработке производных классов с настройками class_key.
Кастомный файл-процессор должен состоять из класса, и обязан в конце вернуть своё имя. Простейший пример:
<?php
class SampleNameCreateProcessor extends modObjectCreateProcessor {
// Тип объекта, с которым будем работать - это имя выводится в логе операций MODX
// и подставляется в сообщения об ошибках
public $objectType = 'sampleName';
// xPDO класс объекта
public $classKey = 'sampleName';
// Используемый лексикон - нужен для вывода ошибок
public $languageTopics = array('sample');
// Разрешение, которое проверяеся при работе с процессором
// Можно оставить пустым - тогда смогут работать все менеджеры
public $permission = 'new_document';
}
// Обязательно возвращаем имя класса, чтобы MODX знал, что именно нужно инициализировать в процессоре
return 'SampleNameCreateProcessor';
Процессоры можно вызвать через $modx->runProcessor(), передав нужные параметры. Чтобы узнать, какие методы можно переопределить, достаточно посмотреть в исходник наследуемого modObjectCreateProcessor.
Информацию о работе некоторых процессоров можно найти в статье посвященной MODX API.
Если столкнулись с проблемой когда вместо вашего класса система ищет класс с именем modObject — укажите правильный $classKey.
P.S. Вы сэкономите себе уйму времени, если будете использовать максимально стандартизированные названия классов. Например для обьекта sampleName, использовать класс с названием sampleNameCreateProcessor, или sampleNameUpdateProcessor, и т. д.
Связь компонента и таблицы БД
Модель работы с БД в MODX Revo состоит из набора файлов php, включающих в себя основные объекты и расширения для конкретной БД. В примере будет использован MySQL. Для создания новой таблицы в MySQL используйте:
CREATE TABLE `modx_sample_names` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`description` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`active` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
INDEX (`active`)
) ENGINE=MyISAM CHARSET=utf8 COLLATE utf8_general_ci;
Незабудьте указать свои параметры.
Больше информации о работе с таблицами БД.
Для создания связи с БД можно использовать xml схему, и преобразовать ее, а можно создать нужные файлы вручную. Если вам интересно собрать все вручную, взаимосвязь файлов описана после описания схемы.
XML схема таблиц
Для автоматической генерации модели работы с БД используется схема таблиц, созданная с помощью XML файла, в котором описаны все объекты и их связи. Схема может изменяться, по мере развития дополнения, не нужно пытаться предусмотреть сразу все колонки в таблицах — их можно добавить их в любое время.
Сама принцип схемы придуман для того, чтобы можно было быстро адаптировать данные под разные типы БД.
Схема хранится по адресу core/components/sample/model/schema/sample.mysql.schema.xml
Объекты
Каждый объект описывается в теге object. В атрибутах объекта указывается его имя и таблица БД, в которой он будет храниться. Таблица указывается без префикса сайта — он будет добавлен автоматически самим MODX.
<object class="smplObjectName" table="sample_object_table_name" extends="xPDOSimpleObject">
В схеме не указывается id у объектов, т.к. предполагается расширять уже существующий объект MODX, для этого используется xPDOSimpleObject.
Если использовать xPDOObject — придется самостоятельно все прописывать.
Поля объектов
<field key="name" dbtype="varchar" precision="100" phptype="string" null="false" default="" />
<field key="description" dbtype="text" phptype="text" null="true" default="" />
<field key="active" dbtype="tinyint" precision="1" phptype="boolean" attributes="unsigned" null="true" default="1" />
- key — имя поля
- dbtype — тип поля в базе данных: int, varchar, text и т.д.
- precision — точность, или размер поля. Требуется для типов с фиксированной длиной, как например int и varchar. У полей text не указывается.
- phptype — тип переменной в php, xPDO будет менять значение согласно ему: integer, string, float, json, array. Обратите внимание, что json и array — это изобретение MODX.
Array — это для сериализованных данных, с сохранением типа, а json — обычный json. При сохранении такого поля его значение будет прогоняться через serialize() или json_encode(), a при получении — через unserialize() и json_decode.
Таким образом, можно удобно хранить массивы в базе данных. - null — может ли поле быть пустым? Если вы укажите здесь false, а при работе с объектом не пришлете значение — будет ошибка в логе.
- default — значение по умолчанию, будет использовано, если поле может быть null, и для него нет данных при сохранении
- attributes — дополнительные свойства для передачи в БД. Они точно такие же, как в mySql.
Индексы
Ускоряют работу таблицы.
<index alias="name" name="name" primary="false" unique="false" type="BTREE">
<column key="name" length="" collation="A" null="false" />
</index>
<index alias="active" name="active" primary="false" unique="false" type="BTREE">
<column key="active" length="" collation="A" null="false" />
</index>
- primary — является ли индекс первичным? Обычно — нет, первичный индекс, как правило, по полю id от xPDOSimpleObject.
- unique — является ли индекс уникальным? То есть, может ли быть в таблице 2 и более одинаковых значения у этого поля? Уникальный индекс обычно по колонке id.
Отношение объектов
<composite alias="Subscribers" class="smplObjectName" local="id" foreign="sample_id" cardinality="many" owner="local" />
<aggregate alias="Template" class="modTemplate" local="template" foreign="id" cardinality="one" owner="foreign" />
<aggregate alias="Snippet" class="modSnippet" local="snippet" foreign="id" cardinality="one" owner="foreign" />
- Composite — объект является главным, по отношению к другому. При удалении такого объекта будут удалены все дочерние объекты, связанные с ним здесь.
- Aggregate — объект подчинён другому объекту. При его удалении главному ничего не будет.
- alias — псевдоним связи. Используется в $object->getMany('Subscribers'); или $object->addOne('Template');
- class — настоящее имя класса, с которым связывается текущий объект.
- local — поле текущего обхекта, по которому идёт связь.
- foreign — поля объекта, с которым связываемся.
- Cardinality — тип связи. Один к одному, или один к нескольким. Обычно у связи aggregate это one, а у composite — many, то есть, у родителя много потомков, а у потомков только один родитель. Но бывают и исключения.
Если связь many, то использует addMany() и getMany(), если one — то addOne() и getOne().
Итоговая схема выглядит примерно так:
<?xml version="1.0" encoding="UTF-8"?>
<model package="sample" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" phpdoc-package="sample" version="1.1">
<object class="sampleName" table="sample_object_table_name" extends="xPDOSimpleObject">
<field key="name" dbtype="varchar" precision="100" phptype="string" null="false" default="" />
<field key="description" dbtype="text" phptype="text" null="true" default="" />
<field key="active" dbtype="tinyint" precision="1" phptype="boolean" attributes="unsigned" null="true" default="1" />
<index alias="name" name="name" primary="false" unique="false" type="BTREE">
<column key="name" length="" collation="A" null="false" />
</index>
<index alias="active" name="active" primary="false" unique="false" type="BTREE">
<column key="active" length="" collation="A" null="false" />
</index>
<composite alias="Subscribers" class="smplObjectName" local="id" foreign="sample_id" cardinality="many" owner="local" />
<aggregate alias="Template" class="modTemplate" local="template" foreign="id" cardinality="one" owner="foreign" />
<aggregate alias="Snippet" class="modSnippet" local="snippet" foreign="id" cardinality="one" owner="foreign" />
</object>
<object class="sampleAnotherName" table="sample_aother_table" extends="xPDOSimpleObject">
<field key="sample_id" dbtype="int" precision="10" phptype="integer" attributes="unsigned" null="false" default="0" />
<field key="user_id" dbtype="int" precision="10" phptype="integer" attributes="unsigned" null="true" default="0" />
<index alias="key" name="key" primary="false" unique="true" type="BTREE">
<column key="sample_id" length="" collation="A" null="false" />
<column key="user_id" length="" collation="A" null="false" />
</index>
<aggregate alias="Sample" class="sampleName" local="sample_id" foreign="id" cardinality="one" owner="foreign" />
<aggregate alias="User" class="modUser" local="user_id" foreign="id" cardinality="one" owner="foreign" />
</object>
</model>
После того как файл сгенерирован и размещен на сервере, надо его преобразовать. После преобразования схемы, получается набор файлов, принцип работы которых описан ниже.
Файлы участвующие в связи с БД
Файл core/components/sample/model/sample/metadata.mysql.php — в том файле создаем xPDO-модель, чтобы рассказать MODX о существовании нашей таблицы. Для создания нескольких таблиц в рамках одного дополнения, все таблицы прописываются в этом файле.
<?php
$xpdo_meta_map = array(
'xPDOSimpleObject' => // Объект будет расширять xPDOSimpleObject
array(
0 => 'sampleName', // Название класса для объекта таблицы БД
// 1 => 'nextSampleName', // Если объектов несколько, добавляем в массив
),
);
Файл core/components/sample/model/sample/samplename.class.php — создаем класс для объекта таблицы, например класс создающий элемент.
<?php
class sampleName extends xPDOSimpleObject {
public function create($name){
// Пытаемся создать новый элемент
$item = $this->modx->newObject('sampleName');
$item->set('name',$name);
if(!$item->save()){
return FALSE;
}
return $item;
}
}
Файл core/components/sample/model/sample/mysql/samplename.map.inc.php — карта, описывающая все поля таблицы.
<?php
// Указываем, к какому объекту относится описание
$xpdo_meta_map['sampleName'] = array(
'package' => 'sample', // имя компонента
'version' => '1.1',
'table' => 'sample_names', // имя соответствующей таблицы
'extends' => 'xPDOSimpleObject',
'fields' => // Список полей объекта
array(
'name' => '',
'description' => '',
'active' => 1,
),
'fieldMeta' =>
array( // Описываем свойства каждого поля
'name' =>
array(
'dbtype' => 'varchar', // Тип поля в БД
'precision' => '255', // Длина поля
'phptype' => 'string', // Тип поля в PHP
'null' => false, // Допустимо ли значение NULL
'default' => '', // Значение по умолчанию
),
'description' =>
array(
'dbtype' => 'text',
'phptype' => 'string',
'null' => true,
'default' => '',
),
'active' =>
array(
'dbtype' => 'tinyint',
'precision' => '1',
'phptype' => 'boolean',
'null' => true,
'default' => 1,
),
),
);
Файл core/components/sample/model/sample/mysql/samplename.class.php — класс для объекта связи с таблицей.
<?php
require_once (dirname(dirname(__FILE__)) . '/samplename.class.php');
class sampleName_mysql extends sampleName {}
Список классов modExtra
- modExtraManagerController — заготовка страницы админки. Класс расположен в файле по адресу /core/model/modx/modmanagercontroller.class.php; Все его методы можно посмотреть там. Метод getDefaultController() по умолчанию вызывает Index
Ссылки
Хорошие статьи о написании компонентов от других авторов:
- Официальное руководство по написанию компонентов MODX.
- Курс по созданию компонентов от Василия Наумкина.
- Курс от Ильи Уткина
- Видео по написанию классов и сервисов от OpenModx
Шаблоны действий
Подключение js и css файлов из папки assets:
// Старый метод
$this->modx->regClientCSS()
$this->modx->regClientStartupScript()
$this->modx->regClientScript()
$this->modx->regClientStartupHTMLBlock()
// современный метод (работает с ajaxmanager)
$this->addCss()
$this->addJavascript()
$this->addLastJavascript()
$this->addHtml()
// для избежания проблем с кешированием во время отладки, можно добавить штамп времени в подключение
$this->addJavascript($this->samplename->config['jsUrl'].'mgr/samplename.js?time='.time());
Разные конструкции:
// Передаем опции настроек сниппета в переменную
$tpl = $modx->getOption('tpl',$scriptProperties,'chunkName');
// заносим данные в чанк
$chunk = $this->modx->newObject('modChunk');
$chunk->set('name',$name);
// Перебираем массив элементов, перемешивая данные с тегами из шаблона
foreach ($doodles as $doodle) {
$doodleArray = $doodle->toArray();
$out .= $dood->getChunk($tpl,$doodleArray);
}
Здравствуйте!
Позвольте представиться, меня зовут Марина. Более 10 лет я занимаюсь обслуживанием сайтов и развитием интернет проектов. Если вы хотите избавиться от хлопот связанных с созданием и поддержкой сайта, тогда вы попали по адресу. При работе с сайтами я предоставляю качественные услуги, ориентируясь на ваши индивидуальные потребности. Для связи со мной воспользуйтесь формой обратной связи.