Я решил ответить на это письмо статьёй, так как многим начинающим программистам будет интересно эти комментарии почитать.
Автор учится писать сниппеты для 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 массива). Вот он:
Результат расчета я вывожу на экран, но хочу еще дать возможность пользователю получать результаты расчета на почту — это я тоже сделала с помощью готового сниппета. Но отправить могу только сам результат расчёта, а хочется отправить еще параметры, по которым считали, т.е. те, что на данный момент у меня в GET-массиве.
P.S. Вернее то, что я написала в $out — все отправляется на почту, но в одну строку без форматирования. А нужно чтобы было красиво. И наверное писать этот сниппет надо как-то по-другому.
Попробуйте такой способ: https://ilyaut.ru/reposts/sending-mail-through-modmail/
Отправляю так:
Очень интересный урок, но возник вопрос, каким образом можно реализовать возможность выводить меню любого уровня вложенности? Это через цикл делается? Благодарю за совет или подсказку!