Упрощаем работу с TV
Продолжая тему, начатую Василием, предлагаю решение для упрощения работы с TV. Оно поможет тем, у кого логика сайта завязана на TV — запросы, фильтры и т.п.
Решается эта задача созданием полей в таблице ресурсов и копированием в них значений TV.
Покажу на своем примере.
1. Создаем TV «tv.action», которая будет признаком, что ресурс акционный. Это нужно для выборки только тех ресурсов, которые участвуют в акции. 1 — участвует, 0 — нет. Обратите внимание на префикс.
2. В таблице modx_site_content создаем поле action. Удобнее всего через phpMyAdmin.
3. Создадим вот такой небольшой плагин для нашего поля
4. Теперь на странице ресурса пишем, например, такой запрос с обычным where и без подключения TV-шек
В результате ТВ-шки работают как обычно, а вот запросы значительно упростились, так как они теперь работают только с одной таблицей. И на странице можно указывать тег с именем добавленного поля ([[*action]]), парсер обработает его быстрее, так как он не будет делать дополнительный запрос к таблице ТВ-шек. Вот такой велосипед получился.
Решается эта задача созданием полей в таблице ресурсов и копированием в них значений TV.
Покажу на своем примере.
1. Создаем TV «tv.action», которая будет признаком, что ресурс акционный. Это нужно для выборки только тех ресурсов, которые участвуют в акции. 1 — участвует, 0 — нет. Обратите внимание на префикс.
2. В таблице modx_site_content создаем поле action. Удобнее всего через phpMyAdmin.
Тип: tinyint
Длина: 1
Значение по-умолчанию: 0
Атрибуты: UNSIGNED
3. Создадим вот такой небольшой плагин для нашего поля
<?php
switch ($modx->event->name) {
case 'OnMODXInit':
// Загружаем наше поле в модель ресурса
$modx->loadClass('modResource');
$modx->map['modResource']['fields']['action'] = 0;
$modx->map['modResource']['fieldMeta']['action'] = array(
'dbtype' => 'tinyint',
'precision' => 1,
'attributes' => 'unsigned',
'phptype' => 'integer',
'null' => false,
'default' => 0,
);
break;
case 'OnDocFormSave':
// Сохраняем ТВ в поле таблицы ресурса
$resource->set('action', $resource->getTVValue('tv.action'));
$resource->save();
break;
}
Не забываем отметить нужные события.4. Теперь на странице ресурса пишем, например, такой запрос с обычным where и без подключения TV-шек
[[!pdoResources?
&parents=`0`
&where=`{"action":"1"}`
&tpl=`myTpl`
&depth=`0`
]]
и получаем все ресурсы, для которых установлен признак акции. И никакой мороки с значениями по умолчанию, джойнами и т.п. Сложные типы TV-шек не тестировал, но думаю должно работать. Для тех, у кого много TV, скорость запросов должна заметно вырасти. Было бы интересно узнать результаты до и после.В результате ТВ-шки работают как обычно, а вот запросы значительно упростились, так как они теперь работают только с одной таблицей. И на странице можно указывать тег с именем добавленного поля ([[*action]]), парсер обработает его быстрее, так как он не будет делать дополнительный запрос к таблице ТВ-шек. Вот такой велосипед получился.
Важно!
Имя ТВ и поля должны отличаться! В противном случае, при использовании TV на странице сайта значение поля будет некорректным. Можно добавить префикс «tv» к имени ТВ-шки (подробности).Рекомендация!
Данный прием нужно использовать осторожно и только людям с устойчивой нервной системой, так как для оптимизации запросов происходит дублирование значений ТВ в таблице ресурсов. В противном случае возможны побочные действия — обострение состояния до «аж кушать не могу» из-за дублей и добавленных полей, бессоница из-за нарушения гармонии в нормализации данных (для некоторых это большой грех), головокружение и тошнота от увеличившейся скорости работы сайта. Детям использовать это можно только под присмотром взрослых! :)
Поблагодарить автора
Отправить деньги
Комментарии: 44
Сергей, молодец!
Все гениальное просто :) Твой пытливый ум еще много полезностей, чую, придумает )))
Все гениальное просто :) Твой пытливый ум еще много полезностей, чую, придумает )))
Спасибо!
Очень надеюсь, но думаю, что дальнейшие улучшения для версии 2.х будут скоро не актуальны в связи с амбиционными планами по выпуску в скором времени MODX 3.
Очень надеюсь, но думаю, что дальнейшие улучшения для версии 2.х будут скоро не актуальны в связи с амбиционными планами по выпуску в скором времени MODX 3.
О 3 версии уже года 3 идут разговоры.
Ну вот, срок обещанного подходит к концу :)
Вот и поглядим, знают ли они русские народные пословицы :)
Из будущего авторитетно заявляю: не знают.
У тебя помоему тут в таблице ТВшек хранится лишняя информация? В статье от Николая с подобной штукой был плагин, который удаляет лишнюю инфу из таблицы с ТВхами. Я его немного переписал. Вот, если кому надо:
В массиве $tvs инфа о ТВхах в таком виде: id => name.
<?php
/*
OnBeforeDocFormSave
OnDocFormSave
OnResourceTVFormRender
*/
$tvs = array(
2 => "image",
3 => "city",
4 => "company",
5 => "email",
);
switch($modx->event->name)
{
/* >> Рендеринг ТВшек */
case 'OnResourceTVFormRender':
if( !$document = $modx->getObject('modResource', $resource) )
{
$modx->log(xPDO::LOG_LEVEL_ERROR, "Не был получен документ");
return;
}
$categories = & $scriptProperties['categories'];
foreach( $categories as $c_id => & $category )
{
foreach( $category['tvs'] as & $tv )
{
if( array_key_exists($tv->id, $tvs) )
{
$q = $modx->newQuery('modTemplateVar');
$q->select( 'type' );
$q->where( 'name = "'. $tvs[ $tv->id ] .'"' );
$tv_type = $modx->getValue( $q->prepare() );
unset($q);
// >> Рендеринг тэгов
if( $tv_type == 'tags' )
{
$q = $modx->newQuery('modResourceTag');
$q->select(array(
"GROUP_CONCAT(distinct tag_id) as tags",
));
$q->where(array(
"resource_id" => $document->id,
));
$tags = $modx->getValue($q->prepare());
$value = str_replace(",", "||", $tags);
$tv->value = $value;
$tv->relativeValue = $value;
$inputForm = $tv->renderInput($document, array('value' => $tv->value));
$tv->set('formElement', $inputForm);
}
// << Рендеринг тэгов
// >> Рендеринг строковых ТВшек
else if(
$tv_type == 'autotag' ||
$tv_type == 'tag' ||
$tv_type == 'image' ||
$tv_type == 'email' ||
$tv_type == 'date' ||
$tv_type == 'option' ||
$tv_type == 'listbox-multiple' ||
$tv_type == 'listbox' ||
$tv_type == 'resourcelist' ||
$tv_type == 'hidden' ||
$tv_type == 'text' ||
$tv_type == 'textarea' ||
$tv_type == 'richtext' ||
$tv_type == 'list-multiple-legacy' ||
$tv_type == 'file' ||
$tv_type == 'checkbox' ||
$tv_type == 'number' ||
$tv_type == 'migx'
) {
$tv->value = $document->$tvs[ $tv->id ];
$tv->relativeValue = $tv->value;
$inputForm = $tv->renderInput($document, array('value'=> $tv->value));
$tv->set('formElement', $inputForm);
}
// << Рендеринг строковых ТВшек
// >> Рендеринг массивных ТВшек
else if(
$tv_type == 'listbox-multiple'
) {
$tv_value_array = array_map( function($v){ return trim($v,':'); }, explode( '::', $document->$tvs[ $tv->id ] ) );
$tv->value = $tv_value_array;
$tv->relativeValue = $tv_value_array;
$inputForm = $tv->renderInput($document, array('value' => $tv->value));
$tv->set('formElement', $inputForm);
}
// << Рендеринг массивных ТВшек
}
}
}
break;
/* << Рендеринг ТВшек */
/* >> Перед сохранением документа */
case 'OnBeforeDocFormSave':
if( !$resource = & $scriptProperties['resource'] )
{
$modx->log(xPDO::LOG_LEVEL_ERROR, "Не был получен документ");
return;
}
// >> получим список id ТВшек, с которыми будем работать
$i=0;
$tv_where='';
foreach( $tvs as $id => $name )
{
$tv_where .= !$i ? '' : ' OR ';
$tv_where .= 'name = "'. $name .'"';
$i++;
}
unset($i); unset($id); unset($name);
$q = $modx->newQuery('modTemplateVar');
$q->select( 'id, type, name' );
$q->where( $tv_where );
$s = $q->prepare();
$s->execute();
$tv_rows = $s->fetchAll(PDO::FETCH_ASSOC);
unset($q); unset($s);
// << получим список id ТВшек, с которыми будем работать
// >> Обрабатываем ТВшки
foreach( $tv_rows as $tv_row )
{
$var = 'tv'. $tv_row['id'];
if( isset( $resource->$var ) )
{
// >> теги
if( $tv_row['type'] == 'tags' )
{
$tags = array();
foreach( (array)$resource->Tags as $tag )
{
$tag->active = 0;
$tags[ $tag->tag_id ] = $tag;
}
if( !empty($resource->$var) )
{
foreach( (array)$resource->$var as $tv_value )
{
if( $tv_value )
{
if( !empty($tags[ $tv_value ]) )
{
$tags[ $tv_value ]->active = 1;
}
else {
$tags[ $tv_value ] = $modx->newObject('modResourceTag', array(
"tag_id" => $tv_value,
));
}
}
}
}
$resource->Tags = $tags;
$tags_ids = array();
foreach( $resource->Tags as $tag )
{
if( $tag->active )
{
$tags_ids[] = $tag->tag_id;
}
}
$resource->$tv_row['name'] = ( $tags_ids ? implode(",", $tags_ids) : NULL );
}
// << теги
// >> картинки и другие строковые
else if($tv_row['type'] == 'autotag' ||
$tv_row['type'] == 'tag' ||
$tv_row['type'] == 'image' ||
$tv_row['type'] == 'email' ||
$tv_row['type'] == 'date' ||
$tv_row['type'] == 'option' ||
$tv_row['type'] == 'listbox' ||
$tv_row['type'] == 'resourcelist' ||
$tv_row['type'] == 'hidden' ||
$tv_row['type'] == 'text' ||
$tv_row['type'] == 'textarea' ||
$tv_row['type'] == 'richtext' ||
$tv_row['type'] == 'file' ||
$tv_row['type'] == 'number' ||
$tv_row['type'] == 'migx'
) {
//print_r( $resource->$var );
$resource->$tv_row['name'] = $resource->$var;
}
// << картинки и другие строковые
// >> массивы
else if($tv_row['type'] == 'listbox-multiple' ||
$tv_row['type'] == 'list-multiple-legacy' ||
$tv_row['type'] == 'checkbox'
) {
$resource->$tv_row['name'] = ( $resource->$var ? implode("||", $resource->$var) : NULL );
}
// << массивы
}
}
// << Обрабатываем ТВшки
break;
/* << Перед сохранением документа */
/* >> Сохранение документа */
case 'OnDocFormSave':
if( !$resource = & $scriptProperties['resource'] )
{
$modx->log(xPDO::LOG_LEVEL_ERROR, "Не был получен документ");
return;
}
if($tvs)
{
$modx->removeCollection('modTemplateVarResource', array(
'tmplvarid:in' => array_keys($tvs),
'contentid' => $resource->id,
));
// Сбрасываем автоинкримент
//$sql = "ALTER TABLE {$tv_table} auto_increment = 1";
//$modx->prepare($sql)->execute();
}
break;
/* << Сохранение документа */
}
В массиве $tvs инфа о ТВхах в таком виде: id => name.
Супер! Всё работает! Спасибо за своевременное решение!
Я перевёл сразу 6 самых ключевых тв-шек в таблицу ресурсов и всё работает! К тому же 2 из них в формате timestamp. Всё сохраняется правильно и работает исключительно!
Пример кода для timestamp
В
Я перевёл сразу 6 самых ключевых тв-шек в таблицу ресурсов и всё работает! К тому же 2 из них в формате timestamp. Всё сохраняется правильно и работает исключительно!
Пример кода для timestamp
В
<?php
switch ($modx->event->name) {
case 'OnMODXInit':
// Загружаем поле vip_time в модель ресурса
$modx->map['modResource']['fields']['vip_time'] = 0;
$modx->map['modResource']['fieldMeta']['vip_time'] = array(
'dbtype' => 'int',
'precision' => 20,
'phptype' => 'integer',
'default' => 0,
);
break;
case 'OnDocFormSave':
// Сохраняем ТВ в поле таблицы ресурса
$resource->vip_time = strtotime($resource->getTVValue('vip_time'));
$resource->save();
break;
}
Сейчас буду думать как написать код для копирования уже существующих значений в таблицу…
Здорово!
Если ты выводишь ТВ-шки на странице сайта, то обрати внимание на последние пару абзацев статьи.
Спасибо что предупредил. Прийдётся сменить названия тв-шек… ))
… или скорее всего названия полей, потому что у меня кучи логики уже навешано на тв-шки…
Если ты их не будешь выводить но фронте, то можно ничего не менять.
ага, только прийдётся… глюк работает исправно )))
Просто добавил всем новым полям в таблице префикс ext_ и изменил в скриптах.
Там кстати у меня есть скрипты на Ajax, в которых нужно прописывать значения параллельно и в базу и в тв, но делается это легко…
Там кстати у меня есть скрипты на Ajax, в которых нужно прописывать значения параллельно и в базу и в тв, но делается это легко…
Тогда не забудь добавить этот префикс и в теги на странице — [[*ext_fieldname]], чтобы парсер не лазил в таблицу ТВ-шек.
Ага, точно. Девиз: выжать максимальную производительность! )))
Конечно. Нам этот «прицеп» нужен только для форм в админке, а на сайте можно его отцепить.
Прикольно, именно так уже какое-то время и фильтруются новости на главной modx.pro. Там есть 2 ТВ параметра: выводить на главной, или скрывать на главной.
При работе с ТВ запрос с фильтрацией по этим 2м ТВ выходил под 0.5 сек, сейчас — 0.06 сек. Мой плагин выглядит вот так:
При работе с ТВ запрос с фильтрацией по этим 2м ТВ выходил под 0.5 сек, сейчас — 0.06 сек. Мой плагин выглядит вот так:
<?php
switch ($modx->event->name) {
case 'OnMODXInit':
$modx->loadClass('modResource');
$modx->map['modResource']['fields']['show_on_start'] =
$modx->map['modResource']['fields']['hide_on_start'] =
$modx->map['modResource']['fields']['ticket_rating'] = 0;
$modx->map['modResource']['fieldMeta']['show_on_start'] =
$modx->map['modResource']['fieldMeta']['hide_on_start'] = array(
'dbtype' => 'tinyint',
'precision' => 1,
'attributes' => 'unsigned',
'phptype' => 'boolean',
'null' => true,
'default' => 0,
);
$modx->map['modResource']['fieldMeta']['ticket_rating'] = array(
'dbtype' => 'int',
'precision' => 10,
'attributes' => '',
'phptype' => 'int',
'null' => true,
'default' => 0,
);
break;
case 'OnBeforeDocFormSave':
$resource->set('show_on_start', !empty($resource->get('tv7')));
$resource->set('hide_on_start', !empty($resource->get('tv8')));
break;
case 'OnTicketVote':
if ($object->class == 'Ticket') {
if ($ticket = $modx->getObject('Ticket', $object->id)) {
$properties = $ticket->getProperties('tickets');
$rating = !empty($properties['rating'])
? $properties['rating']
: 0;
$ticket->set('ticket_rating', $rating + $object->value);
$ticket->save();
}
}
break;
}
Ка видите, там еще и сортировка по рейтингу тикетов есть — дарю! OnTicketVote был добавлен в последней версии Tickets.
А вот и цифры. Результат впечатляет! И это всего для двух ТВ-шек.
Ну там монструозненько было, с having и 3500 тикетов. Если интересно, вот старый страшный код:
$scriptProperties['leftJoin'] = json_encode(array(
'Votes' => array('class' => 'TicketVote', 'on' => 'Votes.id = Ticket.id AND Votes.class = \'Ticket\''),
'TVshow' => array('class' => 'modTemplateVarResource', 'on' => 'TVshow.contentid = Ticket.id AND TVshow.tmplvarid = 7'),
'TVhide' => array('class' => 'modTemplateVarResource', 'on' => 'TVhide.contentid = Ticket.id AND TVhide.tmplvarid = 8'),
));
$scriptProperties['select'] = json_encode(array(
'Votes' => 'SUM(Votes.value) as rating',
'TVshow' => 'TVshow.value as show',
'TVhide' => 'TVhide.value as hide',
));
$scriptProperties['having'] = '["rating > -2 OR rating IS NULL"]';
$scriptProperties['where'] = '["(Ticket.parent IN ('.$parents.') OR TVshow.value IS NOT NULL) AND TVhide.value IS NULL"]';
Новый вариант симпатичнее и понятнее. Красота!
Да, новый вариант вот такой:
$scriptProperties['where'] = '["
(Ticket.parent IN ('.$parents.') OR Ticket.show_on_start = 1)
AND Ticket.hide_on_start = 0
AND Ticket.ticket_rating > -2
"]';
Собственно, именно поэтому я и предпочитаю не использовать ТВ вообще. И теперь это вполне возможно, даже без miniShop2.
Только что обнаружил, что рендерятся эти новые поля на странице криво. Пока не понял почему. В таблице одно значение, а выводится другое.
Ну, у меня проблем нет. В админке используются нормальные ТВ, при сохранении в ресурс они преобразовываются в значения ресурса, а дальше в выборках участвуют только поля ресурса.
Везде всё хорошо =)
Везде всё хорошо =)
В админке и в выборках все нормально, работает как положено. А если попробовать вывести на страницу, например, [[*action]] из моего примера, то получаю всегда 1, независимо от значения в таблице ресурсов и TV. Для интереса создал поле varchar для текстовой ТВ-шки. Сохраняется нормально (пишет в таблицу значение «test»), а на страницу выводит массив
Array ( [0] => test [1] => test [2] => default [3] => [4] => text )
Проблема в том, что в классе modRequest в 247 строчке подгружаются ТВ-шки и затирают значение поля. Выход нашел пока только один — ТВ-шкам давать префикс. Например, создаем ТВ «tv.action», а поле для нее создаем «action».
Тогда в плагине нужно писать так
Тогда в плагине нужно писать так
$resource->set('action', $resource->getTVValue('tv.action'));
Вся суть сводится к тому, чтобы добавлять все поля в одну таблицу. Но этому же есть какой-то предел. Например, у товара minishop может быть множество свойств и все их не запихнешь в одну таблицу. Или речь идёт о полях, по которым наиболее важна скорость выборки?
Второе. Когда необходимо постоянно работать с некоторыми TV, появляется смысл добавить дублирующие значения в основную таблицу. В противном случае это не нужно.
Это не совсем дубль. ТВ-шки используются для отображения в форме в админке — они могут быть разного типа, чекбоксы, селекты и т.п. Они нужны для работы менеджеров. А поля в таблице сохраняют только текущее значение ТВ. Они предназначены для использования в запросах. Василий привел пример джойнов для двух ТВ. И это выглядит пугающе. А представьте, что вам нужно обработать несколько ТВ-шек. Мало того, что потратите кучу времени, чтобы грамотно составить запрос со всеми этими джойнами, так еще и такой запрос будет нагружать сайт. А в данном случае, вы работаете с одной таблицей со всеми вытекающими плюсами. Это нужно не всем, а только тем, кто активно пользуется ТВ-шками.
Кроме того, нет ничего страшного в том, чтобы добавить даже 30 полей. Думаю, даже вы не заметите разницы, а уж mySQL точно.
Кроме того, нет ничего страшного в том, чтобы добавить даже 30 полей. Думаю, даже вы не заметите разницы, а уж mySQL точно.
Помоему в мускуле есть ограничения на кол-во полей определённого типа. Или это дело настроить где можно?
Помню вроде про 4096 столбцов. Про типы ничего сказать не могу.
Новая тенденция «Нафиг всю эту сложную архитектуру, 4096 столбцов решат все ваши проблемы» )))
Я надеюсь, вы понимаете о чем пишете.
TV — это способ расширения системных таблиц, позволяющий эмулировать добавление новых полей — на странице теги полей и TV одинаковы. Он позволяет обычным менеджерам, не программистам, достаточно легко расширять таблицу ресурсов под собственные простые задачи (заметьте, для таблицы юзеров придуман другой механизм). Но за удобство надо платить. Для более сложных задач авторы MODX предлагают решение в виде CRC, в котором вы можете добавлять свои поля в таблицу!!! Вот вам два легальных способа расширения таблиц. Если вы сторонник только первого способа, то предложенный мной вариант явно не для вас. Я даже написал рекомендацию для таких как вы.
TV — это способ расширения системных таблиц, позволяющий эмулировать добавление новых полей — на странице теги полей и TV одинаковы. Он позволяет обычным менеджерам, не программистам, достаточно легко расширять таблицу ресурсов под собственные простые задачи (заметьте, для таблицы юзеров придуман другой механизм). Но за удобство надо платить. Для более сложных задач авторы MODX предлагают решение в виде CRC, в котором вы можете добавлять свои поля в таблицу!!! Вот вам два легальных способа расширения таблиц. Если вы сторонник только первого способа, то предложенный мной вариант явно не для вас. Я даже написал рекомендацию для таких как вы.
Так и думал, что коммент как-то не так воспримется. Смысл один, мы либо добавляем поля прямо в основную таблицу, либо в какую-то связанную таблицу, будь то таблица ТВ-шек, свойств товара или что-то ещё. В первом случае всё просто и быстро, но не очень гибко и красиво, во втором — нужны джойны, зато всё красиво и гибко.
Кстати, а ведь этот способ подойдёт и для сортировки и фильтрации extended-полей!
Надо будет потом попробовать…
Надо будет потом попробовать…
Точно. Идея очень здравая и избавила бы от многих неудобств.)
И сейчас все побегут массово расширять все системные таблицы )))
Важно! Добавил рекомендации для использования этого метода, чтобы избежать страшных последствий.
Где можно почитать про возможные значения phptype?
Мне нужно добавить несколько абсолютно-разных полей, но как я понял, phptype — это не атрибут базы, а php-эквивалент для dbtype?
Мне нужно добавить несколько абсолютно-разных полей, но как я понял, phptype — это не атрибут базы, а php-эквивалент для dbtype?
Я для себя понимаю так:
Это тип, к которому будет приведено значение полученное из базы, средствами xPDO. То бишь dbtype может быть text, а phptype — и string, и json.
Я читаю здесь — github.com/modxcms/revolution/blob/89c79f7ae886184dd4136cfe09663bf5c019ee7d/core/model/schema/modx.mysql.schema.xml
Это тип, к которому будет приведено значение полученное из базы, средствами xPDO. То бишь dbtype может быть text, а phptype — и string, и json.
Я читаю здесь — github.com/modxcms/revolution/blob/89c79f7ae886184dd4136cfe09663bf5c019ee7d/core/model/schema/modx.mysql.schema.xml
Спасибо!
я вот воплощаю пашиным плагином, и у меня на методе renderInput вот такое на страницу вылезает
это при том, что migx полей у шаблона нет. Для проверки отключаю плагин migx — вылезает путь из yandexcoordstv. Включаю migx обратно, лезу в него, закоменчиваю строки
— всё норм. Что за колдунство?) как это работает? как убрать надпись?
это при том, что migx полей у шаблона нет. Для проверки отключаю плагин migx — вылезает путь из yandexcoordstv. Включаю migx обратно, лезу в него, закоменчиваю строки
case 'OnTVInputRenderList':
$modx->event->output($corePath.'elements/tv/input/');
break;
— всё норм. Что за колдунство?) как это работает? как убрать надпись?
короче, все проигнорировали, а я решил-таки проблему спустя полгода))
пашиному плагину нужно добавить событие 'OnTVInputRenderList' с таким приоритетом, чтобы он точно вызывался последним. И… больше ничего не делать. Все красиво.
пашиному плагину нужно добавить событие 'OnTVInputRenderList' с таким приоритетом, чтобы он точно вызывался последним. И… больше ничего не делать. Все красиво.
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.