Расширение любых таблиц MODX

В очередной раз понадобилось изменить таблицу сайта, менять которую не предусмотрено.

Что обычно люди делают в таких случаях? Верно, редактируют ядро или нужный компонент, и больше их не обновляют. Но ведь это неправильно, и можно решить вопрос иначе.

Пишем обычный плагин, выставляем для него событие OnMODXInit и меняем модель для нужных классов системы или дополнений. Например, я добавил id менеджера в заказ miniShop2:
<?php
switch ($modx->event->name) {

	case 'OnMODXInit':
		$modx->loadClass('msOrder');
		$modx->map['msOrder']['fields']['manager_id'] = 0;
		$modx->map['msOrder']['fieldMeta']['manager_id'] = array(
			'dbtype' => 'int',
			'precision' => 10,
			'attributes' => 'unsigned',
			'phptype' => 'integer',
			'null' => true,
			'default' => 0,
		);
		break;
}
Остаётся только физически добавить свои колонки в таблицы базу данных, и MODX работает с ними, как с родными. Сохраняет, выбирает, фильтрует — всё как положено.

Больше никаких проблем с обновлениями.

В комментариях возникли вопросы, а что со скоростью?
Отвечаю, этот код отрабатывает примерно за 0.00048 сек. Прикиньте, сколько нужно инициализировать новых классов и добавить полей, чтобы заметить тормоза.
Василий Наумкин
16 ноября 2015, 07:07
modx.pro
116
10 157
+11

Комментарии: 60

Павел Гвоздь
16 ноября 2015, 10:20
0
Василий, я правильно понимаю, что это как-то связано вот с этим — habrahabr.ru/post/253737/?

