Posted on Sep 02, 2011 by James Rotering
PHP Skill Level: средний
MODx Skill Level: новичок
Описание: В этом руководстве показано, как переделать для MODx Revolution файл со смешанным кодом PHP/HTML. Оно для людей, которым комфортно работать с PHP, но они все еще учатся основам MODx.
Если Вы подходите под большинство следующих описаний, это руководство должно быть Вам полезным:
— Вы писали скрипты, которые сочетают PHP код и HTML
— Вы понимаете циклы и массивы PHP
— Вы писали PHP код, который подключается к базе данных и извлекает записи
— У вас есть общее представление о том, как работают MODx сниппеты и другие элементы
Для примера я рассмотрю упрощенную версию реального проекта, который я недавно закончил.
Приложение было разработано на PHP/HTML и делает следующее:
— получает список записей из таблицы базы данных
— отображает записи в HTML-таблице
— отображает два выпадающих поля выбора для фильтрации результатов по определенным значениям столбцов.
Если Вам интересно, работу приложения Вы можете увидеть здесь.
Начинаем со смешанного PHP/HTML-файла
Исходный код для этого приложения выглядел примерно так (упрощен для ясности):
Файл: acctCodes.php (оригинальная версия)
<html>
<head>
<title>Номера счетов и определения SFS</title>
</head>
<body>
<h1>Номера счетов и определения SFS</title>
<form>
<!-- Первый SELECT box -->
<div>
<label for="byCategory">Поиск по категории:</label>
<select name="byCategory" id="byCategory">
<option>Выберите категорию</option>
<?php
// некий код - подключается к базе данных
$values = // некий код - выполняет запрос для получения значений первого выпадающего списка
foreach ($values as $value) {
echo '<option value="' . $value . '">' . $value . '</option>';
}
?>
</select>
</div>
<!-- Второй SELECT box -->
<div>
<label for="byType">Поиск по типу:</label>
<select name="byType" id="byType">
<option>Выберите тип</option>
<?php
$values = // некий код - выполняет запрос для получения значений второго выпадающего списка
foreach ($values as $value) {
echo '<option value="' . $value . '">' . $value . '</option>';
}
?>
</select>
</div>
</form>
<!-- Таблица номеров счетов -->
<table id="acctCodes">
<thead>
<!-- Вы знаете, как выглядят ячейки <th>. Я здесь упрощаю -->
</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.
Часть первая: Чистка (Сначала получаем значения, а затем вывод)
Первый шаг к чистке этого PHP кода для MODx (да и в целом) — это уход от смешивания логики приложения с разметкой страницы. Вместо выполнения каждого запроса перед тем как его использовать, мы запустим все запросы, а затем по необходимости просто выведем результаты с помощью echo.
В результате получаем что-то вроде этого (опять же, очень упрощено для ясности):
Файл: acctCodes.php (очищенная версия)
<?php
// некий код - подключается к базе данных
$values = // некий код - выполняет запрос для получения значений первого выпадающего списка
$options1 = '';
foreach ($values as $value) {
$options1 .= '<option value="' . $value . '">' . $value . '</option>';
}
$values = // некий код - выполняет запрос для получения значений второго выпадающего списка
$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</title>
</head>
<body>
<h1>Номера счетов и определения SFS</h1>
<form>
<!-- Первый SELECT box -->
<div>
<label for="byCategory">Поиск по категории:</label>
<select name="byCategory" id="byCategory">
<option>Выберите категорию</option>
<?php echo $options1; ?>
</select>
</div>
<!-- Второй SELECT box -->
<div>
<label for="byType">Поиск по типу:</label>
<<font color="#006699">select name="byType" id="byType">
<option>Выберите тип</option>
<?php echo $options2; ?>
</select>
</div>
</form>
<!-- Таблица номеров счетов -->
<table id="acctCodes">
<thead>
<!-- Вы знаете, как выглядят ячейки <th>. Я здесь упрощаю -->
</thead>
<tbody>
<?php echo $trrows; ?>
</tbody>
</table>
</body>
</html>
Это делает разметку гораздо более легкой для просмотра и редактирования. Весь код PHP содержится в четырех блоках: первый же запрос устанавливает значения, а следующие три простыми командами echo выводят эти значения на экран.
Это уже более приемлемо для переноса этого небольшого веб-приложения в MODx.
Часть вторая: Сниппеты и плейсхолдеры
Чтобы перенести наш очищенный код в MODx, нам нужно сделать несколько дополнительных изменений.
Наш HTML код можно сразу копировать в ресурс MODx. (Ресурс более не изменится! — прим. пер.)
Но код PHP нам необходимо заменить сниппетами и некоторыми плейсхолдерами.
Содержимое нашего ресурса MODx будет выглядеть следующим образом:
Ресурс MODx: 'acctCodes'
[[accountCodes]]
<html>
<head>
<title>Номера счетов и определения SFS</title>
</head>
<body>
<h1>Номера счетов и определения SFS</h1>
<form>
<!-- Первый SELECT box -->
<div>
<label for="byCategory">Поиск по категории:</label>
<select name="byCategory" id="byCategory">
<option>Выберите категорию</option>
[[+options1]]
</select>
</div>
<!-- Второй SELECT box -->
<div>
<label for="byType">Поиск по типу:</label>
<select name="byType" id="byType">
<option>Выберите тип</option>
[[+options2]]
</select>
</div>
</form>
<!-- Таблица номеров счетов -->
<table id="acctCodes">
<thead>
<!-- Вы знаете, как выглядят ячейки <th>. Я здесь упрощаю -->
</thead>
<tbody>
[[+trrows]]
</tbody>
</table>
</body>
</html>
В нашем ресурсе четыре блока кода PHP были заменены тегами MODx. Первый, [[accountCodes]], будет вызывать сниппет accountCodes (который мы еще не написали). Следующие три тега — плейсхолдеры, которые будут заменены на значения, заданые нашим сниппетом.
Теперь нам нужно написать этот сниппет
Часть третья: Сниппет
Наш сниппет «accountCodes» будет содержать код из этого первого блока PHP — все запросы и цикл. Единственное, что мы собираемся добавить — несколько строк кода API MODx для создания плейсхолдеров.
Сниппет: «accountCodes»
<?php
// некий код - подключается к базе данных
$values = // некий код - выполняет запрос для получения значений первого выпадающего списка
$options1 = '';
foreach ($values as $value) {
$options1 .='<option value="' . $value . '">' . $value . '</option>';
}
$values = // некий код - выполняет запрос для получения значений второго выпадающего списка
$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. Однако мы можем предпринять несколько дополнительных шагов, которые сделают наше приложение гораздо сексуальнее, используя больше функций API в MODx. Я оценил бы нашу текущую реализацию на «удовлетворительно» (в оригинале — «C». Давайте продолжим работу.
Часть четвертая: Используем чанки как шаблоны вывода
Одним из критических замечаний, которые могут быть сделаны нашему текущему коду PHP является то, что он по-прежнему смешивает разметку с логикой. Наш цикл 'foreach' по-прежнему содержит куски HTML разметки:
foreach ($values as $value) {
$options1 .= '<option value="' . $value . '">' . $value . '/option>';
}
Эта стратегия далека от идеала: предположим, босс просит HTML кодера Скиппи (в ориг. — «Skippy the HTML code monkey» — прим. пер.), изменить HTML код этого приложения, добавив несколько дополнительных атрибутов для каждого тега <option>. Но мы же не хотим, чтобы Скиппи («Skippy the HTML code monkey») оказался где-нибудь возле нашего PHP скрипта, не так ли?
Есть несколько способов исправить эту ситуацию в PHP — на ум приходит подтягивание HTML кода из внешних файлов и замена строк. Тем не менее нам не нужно беспокоиться и выяснять подходящее решение. Для этой задачи MODx предлагает другое простое и элегантное решение: чанки с плейсхолдерами.
Код «option» из приведенного выше сниппета может быть переписан в виде чанка, названного «option»:
Чанк: «option»
<option value="[[+value]]">[[+value]]</option>
В этом чанке нет ничего особенного — немного HTML с парой плейсхолдеров. «Option» — простой, полезный чанк, который можно использовать в любом месте — в нескольких сниппетах в разных ресурсах.
Теперь мы можем изменить наш сниппет, добавив немного больше кода API MODx, чтобы использовать значение этого чанка в нашем цикле:
<?php
// некий код - подключается к базе данных
$values = // некий код - выполняет запрос для получения значений первого выпадающего списка
$options1 = '';
foreach ($values as $value) {
$modx->setPlaceholder('value', $value);
$options1 .= $modx->getChunk('option');
}
Теперь наш цикл создаёт плейсхолдер для $value и добавляет значение чанка 'option' в выходную строку.
Это позволяет полностью отделить весь HTML код от PHP. Если Скиппи («Skippy the HTML code monkey») необходимо добавить атрибут к элементам <option>, он может сделать это путем редактирования чанка «option». (Просто скажите ему не трогать эти плейсхолдеры!)
Строки таблицы можно сделать аналогично:
Чанк: «trrows»
<tr>
<td>[[+category]]</td>
<td>[[+status]]</td>
<td>[[+account]]</td>
<td>[[+acctType]]</td>
<td>[[+title]]</td>
<td>[[+definition]]</td>
</tr>
и PHP код для его вызова
each ($records as $record) {
$trrows .= $modx->getChunk('trrows', $record);
}
Отметим, что в этом примере нам даже не придется устанавливать какие-либо плейсхолдеры!
Ассоциативный массив $record мы можем передать в GetChunk() в качестве аргумента, и плейсхолдеры будут автоматически** установлены для каждой пары ключ/значение в массиве!
(**MODX Awesomeness! You're soaking in it!**) (Аналогия со слоганом какого-то стирального порошка? — прим. пер.)
Так что теперь мы можем сохранить все наши куски 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);
Это уже начинает выглядеть очень хорошо. Мы использовали API MODx для шаблона вывода результата и наш сниппет выглядит действительно чистым. Тем не менее есть одна вещь, которая беспокоит меня, и может быть потенциально проблематична — теперь у нас вызовы MODx API приобщены к логике нашего приложения. Если мы на 100% уверены, что нам только когда-нибудь понадобятся данные, напечатанные на этой веб-странице и в этом формате, то это действительно не проблема. Но если есть вероятность, что эти данные, возможно, необходимо использовать по-другому — где-то за пределами MODx — было бы лучше держать основную логику приложения отдельно от сущностей MODx.
В настоящий момент приложение получает оценку «хорошо» (в оригинале — «B»). Давайте сделаем последний шаг и повысим нашу оценку до «отлично» (в оригинале — «A»).
Часть пятая: Отделение логики Вашего приложения
Все, что нам нужно сделать для последнего шага — разделить наш сниппет на две части. Первая часть будет содержать нашу логику приложения и вернёт все массивы как члены одного большого массива.
Это будет сделано на чистом PHP и в идеале должно быть сохранено в виде файла на нашем сервере.
Вторая часть — непосредственно сниппет. Он сначала получит массив, возвращаемый файлом логики, затем использует чанки для шаблонизации строк результата, и, наконец, установит плейсхолдеры для отображения результатов в ресурсах.
Вот чем это всё заканчивается:
Файл: acctCodes.php
<?php
// некий код - подключается к базе данных
$values = // некий код - выполняет запрос для получения значений первого выпадающего списка
$values2 = // некий код - выполняет запрос для получения значений второго выпадающего списка
$records = // некий код - выполняет запрос для получения записей для таблицы
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);
Теперь наши утки в ряд (Now we have our ducks in a row — прим. пер.). Давайте рассмотрим, что мы сделали:
— У нас есть логика приложения в файле на сервере (полностью за пределами MODx).
— Мы передаем данные напрямую (без какой-бы то ни было HTML разметки) в наш MODx сниппет.
— Мы позволяем MODx управлять шаблонизацией наших выходных записей.
— Мы устанавливаем все значения плейсхолдеров в одном сниппете в верхней части ресурса.
— Где это необходимо, мы выводим наши данные на страницу с помощью плейсхолдеров.
Мы прошли долгий путь от нашего первоначального смешанного PHP/HTML-файла, не так ли? Я бы оценил эту окончательную реализацию на «отлично» (в оригинале — «A»). Тем не менее, у кого-то, вероятно, есть лучшие идеи, как разделить различные проблемные области. Но для знатоков PHP (в ориг. — PHP hacks — прим. пер.) подобно мне, это хорошее место для начала.
Надеюсь, вы узнали некоторые из ключевых проблем при написании простых приложений PHP для использования в MODx. Не стесняйтесь оставлять какие-нибудь мысли, вопросы или предложения по поводу этого руководства в комментариях здесь.
Прим. пер. — в результате были созданы:
— ресурс acctCodes (ч.2)
— сниппет accountCodes (ч.5)
— файл acctCodes.php (ч.5)
— чанк option (ч.4)
— чанк trrows (ч.4)