ФорумПрограммированиеPHP для идиотов → Шаблонизатор - функция для сложной вёрстки

Шаблонизатор - функция для сложной вёрстки

  • stopkran

    Сообщения: 53 Репутация: N Группа: Кто попало

    Spritz 15 мая 2011 г. 9:41

    Искал что-то про алгоритмы и PHP, попал на статью http://pyha.ru/forum/topic/659.0. Очень понравилось. Но в отличном примере о верстальщике-извращенце логика шаблонизации всё-таки не совсем хорошая, там есть минимум два слабых места. Напомню код блочного шаблона:

    <!– BEGIN:index –> 

    <table cellpadding="3">
    <!– BEGIN:line –>
    <!– BEGIN:names –>
    <tr>
    <!– BEGIN:name –>
    <td><b>{team.name}</b></td>
    <!– END:name –>
    </tr>
    <!– END:names –>
    <!– BEGIN:cities –>
    <tr>
    <!– BEGIN:city –>
    <td>{team.city}<br><br></td>
    <!– END:city –>
    </tr>
    <!– END:cities –>
    <!– END:line –>
    </table>

    <!– END:index –>


    Этот шаблон парсится в цикле, в котором, в частности, используется условие

    if (++$j == sizeof($teams) || ++$i == 3)


    Так вот: 1) откуда берётся это волшебное число - "3"? 2) Наименования 'city' и 'name' должны быть указаны в трёх местах: в массиве данных, в шаблоне и в цикле обработки массива; последнее - лишняя слабость. Есть и ещё мелочь: в HTML не бывает таблиц с разным количеством ячеек в первой и последней строке (во всяком случае, без использования атрибутов 'rowspan'), а в данном примере работы XTemplate создаётся именно такая, "кривая" таблица.

    ***

    Шаблон - это то, что придумал верстальщик. А он придумал таблицу в три колонки (произвол) и в две строки (семантика) - эти условия и должны отражаться в шаблоне, это и есть в данном случае минимальный и неделимый блок:

    <tr><td><b>Спартак</b></td><td><b>Зенит</b></td><td><b>Шинник</b></td></tr>
    <tr><td>Москва</td><td>Санкт-Петербург</td><td>Ярославль</td></tr>


    Я чаще всего использую в качестве шаблонов функции. Это получилась довольно интересная задача - сделать функцию-шаблон для такой структуры - из "перпендикулярного" шаблону массива (с неизвестным заранее количеством строк):

    $teams = Array(
    Array('name' => 'Спартак', 'city' => 'Москва'),
    Array('name' => 'Зенит', 'city' => 'Санкт-Петербург'),
    Array('name' => 'Шинник', 'city' => 'Ярославль'),
    Array('name' => 'Сатурн', 'city' => 'Раменское'),
    Array('name' => 'Луч', 'city' => 'Владивосток')
    );


    Функция получилась такая:

    function tpl_row_transpose($arr = array()) {
    $res = '';
    while (count($arr)) {
    $name = array(1 => '', 2 => '', 3 => '');
    $city = $name;
    for($i = 1; $i <= count($name); $i ++) {
    if ($team = array_shift($arr)) {
    $name[$i] = $team['name'];
    $city[$i] = $team['city'];
    }
    }
    $res .= <<<TPL
    <tr><td><b>$name[1]</b></td><td><b>$name[2]</b></td><td><b>$name[3]</b></td></tr>
    <tr><td>$city[1]</td><td>$city[2]</td><td>$city[3]</td></tr>

    TPL;
    }
    return $res;
    }


    Эта функция, конечно не универсальна (в отличие от блочных шаблонов XTemplate). Но ведь и 1) данная задача верстки довольно необычна (логично, что для нестандартной задачи нужна отдельная функция), 2) появление загадочного числа "три" значительно демистифицируется.

    При расширении таблицы до 4-х колонок (ключевая проверка!) надо совершить больше действий, чем для XTemplate: надо изменить шаблон, добавив в него $name[4] и $city[4], а также добавить в массив $name элемент "4 => ''". Но эти действия достаточно логичны и наглядны; к тому же, развивая их логику, функцию можно улучшить. Например, вынести собственно шаблон в отдельный файл (сейчас, для простоты, в переменную) по "классической" схеме:

    $tpl = "<tr><td><b>[name]</b></td><td><b>[name]</b></td><td><b>[name]</b></td></tr>
    <tr><td>[city]</td><td>[city]</td><td>[city]</td></tr>";


    Функции в этом случае понадобится уже две (да и, по-хорошему, чтобы избежать всяких global, вообще нужно делать класс):

    function tpl_row_transpose2($arr = array(array('', '')), $tpl = '') {
    global $magic_number, $counter;
    $names = array_keys($arr[0]);
    if (!preg_match_all("/\[{$names[0]}\]/", $tpl, $values)) return 'false';
    $magic_number = count($values[0]);
    foreach($names as $value) {
    $counter = 0;
    $tpl = preg_replace_callback("/\[({$value})\]/", 'tpl2func', $tpl);
    }
    $tpl = '$curr_tpl = "' . $tpl . '";';
    $res = ''; $limit = 0;
    while (count($arr)) {
    for($i = 0; $i < $magic_number; $i ++) {
    if ($row = array_shift($arr))
    foreach($names as $value) ${$value}[$i] = $row[$value];
    else foreach($names as $value) ${$value}[$i] = '';
    }
    eval($tpl);
    $res .= $curr_tpl;
    }

    return $res;
    }

    function tpl2func ($matches){
    global $counter;
    return '$' . $matches[1] . '[' . $counter ++ . ']';
    }


    Это, типа, намного сложнее, чем "внешний" код обработки шаблона в XTemplate. Но ведь 1) там есть ещё и внутренний код (в подключаемом файле); 2) в системе XTemplate надо указывать сущности 'city' и 'name' в трёх местах, а функция tpl_row_transpose2 не требует таких подробностей (сущности должны коррелировать между собой только "напрямую" - в массиве и в шаблоне); 3) $magic_number "3" вычисляется без участия человека (на основе шаблона).

    Результат работы обеих функций можно посмотреть на стр. http://ir2.ru/wysiwyg/complex_template.php (php код файла - по той же ссылке с ?code).
  • AlexB

    Сообщения: 4290 Репутация: N Группа: в ухо

    Spritz 15 мая 2011 г. 10:09, спустя 27 минут 43 секунды


    1) откуда берётся это волшебное число - "3"?
    Очевидно, что это количество столбцов. В реальном, а не тестовом, коде это должно быть прописано в конфигурационном файле, ну например в виде константы.  


    2) Наименования 'city' и 'name' должны быть указаны в трёх местах: в массиве данных, в шаблоне и в цикле обработки массива; последнее - лишняя слабость.
    Массива данных, как такового, в реальной жизни нет.  Он опять же приведен как тестовый. На самом деле, скорее всего это массив есть результат работы другого слоя, который извлекает данные, как он внутри устроен к шаблонизации отношения не имеет, там может быть вообще SELECT * без всяких имен. В цикле обработки этих имен нет - там есть название блока, в котором должны отображаться соответствующие данные. Почувствуйте разницу .. это уже указание на элемент дизайна, а не на данные.    


    Есть и ещё мелочь: в HTML не бывает таблиц с разным количеством ячеек в первой и последней строке (во всяком случае, без использования атрибутов 'rowspan'), а в данном примере работы XTemplate создаётся именно такая, "кривая" таблица.
    Ну разумеется, можно дополнить код, чтоб он добивал исходные данные до кратного числу столбцов числа. Для простоты понимания это опущено, потому что опять же к пример тестовый и к шаблонизации это отношения не имеет.
  • stopkran

    Сообщения: 53 Репутация: N Группа: Кто попало

    Spritz 15 мая 2011 г. 10:48, спустя 38 минут 22 секунды

    2) Ясно. То есть в шаблон передаются безымянные данные, от которых требуется соответствие только по структуре. У меня просто немного другой подход к этой части: много приходится работать с проектом, в котором десятки mysql-таблиц, и "чтобы ничего не запоминать", основная часть полей шаблонов в точности соответствует наименованиям полей mysql-таблиц. Ну, а функции, наоборот, ничего не знают о шаблоне (просто пытаются втиснуть туда массив).
  • AlexB

    Сообщения: 4290 Репутация: N Группа: в ухо

    Spritz 15 мая 2011 г. 11:35, спустя 47 минут 17 секунд

    Я, честно говоря, не понял какая связь между количеством таблиц в БД и шаблоном. Также не понял, каким образом функции "ничего не зная о шаблоне" могут впихнуть в него данные.
    Могу сказать одно, вот это:

    $tpl = "<tr><td><b>[name]</b></td><td><b>[name]</b></td><td><b>[name]</b></td></tr>
    <tr><td>[city]</td><td>[city]</td><td>[city]</td></tr>";

    Сводит на нет, все усилия. Так как в реальной жизни, каждое <td> может включать в себя еще 100 тегов, ну и нах такой шаблон, где каждое изменение в дизайне надо делать в нескольких местах?
  • stopkran

    Сообщения: 53 Репутация: N Группа: Кто попало

    Spritz 16 мая 2011 г. 6:50, спустя 19 часов 14 минут 41 секунду

    "вот это" ($tpl = "<tr>") было для краткости (вместо $tpl = implode(file(… + путь к файлу))… + содержимое файла). В оригинале - "…вынести собственно шаблон в отдельный файл (сейчас, для простоты, в переменную) по "классической" схеме".

    У принципе, даже и "ничего не зная", можно впихнуть в шаблон массив - наугад. Но на практике - "не знает" собственно функция, которая накладывает массив на шаблон. А другие функции (готовящие данные), конечно, вынуждены знать, хотя бы отчасти. С одной стороны, это менее чёткое отделение представления от данных. С другой стороны, ведь именно такое разделение - не самоцель. Для меня больший приоритет имеет автоматизация (в частности, минимизация человеческого произвола в общении данных, движка и представления) - ради неё я готов разделение ухудшить (ну, что-то вроде денормализации в mysql). Мне очень понравилась "блочная" идея (впервые увидел в вашей теме), в ней чувствуется определённый резерв, сила. Но… вопросы пока так и остались (для меня очень принципиальные).

Пожалуйста, авторизуйтесь, чтобы написать комментарий!