Как НЕ надо программировать на 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=`\n
<img class="img_left" src="[[*img_programm]]" alt="[[*pagetitle]]" />
`]]
[[*content]]
[[*id:is=`6`:then=`\n
\n
<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">\n [[!+page.nav]]
</ul>
<div class="clear"></div>
`]]
[[*parent:is=`240`:then=`\n
<!--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=`\n
<p align="right"><span class="link"><a href="javascript:history.back();">Назад к списку</a></span></p>
`]]
<hr />
<div class="right_box3">\n [[$Block_news]]
[[$Block_Article]]
<div class="clrflt">\n </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), тем не менее их использование накладывает дополнительные расходы, так как дополнительно нагружают парсер. Плюс, как я писал тут, в чанках вы управляете кешем элементов только на уровне текущего документа, а не на уровне всего сайта. Потому старайтесь придерживаться правила: всякая логика в сниппетах, оформление в чанках.
Как же было бы правильней реализовать логику?
- Создаем чанк, который будет у нас шаблоном для вывода результата работы сниппета.
[[*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-ти раз пытался «найти» переменную, пережевывая чанк.
- Создаем сниппет.
<?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>