Рефакторинг простого 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. Не стесняйтесь добавлять комментарии, делится идеями или предложениями по  поводу этого урока ниже.