Pull to refresh

Рефакторинг простого PHP приложения для MODx

Reading time 11 min
Views 6.5K
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'] .  
       &nbsp;            '</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)
Tags:
Hubs:
+8
Comments 16
Comments Comments 16

Articles