И что твоим способом можно обойтись без создания своих компонентов ради моделей, как описано в той инструкции от Николая?
    Василий Наумкин
    16 ноября 2015, 10:23
    +3
    Наверное, как-то связано, но реализации через простейший плагин я нигде не видел.

    На инновацию не претендую, может я опять изобрёл велосипед.
      Сергей Шлоков
      16 ноября 2015, 13:08
      +1
      Не знаю, как у других, у меня и такого велосипеда не было. В закладки.
      Fi1osof
      16 ноября 2015, 15:43
      +1
      Это гораздо ближе вот к этому (на коммунити этот пост был написан еще в 2013-ом году).
        Павел Гвоздь
        16 ноября 2015, 15:48
        0
        А разве нельзя будет с помощью способа в этом плагине обойтись без создания компонента ради модели, действуя по твоей инструкции?
          Fi1osof
          16 ноября 2015, 16:07
          +1
          Можно. Просто я плагины недолюбливаю. В них отладка хромает. Метадата в компоненте надежней. Вот еще советую к ознакомлению статью по теме.
      Николай Загумённов
      16 ноября 2015, 10:24
      0
      А плагин должен постоянно висеть?
        Василий Наумкин
        16 ноября 2015, 10:25
        +2
        Конечно, в этом и фокус. При каждой загрузке системы изменяется модель в её памяти.

        Если плагин отключить, то, как-бы, ничего и не расширялось.
          Николай Загумённов
          16 ноября 2015, 10:28
          0
          Если будет много добавленных полей не будет грузить сервер?
            Василий Наумкин
            16 ноября 2015, 10:32
            0
            Чем? Добавлением значений в массив?

            Нет, не будет.
            Іван Клімчук
            16 ноября 2015, 11:29
            0
            Если не злоупотреблять, то грузить не будет.
              Павел Гвоздь
              16 ноября 2015, 11:42
              0
              Как понять «злоупотреблять»? Не совсем понимаю, сколько надо подгрузить loadClass и прописать дополнительных колонок, чтобы начало грузить систему?
                Іван Клімчук
                16 ноября 2015, 11:46
                1
                +1
                Экспериментальным путем определеяется. Все же зависит от производительности сервера, нагрузки и тд. Дополнить массив — операция не сильно дорогая, но при большой нагрузке это булет происходить при кажлом запросе. Так что на нагруженных серверах нужно собирать статистику и метрики по расходу памяти и затраченном времени. Ну и возможно на посещаемом сайте придется другой метод использовать, но это уже задачи из раздела «Оптимизация».
                  Павел Гвоздь
                  16 ноября 2015, 11:48
                  0
                  Спасибо. Я так понимаю, на посещаемых сайтах решение, которое предоставил Николай в своей статье подойдёт больше?
                  Василий Наумкин
                  16 ноября 2015, 11:54
                  +2
                  В теории, наверное, да.

                  А на практике в системе и так уже грузятся десятки классов и всё это занимает миллисекунды. Те же классы miniShop2 тоже грузятся, но чуть позже, поэтому нужно вызвать loadClass здесь вручную.
                  Работа с массивом вообще, считай, ничего не стоит.

                  Я не могу придумать, как можно по-настоящему затормозить систему таким плагином. Добавить 10 000 полей, разве что, и то не факт.
                Василий Наумкин
                16 ноября 2015, 12:01
                +1
                Если злоупотреблять, то тоже не будет.

                У меня этот код отрабатывает за 0.00048 сек. Добавил в заметку.
          Николай Савин
          16 ноября 2015, 13:05
          0
          Василий, можно ли этим плагином добавить в msProduct какой либо параметр товар и дальше с ним работать не используя плагины miniShop?
          На первый взгляд это попроще будет чем плагины писать
            Василий Наумкин
            16 ноября 2015, 13:06
            0
            Можно, суть ровно та же.

            Только плагины miniShop2 позволяют еще и в саму админку всякое добавлять.
              Николай Савин
              16 ноября 2015, 13:08
              0
              Можно то можно, я попробовал и понял для себя что в админке без понимания работы extJs никуда
                Андрей
                21 ноября 2015, 16:41
                0
                С помощью плагинов расширяется только вкладка «Свойства товара», так ведь?
              Fi1osof
              16 ноября 2015, 16:12
              +4
              Кстати, если кто будет экспериментировать на старых сайтах, имейте виду, что событие OnMODXInit появилось только в MODX-2.3.0. На более ранних версиях это не сработает.
                Григорий Коленько
                10 декабря 2015, 00:42
                0
                А есть какой-то вариант расширения класса, к примеру tickets, чтобы переписать пару функций, и класс уже грузился с переписанным функционалом?
                  Воеводский Михаил
                  10 декабря 2015, 01:06
                  0
                  Только если разработчиком предусмотрена такая возможность. Насколько помню, основной класс в Tickets штатно переопределить нельзя.

                  Обойти это Вы можете следующим способом:
                  1. Сделать расширяющий класс
                  2. Сделать копию необходимых сниппетом от Tickets
                  3. В копиях сниппетов изменить способ инициализации основного сервиса, чтобы подгружался расширенный класс.

                  Если воспользуетесь поиском, найдете подробную статью Василия с описанием этого метода.
                Николай
                28 апреля 2016, 20:33
                0
                Обнаружил проблему. Создал простенький плагин и одну дополнительную колонку в БД. Всё работает, но при сохранении ресурсов процесс сохранения подвисает на долгое время. Отключаю плагин — всё ок.

                Вот код плагина:
                <?php
                
                switch ($modx->event->name) {
                	case 'OnMODXInit':                                                             
                		$modx->loadClass('modResource');
                		$modx->map['modResource']['fields']['old_content'] = '';
                		$modx->map['modResource']['fieldMeta']['old_content'] = array(
                			'dbtype' => 'mediumtext',
                			'phptype' => 'string',
                			'null' => true,
                			'default' => '',
                		);
                		break;
                }

                Вот колонка в базе:


                Никто не сталкивался с этим, в чём может быть проблема?
                  Лев Вербицкий
                  29 апреля 2016, 10:37
                  0
                  $modx->loadClass('modResource');
                  Удалите
                    Николай
                    29 апреля 2016, 10:42
                    0
                    Удалил, но проблема осталась...(
                      Stepan
                      21 июня 2021, 12:59
                      0
                      а как именно вы туда пишете покажите
                        Николай Савин
                        21 июня 2021, 16:31
                        0
                        А не смущает что данный комментарий 5 лет назад написан?
                          Николай
                          22 июня 2021, 14:09
                          0
                          Да, я давно уже разобрался)) Сейчас легко расширяю что только можно, в том числе в админке) Но в этом вопросе не знаю что не так, на скринах вроде всё верно.
                    Сергей Шлоков
                    29 апреля 2016, 12:48
                    0
                    Для начала надо смотреть логи и консоль браузера. Код валидный. Видимо где-то есть проблема в обработке.
                      Николай
                      29 апреля 2016, 15:21
                      0
                      Вот консоль:


                      Дословно:
                      XSS Auditor отказался выполнить сценарий в 'http://good4live.ru/connectors/index.php', потому что его исходный код был найден в запросе. Аудитор был включен в качестве сервера послал ни в «X-XSS-защиты», ни заголовка «Content-Security-Policy».

                      причём если отключить плагин, то этой записи уже не будет. А в логах пусто…
                      Николай
                      04 мая 2016, 13:28
                      0
                      up!)
                      Михаил
                      15 февраля 2017, 08:45
                      0
                      Подскажите, пожалуйста, что делаю не так:

                      1. Создал плагин точь-в-точь как в этом посте. Повесил на событие OnMODXInit.
                      2. Добавил в таблицу modx_ms2_orders столбец manager_id joxi.ru/52azjXgu4goPyA
                      3. В стандартном сниппете msOrder добавил
                      $miniShop2->order->add('manager_id', 4);
                      Ни на что не ругается, заказ создается, но в базу записывается 0 всегда. Каким образом сохранять значение в этот столбец?
                        Fullstack
                        13 ноября 2020, 13:21
                        0
                        Присоединяюсь! У нас не сохраняются продукты в modx_ms2_products из-за того, что в таблицу в кастомное поле bitrix_id все время пишется 0, а ноль — это дубликат (ведь поле является ключом-индексом), из-за чего продукт просто не вставляется! Хотя в БД это поле по умолчанию NULL. Попробовал расширить модель, не помогло:

                        $eventName = $modx->event->name;
                        switch ($eventName) {
                        
                            case 'OnMODXInit':
                                // Регистрируем поле bitrix_id в модели продукта
                                $modx->loadClass('modProduct');
                                $modx->map['modProduct']['fields']['bitrix_id'] = null;
                                $modx->map['modProduct']['fieldMeta']['bitrix_id'] = array(
                                    'dbtype' => 'int',
                                    'precision' => 10,
                                    //'attributes' => 'unsigned',
                                    'phptype' => 'integer',
                                    'null' => true,
                                    'default' => null
                                );
                            	break;
                        
                        }
                          Fullstack
                          13 ноября 2020, 13:52
                          0
                          Решил! Надо было использовать другой класс и другое событие.
                          Вот готовый и рабочий вариант:

                          <?php
                          $eventName = $modx->event->name;
                          switch ($eventName) {
                          
                              //case 'OnMODXInit':
                              case 'OnBeforeManagerPageInit':
                                  // Загружаем в модель продукта поле bitrix_id
                                  $modx->loadClass('msProductData');
                                  $modx->map['msProductData']['fields']['bitrix_id'] = null;
                                  $modx->map['msProductData']['fieldMeta']['bitrix_id'] = array(
                                      'dbtype' => 'int',
                                      'precision' => 10,
                                      //'attributes' => 'unsigned',
                                      'phptype' => 'integer',
                                      'null' => true,
                                      //'default' => null,
                                  );
                              	break;
                          
                          }
                            Fullstack
                            13 ноября 2020, 14:08
                            0
                            Хотя нет! При сохранении некоторых ресурсов такая же фигня!
                            Я не могу…
                              Fullstack
                              13 ноября 2020, 14:32
                              0
                              Финальный вариант плагина.
                              Я бы еще не заполнял к нему описание. Мало ли дело было в нём…

                              Добавил на всякий случай еще одно событие. Сейчас все работает:

                              <?php
                              $eventName = $modx->event->name;
                              switch ($eventName) {
                              
                                  case 'OnMODXInit':
                                  case 'OnBeforeManagerPageInit':
                                  case 'OnManagerPageInit':
                                  case 'OnWebPageInit':
                                      // Загружаем в модель продукта поле bitrix_id
                                      $modx->loadClass('msProductData');
                                      $modx->map['msProductData']['fields']['bitrix_id'] = null;
                                      $modx->map['msProductData']['fieldMeta']['bitrix_id'] = array(
                                          'dbtype' => 'int',
                                          'precision' => 10,
                                          //'attributes' => 'unsigned',
                                          'phptype' => 'integer',
                                          'null' => true,
                                          'default' => null,
                                      );
                                  	break;
                              
                              }
                        Кирилл Киселев
                        22 марта 2017, 19:26
                        0
                        Добавил, не работает с BannerY. Это только с минишопом работает?
                        <?php
                        switch ($modx->event->name) {
                        	case 'OnMODXInit':
                         		$modx->loadClass('byAd');
                        		$modx->map['byAd'] = array(
                        		    'fields' =>
                        		    array(
                        		        'active_bgr',
                        		        'active_ukr',
                        		        'active_eng',
                        		    ),
                        		    'fieldMeta' =>
                        		    array(
                                        'active_bgr' =>
                                        array(
                                			'dbtype' => 'boolean',
                                			'precision' => 1,
                                			'attributes' => 'unsigned',
                                			'phptype' => 'boolean',
                                			'null' => true,
                                			'default' => 0,
                                        ),
                                        'active_ukr' =>
                                        array(
                                			'dbtype' => 'boolean',
                                			'precision' => 1,
                                			'attributes' => 'unsigned',
                                			'phptype' => 'boolean',
                                			'null' => true,
                                			'default' => 0,
                                        ),
                                        'active_eng' =>
                                        array(
                                			'dbtype' => 'boolean',
                                			'precision' => 1,
                                			'attributes' => 'unsigned',
                                			'phptype' => 'boolean',
                                			'null' => true,
                                			'default' => 0,
                                        ),
                                    ),
                                );
                        		break;
                        }
                          Кирилл Киселев
                          22 марта 2017, 20:25
                          0
                          Пишет:
                          Could not load class: byAd from mysql.byad.
                          Хотя в консоли класс этот видит.
                            mngatoff
                            30 марта 2017, 15:33
                            1
                            0
                            попробуй дописать баннеры в настройку extension_packages
                          Семён Кудрявцев
                          24 июля 2017, 13:38
                          0
                          А есть ли способ проверить, что модель действительно расширена после этих манипуляций?
                          У меня создано именно такое поле в таблице заказов, и я прямо из заказа отправляю номер менеджера, но в таблицу всегда попадает ноль.
                            Воеводский Михаил
                            24 июля 2017, 17:21
                            0
                            Самое простое — получить объект через getObject(), затем получить массив всех полей через toArray().
                              Евгений
                              06 августа 2019, 12:35
                              0
                              Доброго времени суток.
                              Добавил в таблицу site_content поле import_cid и во внешнем скрипте (modx проинициализирован) пытаюсь получить объект через getObject с условием по этому полю, но выдаёт «Unknown column 'modResource.import_сid' in 'where clause'».
                              var_dump($modx->map['modResource']); в моём скрипте говорит, что import_сid есть.
                              Не подскажите в какую сторону копать?
                                Stepan
                                21 июня 2021, 12:55
                                0
                                в двойных кавычках писали?
                            Петр Синечёв
                            20 февраля 2019, 13:37
                            0
                            Я из заметки не понял, добавленные поля должны где-то появляться? (имеется в виду окно редактирования ресурса)
                            Например добавил столбец content2 в БД и поставил плагин:
                            <?php
                            switch ($modx->event->name) {
                            	case 'OnMODXInit':
                                    $modx->map['modResource']['fields']['content2'] = 0;
                                    $modx->map['modResource']['fieldMeta']['content2'] = array(
                                           'dbtype' => 'mediumtext',
                                           'phptype' => 'string',
                                           'index' => 'fulltext',
                                           'indexgrp' => 'content_ft_idx'
                                    );
                            	break;
                            }
                            Ничего нигде не поменялось. Нужны еще какие-то манипуляции для добавление полей в форму редактирования ресурса?
                              Наумов Алексей
                              20 февраля 2019, 13:50
                              +1
                              Нет, не появится ничего и нигде. Только при прямом доступе к объекту в коде будут доступны эти поля.
                              В форму редактирования нужно добавлять самостоятельно.
                                Николай Савин
                                20 февраля 2019, 14:03
                                2
                                +1
                                В админке на каждой странице каждое поле отдельно жестко прописано, с указанием всех свойств (строка, текст, число). Это сделано в js массиве.
                                Если ты что то новое добавляешь в карту объекта и базу данных (уточню на всякий случай что еще и таблицу базы данных нужно расширять ручками) — то так же ручками нужно добавлять новый дополнительный код в js массив полей.
                                Обычно делают отдельный плагин, чтобы не вносить правки в исходный код MODX.
                                Вот пример, прямо из рабочего проекта дернул
                                switch ($modx->event->name){
                                case 'OnDocFormPrerender':
                                        $modx->controller->addHtml("
                                            <script type='text/javascript'>               
                                                
                                             Ext.ComponentMgr.onAvailable('modx-panel-resource', function(){                 
                                                 if(this.items[1].items[0].id == 'minishop2-product-tab'){
                                                       var leftCol = this.items[1].items[0].items[0].items[0].items[0].items[0].items[0].items[0];
                                                 }else{
                                                      if(this.items[1].items[0].id== 'modx-resource-settings'){
                                                         var leftCol = this.items[1].items[0].items[0].items[0];
                                                     }
                                                     if(this.items[1].items[1].id == 'modx-resource-settings'){
                                                         var leftCol = this.items[1].items[1].items[0].items[0];
                                                     }                    
                                                     
                                                 } 
                                                 
                                                 var kz_title = {
                                                   anchor: '100%',
                                                   description: '<b>[[*kz_title]]</br>Заголовок на казахском</b>',
                                                   fieldLabel: 'Заголовок на казахском',
                                                   id: 'modx-resource-kz-title',
                                                   maxLength:255,
                                                   msgTarget: 'under',
                                                   name:'kz_title',
                                                   xtype:'textfield'
                                                }
                                                
                                                leftCol.items.splice(2, 0,  kz_title); 
                                                
                                                var kz_description = {
                                                   anchor: '100%',
                                                   description: '<b>[[*kz_description]]</br>Описание на казахском</b>',
                                                   fieldLabel: 'Описание на казахском',
                                                   id: 'modx-resource-kz-description',
                                                   maxLength:255,
                                                   msgTarget: 'under',
                                                   name:'kz_description',
                                                   xtype:'textarea'
                                                }
                                                                               
                                                leftCol.items.splice(4, 0,  kz_description);
                                                
                                                
                                                
                                            });
                                        </script>");
                                        break;
                                }
                                
                                На выходе получилось вот так prntscr.com/mnm25m
                                  Петр Синечёв
                                  20 февраля 2019, 14:34
                                  0
                                  Здорово!
                                  А я добавил TV поле и при его сохранении плагин записывал значение в БД
                                  case 'OnDocFormSave':
                                          $resource->set('content2', $resource->getTVValue('content2'));
                                          $resource->save();
                                          break;
                                  Правда, я ожидал, что можно будет делать обычный вызов на странице документа
                                  [[*content2]]
                                  , но он почему-то возвращает массив
                                  ( [0] => content2 [1] => контент 2 [2] => default [3] => [4] => text )
                                  Надо попробовать твой вариант!
                                    Николай Савин
                                    20 февраля 2019, 14:38
                                    0
                                    Это тоже отличная практика. В TV много разных типов, и эту особенность часто удобно использовать.
                                    Что касается проблемы — смотри, у тебя одновременно есть и поле content2 в таблице ресурсов и такой же TV. Конфликт явный. Надо tv переименовать.
                                      Петр Синечёв
                                      20 февраля 2019, 14:45
                                      0
                                      Действительно) А я подумал, что это из-за того, что за основу взял шаблон поля content и начал уже все переделывать. Опыт, штука полезная) Спасибо
                                  Николай
                                  20 февраля 2019, 16:48
                                  1
                                  0
                                  Посмотрите вот эти уроки, там хорошо разжёвывается данная тема:

                                  Свои поля в интерфейсе редактирования ресурса MODx
                                  Свои вкладки и поля ресурса в админке MODx
                                  Andrey
                                  05 сентября 2019, 11:50
                                  +1
                                  ОМАГАД! Просто я искал эту статью наверное всю жизнь! :D
                                    Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                                    60