Я решил ответить на это письмо статьёй, так как многим начинающим программистам будет интересно эти комментарии почитать.
Автор учится писать сниппеты для MODX и для примера поставил себе задачу написать относительно простой сниппет для вывода меню сайта.
Чтобы не раздувать статью буду давать ссылки на версии файла на Github. И вот с чего мы начнем: menucreator.php именно этот код автор попросил прокомментировать.
Избавляемся от дублирования кода
Итак… Первое, что бросается в глаза — сниппет состоит из двух практически одинаковых частей, разделенных условиемif (isset($showSubMenu) && $showSubMenu == 1) {
// ...
} else {
// ...
}Из-за того, что код в обеих частях практически идентичен и во многом повторяется, однажды случится такая ситуация — код в первой части мы изменим, а про вторую забудем, тогда поведение сниппета станет непредсказуемым.Попробуем исправить. Перенесем условие внутрь — как раз к тому месту, в котором начинаются различия в коде:
//...
if (isset($showSubMenu) && $showSubMenu == 1) //Если показываем подменю
{
$subwhere = array('hidemenu'=>$hidemenu, 'published' => true, 'parent'=>$resourceArray['id']);
$subresources = $modx->getCollection('modResource',$subwhere);
if (isset($subresources) && $subresources)
{
$output .= '<ul>';
}
foreach ($subresources as $subresource)
{
$subresourceArray = $subresource->toArray();
$output .= $modx->getChunk($template,$subresourceArray);
}
if (isset($subresources) && $subresources)
{
$output .= '</ul>';
}
}
$resourceArray['test1']='test22Ok';
$output .= $modx->getChunk($template,$resourceArray);
//...Отлично. Теперь вторая часть сниппета нам не нужна и мы ее удаляем. Код сразу сократился на 25 строк. Вот так код выглядит теперь.Оптимизируем выборку
На строчке № 36 мы проверяем значение поля parent. Это было бы оправдано, если мы хотели как-то выделить такие ресурсы в списке или сделать с ними что-то. Но здесь мы их просто пропускаем.На небольших сайтах это не страшно, но как только количество ресурсов начнет расти, скорость работы сниппета начнет падать — ведь ему нужно перебрать все ресурсы сайта.
А раз нам не нужны все ресурсы, просто вынесем условие с parent в выборку where
$where = array('hidemenu'=>$hidemenu, 'published' => true, 'parent' => 0);Ну и удаляем эту проверку, так как теперь она нам не нужна. Вот новая версия.Продолжаем оптимизацию
На строчке № 39 мы делаем еще один запрос в базу данных, чтобы «вытащить» список ресурсов второго уровня. Хоть в коде это лишь одна строчка, запросов будет много, так как она выполняется в цикле для каждого ресурса из предыдущей выборки.И опять таки, на небольших сайтах можно даже не заметить никаких проблем, но это не наш путь)) Мы выберем дочерние ресурсы перед циклом — за один запрос.
if (isset($showSubMenu) && $showSubMenu == 1) //Если показываем подменю
{
$parentIDs = array(); // Сюда запишем все ID-шники первого уровня
foreach ($resources as $resource)
{
$parentIDs[] = $resource->get('id');
}
// Составляем запрос, чтобы выбрать ресурсы второго уровня
$subwhere = array('hidemenu'=>$hidemenu, 'published' => true, 'parent:IN'=>$parentIDs);
$subresources = $modx->getCollection('modResource',$subwhere);
$parents = array(); // Здесь будем хранить уже дерево ресурсов, разбитых на группы
foreach ($subresources as $subresource)
{
$parentID = $subresource->get('parent');
$parents[$parentID][] = $subresource;
}
}Сниппет целиком.Отделяем логику от представления
Ну и тут уже кому как нравится, но чем меньше HTML внутри PHP-кода, тем лучше.Со сниппетом в комплекте должны идти чанки для оформления по умолчанию, чтобы пользователь мог создать свои, используя стандартные как пример.
row.menucreater
<li><a href="[[+uri]]">[[+menutitle:default=`[[+pagetitle]]`]]</a></li> [[+output:notempty=`<ul>[[+output]]</ul>`]]outer.menucreator
<ul[[+classes]]>[[+output]]</ul>
Теперь пользователь может менять оформление как угодно, не заглядывая в код сниппета.
Это одно из преимуществ MODX и мы должны им пользоваться.
Последний подход
Теперь «причешем» код, а так же укажем значение параметров по умолчанию. Здесь каждый решает сам, как ему форматировть код, как выводить ресурсы… Например, мне нравится составлять массив $output, а потом выводить его в виде строки, используя implode.<?php
ini_set('display_errors', -1);
error_reporting(E_ALL);
$tpl = $modx->getOption('tpl', $scriptProperties, 'row.menucreater');
$outerTpl = $modx->getOption('outerTpl', $scriptProperties, 'outer.menucreater');
$ulClass = $modx->getOption('ulClass', $scriptProperties, 'menu');
$classes = $ulClass ? ' class="'.$ulClass.'"' : "";
$showHideMenu = $modx->getOption('showHideMenu', $scriptProperties, 0);
$showSubMenu = $modx->getOption('showSubMenu', $scriptProperties, 0);
$outputSeparator = $modx->getOption('outputSeparator', $scriptProperties, PHP_EOL);
// Составляем условие для выборки первого уровня
$where = array('hidemenu' => 0, 'published' => 1, 'parent' => 0);
// Показываем скрытые пункты меню, если надо
if (isset($showHideMenu) && $showHideMenu == 1) {
$where['hidemenu'] = 1;
}
// Получаем список ресурсов первого уровня
$resources = $modx->getCollection('modResource',$where);
if ($showSubMenu == 1) {
// Составляем список ID ресурсов первого уровня
$parentIDs = array();
foreach ($resources as $resource) {
$parentIDs[] = $resource->get('id');
}
// Составляем запрос, чтобы выбрать ресурсы второго уровня
unset($where['parent']);
$where['parent:IN'] = $parentIDs;
// Получаем все ресурсы второго уровня
$subresources = $modx->getCollection('modResource', $where);
// Строим дерево ресурсов второго уровня
$parents = array();
foreach ($subresources as $subresource) {
$parentID = $subresource->get('parent');
$parents[$parentID][] = $subresource;
}
}
$output = array();
foreach ($resources as $resource) {
// Забираем все параметры ресурса для передачи их в чанк
$resArr = $resource->toArray();
// Проверяем, были ли найдены дочерние ресурсы
if (isset($parents) && isset($parents[$resArr['id']]) && is_array($parents[$resArr['id']])) {
$subOutput = array();
$subresources = $parents[$resArr['id']];
// Оформляем каждый ресурс второго уровня
foreach ($subresources as $subresource) {
$subresArr = $subresource->toArray();
$subOutput[] = $modx->getChunk($tpl,$subresArr);
}
}
// Разделяем ресурсы пустой строкой (или другим разделителем)
$resArr['output'] = implode($outputSeparator, $subOutput);
// И оформляем ресурс первого уровня
$output[] = $modx->getChunk($tpl,$resArr);
}
// Выводим ресурсы с разделителем
return $modx->getChunk($outerTpl, array('output' => $output, 'classes' => $classes));
Без комментариев код прекрасно чувствует себя на 50 строчках.Что дальше?
Вот мы и разобрали основные моменты в этом сниппете. Его можно и дальше развивать, например,- отмечать текущий пункт меню и его родителей,
- добавить возможность указать класс не только для UL, но и для LI,
- оптимизировать скорость, используя PDO для выборки,
- добавить возможность выводить меню любого уровня вложенности
- и прочее-прочее-прочее)
Но начало положено хорошее.
Объектная
Подскажите, пожалуйста, по поводу правильного написания сниппета.
У меня в нем происходит расчет по параметрам из формы (из GET массива). Вот он:
<?php $out = ''; if (isset($_GET)) { $price=$_GET['price']; // цена контракта $done=$_GET['done']; // выполнено $days=$_GET['days']; // дни просрочки $disp=$_GET['disp']; // дни на исполнение $cb=$_GET['cbproc']/100; // размер ставки ЦБ (вводится в %) $dcb=$cb/300; // размер ставки ЦБ за день $k=($days/$disp)*100; // расчет коэффициента * 100% if($k<=50) {$scb=0.01;} elseif ($k<100) {$scb=0.02;} elseif ($k>=100) {$scb=0.03;} $rsize1=$scb*$cb*$days; $peni1=($price-$done)*$rsize1; // пени По ПП 1063 $rsize2=$dcb*$days; // пени 1/300 ставки $peni2=($price-$done)*$rsize2; if($peni1!=0){ $out= '<p>Вы запрашивали расчёт пени для следующих параметров:</p> <ul> <li>Цена контракта, руб.: '.$_GET['price'].'</li> <li>Выполнено: '.$_GET['days'].'</li> <li>Дни просрочки: '.$_GET['done'].'</li> <li>Дни на исполнение: '.$_GET['disp'].'</li> <li>Размер ставки ЦБ: '.$_GET['cbproc'].'</li> </ul> <h3>Пени по Постановлению правительства № 1063: <span class="pnum">'.round($peni1 , 2, PHP_ROUND_HALF_UP).'<span></h3>'; } return $out; }Результат расчета я вывожу на экран, но хочу еще дать возможность пользователю получать результаты расчета на почту — это я тоже сделала с помощью готового сниппета. Но отправить могу только сам результат расчёта, а хочется отправить еще параметры, по которым считали, т.е. те, что на данный момент у меня в GET-массиве.P.S. Вернее то, что я написала в $out — все отправляется на почту, но в одну строку без форматирования. А нужно чтобы было красиво. И наверное писать этот сниппет надо как-то по-другому.
Попробуйте такой способ: https://ilyaut.ru/reposts/sending-mail-through-modmail/
Отправляю так:
[[!AjaxForm? &snippet=`FormIt` &form=`form.pdf` &emailTpl=`emailPdf_tpl` &pdfTpl=`pdf.tpl` &hooks=`hpdf,email` &emailSubject=`сообщение c pdf` &emailTo=`example@example.com` &validate=`name:required,email:required` &validationErrorMessage=`В форме содержатся ошибки!` &successMessage=`Сообщение успешно отправлено` ]]Очень интересный урок, но возник вопрос, каким образом можно реализовать возможность выводить меню любого уровня вложенности? Это через цикл делается? Благодарю за совет или подсказку!