Рефакторинг простого PHP приложения в MODx
Этот урок показывает как сделать рефакторинг смешаного PHP/HTML файла в MODx Revolution. Подходит для тех, кто хорошо знает PHP, но по-прежнему изучает основы MODx. Если вы соответствуете следующему описанию, то урок будет для вас полезным:
- Вы пишете скрипты, которые совмещают PHP и HTML;
- Вы понимаете PHP циклы и массивы;
- Вы пишете PHP код, который обращается к базе данных и извлекает записи;
- У вас есть базовое понимание того, как MODx снипетты и другие элементы работают.
Для примера я буду работать с упрощённой версией реального проекта, который я недавно завершил. Приложение было разработано в PHP/HTML и я сделал следующее:
- Извлёк список записей из таблицы базы данных;
- Отобразил записи в хтмл таблице;
- Вывел два выпадающих окна выбора для фильтрации результатов значений колонок.
Начинаем со смешаного PHP/HTML файла
Оригинальные искодники для этого приложения выглядят приблизительно так (упрощено для чёткости понимания):
Файл: acctCodes.php (оригинальная версия)
<html> <head> <title>SFS Account Codes and Definitions</title> </head> <body> <h1>SFS Account Codes and Definitions</title> <form> <!—первое окно ВЫБОРА --> <div> <label for="byCategory">Find by Category:</label> <select name="byCategory" id="byCategory"> <option>Pick a Category</option> <?php //некоторый код для соединения с базой данных $values = // некоторый код, который запускает запрос для извлечения данных для первого окна выбора foreach ($values as $value) { echo '<option value="' . $value . '">' . $value . '</option>'; } ?> </select> </div> <!—второе окно ВЫБОРА --> <div> <label for="byType">Find by Type:</label> <select name="byType" id="byType"> <option>Pick a Type</option> <?php $values = // код для запуска запроса для извлечения значений для 2го окна выбора foreach ($values as $value) { echo '<option value="' . $value . '">' . $value . '</option>'; } ?> </select> </div> <!—таблица кодов аккаунта --> <table id="acctCodes"> <thead> <!—для упрощения вместо всех <этих тегов> я вставил этот комментарий --> </thead> <tbody> <?php $records = // код, который запускает запрос для извлечения записей таблицы foreach ($records as $record) { ?> <tr> <td><?php echo $record['category']; ?></td> <td><?php echo $record['status']; ?></td> <td><?php echo $record['account']; ?></td> <td><?php echo $record['acctType']; ?></td> <td><?php echo $record['title']; ?></td> <td><?php echo $record['definition']; ?></td> </tr> <?php } ?> </tbody> </table> </body> </html>
PHP код, приведенный здесь – очень прост; есть три отдельных блока кода, где осуществляются запросы и затем результаты выводятся на страницу, обёрнутые в соответствующие HTML теги. В третьем – несколько изощрённый PHP цикл с HTML кодом, для того, чтобы избежать громоздких echo выражений. Как видите – ничего сложного.
Но нужно кое-что еще подчистить, чтобы подготовить данный код к MODx.
Часть 1: Очистка (Вначале установите значения, а дальше вывод)
Первый шаг для подготовки этого PHP кода для MODx (и основной шаг) – это избавится от перемешивания логики приложения и разметки страницы. Вместо исполнения каждого запроса, как раз перед их запуском, мы вначале выполним все наши запросы, а потом при необходимости выведем через echo результат.
Код преобразится к следующему виду (я снова упростил, для чёткого понимания процесса):
Файл: acctCodes.php(очищенная версия)
<?php //некоторый код соединения с базой данных $values = // некоторый код, который выполняет запрос, чтобы извлечь данные для 1го выпадающего меню $options1 = ''; foreach ($values as $value) { $options1 .='<option value="' . $value . '">' . $value . '</option>'; } $values = // некоторый код, который выполняет запрос, чтобы извлечь данные для 2го выпадающего меню $options2 = ''; foreach ($values as $value) { $options2 .= '<option value="' . $value . '">' . $value . '</option>'; } $records = // некоторый код, который выполняет запрос, чтобы извлечь записи из таблицы $trrows = ''; foreach ($records as $record) { $trrows .= '<tr><td>' . $record['category'] . '</td><td> . $record['status'] . '</td> <td>' . $record['account'] . '</td> <td>' . $record['acctType'] . '</td> <td> . $record['title'] . '</td> <td> . $record['definition'] . '</td></tr>'; } ?> <html> <head> <title>SFS Account Codes and Definitions</title> </head> <body> <h1>SFS Account Codes and Definitions</title> <form> <!-- первое окно выбора --> <div> <label for="byCategory">Find by Category:</label> <select name="byCategory" id="byCategory"> <option>Pick a Category</option> <?php echo $options1; ?> </select> </div> <!-- второе окно выбора --> <div> <label for="byType">Find by Type:</label> <select name="byType" id="byType"> <option>Pick a Type</option> <?php echo $options2; ?> </select> </div> <!-- таблица кодов аккаунта--> <table id="acctCodes"> <thead> <!—для упрощения вместо всех <этих тегов> я вставил этот комментарий --> </thead> <tbody> <?php echo $trrows; ?> </tbody> </table> </body> </html>
Это сделает разметку намного проще для просмотра и редактирования. Теперь весь код PHP содержится в 4 блоках кода:
Первый делает запросы и устанавливает значение, следующие три просто выводят значения на экран.
Таким образом мы намного лучше оформили наше маленькое вебприложение в MODx.
Часть 2: Сниппеты и заполнители
Чтобы привести наш улучшенный код в MODx, нам нужно сделать несколько дополнительных изменений. Наш код HTML может быть прямо скопирован в MODx Ресурс. В то же время, мы не можем сделать то же самое с кодом PHP. Нам нужно заменить его нашим Сниппетом и некоторыми Заполнителями.
Наше содержимое ресурса MODx будет выглядеть приблизительно так:
MODx Ресурс: 'acctCodes'
<html> <head> <title>SFS Account Codes and Definitions</title> </head> <body> <h1>SFS Account Codes and Definitions</title> <form> <!-- первый блок SELECT --> <div> <label for="byCategory">Find by Category:</label> <select name="byCategory" id="byCategory"> <option>Pick a Category</option> </select> </div> <!-- второй блок SELECT --> <div> <label for="byType">Find by Type:</label> <select name="byType" id="byType"> <option>Pick a Type</option> </select> </div> <!-- таблица кодов аккаунтов --> <table id="acctCodes"> <thead> <!-- энный элемент таблицы. Для упрощения --> </thead> <tbody> </tbody> </table> </body> </html>
В нашем ресурсе, четыре основные блока кода PHP были заменены тегами MODx. Первый, [[accountCodes]] будет запускать Сниппет accountCodes (который мы еще не написали). Следующие три тега – это Заполнители, которые будут заменены значениями, которые будет устанавливать наш Сниппет.
Теперь нам нужно продолжить и создать этот Сниппет.
Часть 3: Сниппет
Наш "accountCodes" сниппет будет содержать код из нашего первого PHP блока – все запросы кода и циклы, которые создают HTML, который выводится на экран. Единственной вещью, которую мы собираемся добавить – это несколько строчек MODx API кода, который определит эти выходные значения как значения для заполнителей (для наших тегов заполнителей ресурса).
Сниппет: "accountCodes"
<?php //некоторый код для соединения с базой данных $values = // некоторый код, который выполняет запрос, чтобы извлечь данные для 1го выпадающего меню выбора $options1 = ''; foreach ($values as $value) { $options1 .='<option value="' . $value . '">' . $value . '</option>'; } $values = // некоторый код, который выполняет запрос, чтобы извлечь данные для 2го выпадающего меню выбора $options2 = ''; foreach ($values as $value) { $options2 .= '<option value="' . $value . '">' . $value . '</option>'; } $records = // некоторый код, который делает запрос для взятия записей для таблицы $trrows = ''; foreach ($records as $record) { $trrows .= '<tr><td>' . $record['category'] . '</td><td> . $record['status'] . '</td> <td>' . $record['account'] . '</td> <td>' . $record['acctType'] . '</td> <td> . $record['title'] . '</td> <td> . $record['definition'] . '</td></tr>'; } $modx->setPlaceholder('options1', $options1); $modx->setPlaceholder('options2', $options2); $modx->setPlaceholder('trrows', $trrows); ?>
Это наш сниппет. Единственное отличие от оригинального PHP кода – это три строчки "setPlaceholder" кода внизу. Они просто говорят MODx, что если и когда он встречает заполнитель [[+options1]] на странице – он должен заменять его на значение $options1. Заполнители – это простой и элегантный способ в MODx управление значениями вывода в сниппетах.
Теперь мы сделали всё, что нужно, чтобы взять наши веб приложения и запустить их внутри MODx. В то же время, нужно сделать несколько дополнительных шагов, которые сделают наше приложение намного лучше, это использовать функционал MODx API. Наше текущее выполнение пока я бы оценил только на троечку. Давайте сделаем его немного лучше.
Часть 4: Используйте Чанки как Шаблоны вывода
Недостаток нашего текущего кода PHP состоит в том, что он по-прежнему смешивает разметку с логикой. Наши циклы 'foreach' по-прежнему содержат грубые куски кода HTML разметки:
foreach ($values as $value) { $options1 .= '<option value="' . $value . '">' . $value . '</option>'; }
Эта стратегия далека от идеальной: предположим начальник просит какого-нибудь верстальщика поменять HTML для этого приложения, добавив некоторый дополнительный атрибут к каждому тегу <option>. Нам бы не хотелось, чтобы верстальщик лез в наш PHP скрипт, не так ли?
Есть несколько способов поправить данную ситуацию в PHP – брать HTML из внешних файлов и делать действия над строками. В то же время, нам не нужно беспокоится о таком решении. Для такого случая, MODx предлагает другое простое и элегантное решение: Чанки и Заполнители.
Тег "option" из HTML кода сниппета сверху, может быть записан как MODx чанк, называемый "option":
<option value=""></option>
Как видите – ничего сложного. Это всего лишь HTML код с парочкой заполнителей внутри. "option" - это общиепринятый полезный чанк, который может быть использован где-угодно – многочисленными сниппетами во различных ресурсах.
Теперь мы можем поменять наш сниппет - больше MODx API, чтобы использовать значение этого чанка в нашем цикле.
<?php //некоторый код для соединения с базой данных $values = // некоторый код, который делает запрос, чтобы взять данные для первого ниспадающего меню выбора $options1 = ''; foreach ($values as $value) { $modx->setPlaceholder('value', $value); $options1 .= $modx->getChunk('option'); }
Теперь наш цикл устанавливает заполнитель для $value и обрезает значение чанка 'option' для строки вывода.
Это позволит нам полностью отделить HTML код от нашего PHP скрипта. Если HTML верстальщику нужно добавить атрибут к элементам <option>, то он нможет это просто сделать редактируя чанк "option". (Только скажите ему не трогать эти заполнители!)
Строки таблицы могут быть сделаны аналогичным образом:
Чанк: "trrows"
<tr> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> </tr>
и код PHP
foreach ($records as $record) { $trrows .= $modx->getChunk('trrows', $record); }
Примите во внимание, что нам не нужно устанавливать какие-либо заполнители! Так как $record – это ассоциативным массив, то мы можем передать его в getChunk() как аргумент и заполнители автомагически** будут установлены для каждой пары ключ/значение в массиве!
(**MODX просто неимоверно крут!**)
Теперь нам можно оставить все наши HTML куски в чанки и привести наш Сниппет следующим образом:
<?php //некоторый код для соединения с базой данных $values = // некоторый код, который делает запрос, чтобы взять данные для первого выспадающего меню выбора $options1 = ''; foreach ($values as $value) { $modx->setPlaceholder('value', $value); $options1 .= $modx->getChunk('option'); } $values = // некоторый код, который делает запрос, чтобы взять данные для второго выспадающего меню выбора $options2 = ''; foreach ($values as $value) { $modx->setPlaceholder('value', $value); $options2 .= $modx->getChunk('option'); } $records = // код, который делает запрос для взятия записей для таблицы $trrows = ''; foreach ($records as $record) { $trrows .= $modx->getChunk('trrows', $record); } $modx->setPlaceholder('options1', $options1); $modx->setPlaceholder('options2', $options2); $modx->setPlaceholder('trrows', $trrows); ?>
Это начинает выглядеть довольно-таки хорошо. Мы используем преимущества MODx API для шаблонизации наших выводимых строк и наш сниппет теперь выглядит действительно аккуратно. В то же время есть некоторая вещь по этому поводу, которая беспокоит меня и может быть проблематической – теперь мы имеем вызовы MODx API, котоыре пересекаются с логикой основного приложения. Если мы на 100% уверенны, что мы будем выводить эти данные на эту страницу в таком формате, тогда это не является проблемой. Но, если есть какая-либо вероятность того, что эти данные могут быть использованы другим образом – где-нибудь вне MODx, то было бы неплохо держать логику основного приложения отдельно от MODx кода. В данный момент наше приложение получает оценку «хорошо». Давайте нашу оценку изменим на «отлично»,
Часть 5: Отделите логику вашего основного приложения
Всё, что нам нужно сделать – это финальный этам разделения нашего сниппета на 2 секции. Первая секция будет содержать логику основного приложения и будет возвращать данные массивов как члены одного большого массива. Это будет сделано с помощью только PHP и в идеале должно хранится в виде файла на нашем сервере.
Вторая секция будет сам сниппет. Он вначале возьмёт массив, возвращаемый с помощью файла логики, дальше будет использовать чанки для шаблонизации выводимых радов и наконец установит заполнители для вывода результатов в ресурсе.
Вот как всё будет выглядеть в финале:
Файл: acctCodes.php
<?php // $values = // код c запросом, чтобы взять данные для первого выспадающего меню выбора $values2 = // код c запросом, чтобы взять данные для второго выспадающего меню выбора $records = // код c запросом для взятия записей для таблицы return array('values'=>$values, 'values2'=>$values2, 'records'=>$records); ?>
Сниппет: 'acctCodes'
<?php $data = include_once(MY_INCLUDE_PATH . 'acctCodes.php'); $options1 = ''; foreach ($data['values'] as $value) { $modx->setPlaceholder('value', $value); $options1 .= $modx->getChunk('option'); } $options2 = ''; foreach ($data['values2] as $value) { $modx->setPlaceholder('value', $value); $options2 .= $modx->getChunk('option'); } $trrows = ''; foreach ($data['records'] as $record) { $trrows .= $modx->getChunk('trrows', $record); } $modx->setPlaceholder('options1', $options1); $modx->setPlaceholder('options2', $options2); $modx->setPlaceholder('trrows', $trrows); ?>
Теперь у нас все как нужно. Давайте еще раз просмотрим, что мы сделали:
- Вся логика нашего основного приложения в файле на сервере (полностью вне MODx).
- Мы передаём строго данные (никакой HTML разметки) в наш сниппет MODx.
- Мы позволяем MODx управлять шаблонизацией выводимых записей.
- Мы устанавливаем все значение заполнителей одним сниппетом вверху ресурса.
- Мы выводим наши данные на страницу, используя при необходимости заполнители.
Мы прошли довольно-таки значительный путь от нашего смешанного PHP/HTML файла, не правда ли? Я бы оценил эту финальную часть на оценку «отлично». Остальные, возможно, имеют даже лучшие идеи как отделить некоторые вещи. Но для такого «пхпшника» как я, это неплохое начало;). Надеюсь вы научились ключевым идеям, когда писали своё первое PHP приложение для использования в MODx. Не стесняйтесь добавлять комментарии, делится идеями или предложениями по поводу этого урока ниже.
14-12-2011 сниппеты уроки MODx Revolution чанки рефакторинг Виктор Матушевский
Alex Kostin
16.12.2011 10:26Ну что же Вы хабр то не читаете?
Я там этот перевод в начале сентября запостил.
http://habrahabr.ru/blogs/modx/128088/
Не совсем гладко, конечно, получилось, ну да смысл понятен.
А статья, да, полезная и нужная.
И спасибо за Ваши переводы!
PS. Про верстальщиков понравилось. :) У Джеймса Ротеринга, согласитесь, определение более общее было, но и куда более неприятное (я про Скиппи). Видимо, что-то личное.
Viktorminator
16.12.2011 15:31Да. Я когда на следующий день погуглил "рефакторинг", то увидел статью на хабре. Ну ничего :). Буду теперь вначале гуглить, а потом переводить...
Пожалуйста...Да, может какой-то верстальщик ему код попортил, вот теперь их и недолюбливает.
bm
11.05.2012 10:54MODx действительно крут. Стоит изучить