Николай Ланец
30 июня 2013 г., 14:41

Как НЕ надо программировать на MODX

Сегодня оптимизировал работу одного сайта и хочу дать пару советов, которые могут пригодиться многим начинающим программистам, особенно тем, кто по большей степени ведет разработку на уровне тегов MODX.
Кстати, сразу отмечу, что это сайт-визитка, и все страницы кешируемые за исключением отдельных блоков.
Итак, самый главный минус, с которым я столкнулся. В большинстве шаблонов контент страницы выводился через пользовательский чанк, в котором уже был прописан тег [[*content]]. Вот полный листинг этого чанка:
[[*longtitle:notempty=`<h2>[[*longtitle]]</h2>`]] [[Breadcrumbs? &homeCrumbTitle=`Главная` ¤tAsLink=`0` &showHomeCrumb=`1` &crumbSeparator=`<li><img src="/inc/img/right_box1_a.png" style="vertical-align:middle" alt="" /></li>` &respectHidemenu=`1` ]] [[*parent:is=`244`:then=` <img class="img_left" src="[[*img_programm]]" alt="[[*pagetitle]]" /> `]] [[*content]] [[*id:is=`6`:then=` <hr /> [[!getPage@Pages? &elementClass=`modSnippet` &element=`getResources` &parents=`6` &limit=`5` &sortby=`menuindex` &sortdir=`DESC` &tpl=`tpl.otziv` &includeTVs=`1` &includeContent=`1` &tvPrefix=`` &pageVarKey=`page` ]] <ul class="pageList"> [[!+page.nav]] </ul> <div class="clear"></div> `]] [[*parent:is=`240`:then=` <!--dl id="accordion"--> [[!getResources? &tpl=`ProgramsTpl_2` &tplFirst=`ProgramsTpl_2_First` &tplLast=`ProgramsTpl_2_Last` &includeTVs=`1` &tvPrefix=`` &includeContent=`1` &resources =`[[*list_prog_ch]]` &hideContainers=`1` &sortdir=`ASC` &limit=`100` ]] <!--/dl--> `]] [[*parent:is=`244`:then=` <p align="right"><span class="link"><a href="javascript:history.back();">Назад к списку</a></span></p> `]] <hr /> <div class="right_box3"> [[$Block_news]] [[$Block_Article]] <div class="clrflt"> </div>
Здесь предлагаю обратить внимание именно на конструкции типа [[*parent:is=`240`:then=`…
В чем же здесь проблема? Проблема в том, что MODX-парсер работает не так, как, к примеру, php-интерпретатор. По логике вещей здесь должно быть следующее: если ID родителя текущего документа равен 240, то выполняется блок Then.
По логике Да, а на практике Нет. Покажу простой пример как обрабатываются условия на php, чтобы показать, чего не происходит. Вот простой код:
if($modx->resource->parent == 240){ print time(); }
Так вот, в данном примере, если parent не будет равен 240, то функция time() не будет выполнена в принципе. При парсинге MODX же содержимое блока выполнится в любом случае, просто будет результат выведен на странице или нет, определит истинность условия.
В данном случае независимо от условия MODX-парсер выполнил полностью блок Then, а только потом на основе условия решит выводить результат этого блока или нет. То есть не зависимо от того, какой родитель у документа, все блоки в этом чанке будут выполнены. А меж тем этих блоков здесь четыре, при этом два из них — НЕкешируемый сниппет getPage. То есть даже если страница уже закеширована, каждый раз при обращении к ней MODX будет обрабатывать эти сниппеты, потому что они некешируемые. Как результат: 5-10 секунд на генерацию кода закешированной страницы против одной секунды с исправленной логикой.
Здесь мы подобрались к тому, как же правильней прописывать подобные вещи. Мой совет: меньше используйте чанки, больше сниппеты. Не смотря на то, что чанки психологически воспринимаются более простыми элементами (так как в них вроде как HTML, а не исполняемый php), тем не менее их использование накладывает дополнительные расходы, так как дополнительно нагружают парсер. Плюс, как я писал здесь, в чанках вы управляете кешем элементов только на уровне текущего документа, а не на уровне всего сайта. Потому старайтесь придерживаться правила: всякая логика в сниппетах, оформление в чанках.
Как же было бы правильней реализовать логику?
1. Создаем чанк, который будет у нас шаблоном для вывода результата работы сниппета.
[[*longtitle:notempty=`<h2>[[*longtitle]]</h2>`]] [[Breadcrumbs? &homeCrumbTitle=`Главная` ¤tAsLink=`0` &showHomeCrumb=`1` &crumbSeparator=`<li><img src="/inc/img/right_box1_a.png" style="vertical-align:middle" alt="" /></li>` &respectHidemenu=`1` ]] [[+pre_content]] [[*content]] [[+post_content]] <hr /> <div class="right_box3"> [[$Block_news]] [[$Block_Article]] <div class="clrflt"> </div>
На заметку: здесь есть два плейсхолдера: pre_content и post_content. При создании чанков, если не уверены в том, что для всех плейсхолдеров будут переданы переменные, на всякий случай создайте пустые Параметры для этого чанка. В нашем случае это Параметры pre_content и post_content. Не знаю насколько сейчас пофиксили, но раньше парсер до 10-ти раз пытался «найти» переменную, пережевывая чанк.
2. Создаем сниппет.
<?php $pre_content = ''; $post_content = ''; $parent = $modx->resource->id; switch ($parent) { case '244': $pre_content = '<img class="img_left" src="[[*img_programm]]" alt="[[*pagetitle]]" />'; $post_content = '<p align="right"><span class="link"><a href="javascript:history.back();">Назад к списку</a></span></p>'; break; case '6': $post_content = '<hr /> [[!getPage@Pages? &elementClass=`modSnippet` &element=`getResources` &parents=`6` &limit=`5` &sortby=`menuindex` &sortdir=`DESC` &tpl=`tpl.otziv` &includeTVs=`1` &includeContent=`1` &tvPrefix=`` &pageVarKey=`page` ]] <ul class="pageList"> [[!+page.nav]] </ul> <div class="clear"></div>'; break; case '240': $post_content = '<!--dl id="accordion"--> [[!getResources? &tpl=`ProgramsTpl_2` &tplFirst=`ProgramsTpl_2_First` &tplLast=`ProgramsTpl_2_Last` &includeTVs=`1` &tvPrefix=`` &includeContent=`1` &resources =`[[*list_prog_ch]]` &hideContainers=`1` &sortdir=`ASC` &limit=`100` ]] <!--/dl-->'; break; default:; } return $modx->getChunk('Template.Content_simple', array( 'pre_content' => $pre_content, 'post_content' => $post_content, ));
В этом сниппете мы не прописали дополнительное кеширование, но для нас сейчас главное то, что блоки внутри условий будут выполнены только тогда, когда условие будет выполнено. За счет этого мы на практике очень сильно сократили время на генерацию страницы. В итоге код не стал запутанней, но производительность выросла сильно.
И напоследок совет: не храните много кода в шаблонах. На многих сайтах страницы имеют одинаковые шапку и подвал на всем сайте (как правило только главная может существенно отличаться). Так вот создайте общие чанк header и footer, и подгружайте их в каждом шаблоне, чтобы не пришлось потом везде менять, если какой-то блок надо изменить. А то представьте сколько движений надо сделать, чтобы поменять дизайн и логику на сайте, на котором штук 15 вот таких шаблонов:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> [[$HEAD]] </head> <body> <!--header_wrap-start--> <div id="header_wrap"> <!--header-start--> <div id="header"> [[$header]] </div> <!--header-end--> </div> <div class="greenline"></div> <!--header_wrap-end--> <div class="banner"> <div class="banner_left"> [[$Menu.Slider]] </div> <div class="banner_right"> [[$Menu.top]] </div> <div class="clrflt"> </div> <div class="margin5"> </div> <div class="greenline"></div> <div class="clrflt"> </div> <div class="margin3"> </div> <marquee loop="infinite" behavior="altemate" bgcolor="#ddd" direction="left" height="20" width="100%" scrollamount="3" style="color:#076324; font-size:1.1em; padding:7px 0 0 0; font-family:Georgia, Times New Roman, Times, serif; font-style:italic; ">[[$Stroka]]</marquee> <div class="clrflt"> </div> </div> <!--banner-end--> <div id="content"> <!--content-start--> [[$Menu.left]] <div class="content_right fltright"> <div class="my_box"> [[Template.Content_simple]] </div> </div> <div class="clrflt"> </div> </div> <!--content-end--> <!--footer-start--> <div id="footer"> [[$Footer]] </div> <!--footer-end--> </body> </html>


Т.е. при работе с условиями при вызове "снипета с различными параметрами" либо "различными сниппетами" всю логику лучше реализовать в отдельном сниппете?
По хорошему да. Но это не всегда бывает удобно просто из-за того, что сниппеты не умеют возвращать массивы или объекты, а только строку, а на каждый чих чанк создавать - тоже не комильфо. Поэтому мы давно уже ушли от сниппетов в сторону процессоров + Smarty.
ну кстати пздтц какой то)))) Делаю например вызов снипета:
[[!testSnippet? &browser = `[[!+modxSiteTemplate]]` &tpl = `0` ]]
Сам снипет (testSnippet):
<?php $browser = $modx->getOption('browser', $scriptProperties, ''); $tpl = $modx->getOption('tpl', $scriptProperties, '');
if($tpl == 0) { echo «я для tpl = 0»; // Быстро обработался
}else{ echo «я для tpl != 0»; // Тут какой нить код запроса или еще что нить что обрабатывается долго или просто же die(); Например -:) die();
}
Какого хера обработается die(); и при вызове testSnippet с параметром &tpl = `0`?
1. Первое предупреждение за нехорошие слова.
2. Попробуйте все-таки посмотреть что у вас за $tpl. Сделайте так:
<?php $browser = $modx->getOption('browser', $scriptProperties, ''); $tpl = $modx->getOption('tpl', $scriptProperties, ''); // Смотрим содержимое и тип переменной var_dump($tpl); exit; if($tpl == 0) { echo «я для tpl = 0»; // Быстро обработался }else{ echo «я для tpl != 0»; // Тут какой нить код запроса или еще что нить что обрабатывается долго или просто же die(); Например -:) die(); }
Что выведет? Какая переменная?
ну или &tpl = `true` а в снипете if($tpl) все равно… как не крути обрабатываются все условия if
да, там строка, string, согласен.
Не пустая строка? То есть длина более нуля?
Вообщем я делаю так:
Шаблон:
[[!If? &subject=`[[!+modxSiteTemplate]]` &operand=`full` &then=` full [[$fullTemplate]]`
&else=` mobile [[$mobileTemplate]]` ]]
Чанк fullTemplate:
fullTemplate
[[!testSnippet? &browser = `[[!+modxSiteTemplate]]` &tpl = `true` ]]
Чанк mobileTemplate:
mobileTemplate
[[!testSnippet? &browser = `[[!+modxSiteTemplate]]` ]]
Если в снипете (testSnippet) делать так:
<?php $browser = $modx->getOption('browser', $scriptProperties, ''); $tpl = $modx->getOption('tpl', $scriptProperties, '');
if($tpl) { echo «я для fullTemplate»; }else{ echo «я для mobileTemplate»; }
То выводится все правильно. для мобилы снипет отдает echo «я для mobileTemplate»; для пр. echo «я для fullTemplate»;
НО если поставить в первое или последнее условие if например die(); или просто бесконечный цикл какой — нить… то обработает этот die() и для mobileTemplate и для fullTemplate.
То есть вы понимаете о чем я?
То есть вы понимаете о чем я?
Думаю да, понимаю. 99% расклад такой: не зависимо от того, какое у вас условие на уровне шаблона (я про [[!If?), чанки [[$fullTemplate]] и [[$mobileTemplate]] будут отработаны в любом случае. Это особенности MODX-парсера. Это вам не чистый php, где если условие if() не выполнилось, то и внутри него ничто не выполнится. А здесь If — это даже не функция, а просто тег (если говорить про шаблонизацию). В результате он или выведет, или не выведет результат. Но результат внутри в любом случае будет, то есть внутренние теги отработаются.
Это одна из серьезных таких причин, по которой мы используем modxSmarty, а не нативную шаблонизацию MODX-а. На уровне Smarty это было бы так:
{$modxSiteTemplate = $modx->getPlaceholder('modxSiteTemplate')} {if $modxSiteTemplate == 'full'} full {chunk name=fullTemplate} {else if $modxSiteTemplate == 'mobile'} mobile {chunk name=mobileTemplate} {/if}
Вот тут четко будет или одно выполнено, или другое.
Если хотите делать на чистом MODX-е, тогда не сниппет If вызывать надо, а свой сниппет напишите, который четко по условию будет вызывать тот или иной чанк. return $modx->getChunk($chunkName);
P.S. Код надо оборачивать в теги <code> ?
Большое спасибо за статью! Как раз хотел сократить кол-во шаблонов с помощью фильтров вывода, статья отговорила)) Только не нашел вызова снипета, или его вызывать не обязательно?
Заранее спасибо!
Вы сниппет этот и вызываете там, где должен выводиться контент.
Но вообще у меня там была формулировка «Как правильней». Это в данном случае, учитывая убогость изначальной основы заложенной предыдущими программистами. А вообще, так как я написал, мы и сами уже не делали бы, это я все давно писал. Сейчас у нас Smarty и расширяемые шаблоны с блоками.
Точно, извиняюсь — нашел
<div class="my_box"> [[Template.Content_simple]] </div>
Спасибо. Ну до Smarty мне пока рано )

Добавить комментарий