PHP - шаблоны с пользовательскими тегами - это законное использование eval? - PullRequest
5 голосов
/ 24 июля 2010

Обзор

Примерно в конце 2009 года я написал простую систему шаблонов для PHP / HTML, которая будет использоваться нашими дизайнерами для веб-сайтов типа брошюр.Цель системы - разрешить использование шаблонов в чистом HTML-коде с помощью пользовательских тегов, которые обрабатываются PHP.Например, шаблонная страница может выглядеть так:

<tt:Page template="templates/main.html">
  <tt:Content name="leftColumn">
    <p> blah blah </p>
    ...
  </tt:Content>
  <tt:Content name="rightColumn">
    <p> blah blah </p>
    ...
  </tt:Content>
</tt:Page>

Сам шаблон может выглядеть примерно так:

<html>
  <head>...</head>
  <body>
    <div style="float:left; width:45%">
      <tt:Container name="leftColumn" />
    </div>
    <div style="width:45%">
      <tt:Container name="rightColumn" />
    </div>
  </body>
</html>

Помимо тегов Page и Content / Container, естьнесколько других тегов, включенных в ядро ​​для таких вещей, как управление потоком, итерации по коллекции, вывод динамических значений и т. д. Каркас спроектирован так, что очень легко добавить свой собственный набор тегов, зарегистрированных под другим префиксом и пространством имен.

Пользовательские теги в PHP

Как мы можем проанализировать эти пользовательские теги?Поскольку нет гарантии, что HTML-файл представляет собой правильно сформированный XML, такие решения, как XSLT / XPATH, не будут надежными.Вместо этого мы используем регулярное выражение для поиска тегов с зарегистрированными префиксами и заменяем их на код PHP.Код PHP представляет собой конструкцию на основе стека ... при обнаружении открывающего тега объект, представляющий тег, создается помещенным в стек, и запускается его "функция инициализации" (если есть).Всякий раз, когда встречается зарегистрированный закрывающий тег, самый последний объект извлекается из стека и запускается его «функция рендеринга».

Итак, после того, как каркас заменит шаблонные теги на PHP, наша примерная страница может выглядеть как-товот так (на самом деле это немного уродливее):

<?php $tags->push('tt', 'Page', array('template'=>'templates/main.html')); ?>
  <?php $tags->push('tt', 'Content', array('name'=>'leftColumn')); ?>
    <p> blah blah </p>
    ...
  <?php $tags->pop(); ?>
  <?php $tags->push('tt', 'Content', array('name'=>'rightColumn')); ?>
    <p> blah blah </p>
    ...
  <?php $tags->pop(); ?>
<?php $tags->pop(); ?>

Хорошо, плохо, и eval

Теперь, как выполнить наши новыесгенерированный код PHP?Я могу придумать несколько вариантов здесь.Самый простой - просто eval строка, и это работает достаточно хорошо.Тем не менее, любой программист скажет вам: «eval - это зло, не используйте его ...», поэтому вопрос в том, есть ли что-нибудь более подходящее, чем eval, которое мы можем использовать здесь?

Я рассмотрел использование временного или кэшированного файла, использование php:// выходных потоков и т. Д., Но, насколько я понимаю, они не дают какого-либо реального преимущества перед eval.Кэширование может ускорить процесс, но на практике все сайты, которые у нас есть, уже работают невероятно быстро, поэтому я не вижу необходимости в оптимизации скорости на этом этапе.

Вопросы

Для каждой вещи из этого списка: это хорошая идея?Можете ли вы придумать лучшую альтернативу?

  • вся идея в целом (пользовательские теги для html / php)
  • преобразование тегов в код php вместо прямой обработки
  • основанный на стеке подход
  • использование eval (или аналогичного)

Спасибо за чтение и TIA за любые советы.:)

Ответы [ 3 ]

2 голосов
/ 24 июля 2010

Я отправил другой ответ, потому что он радикально отличается от первого, что также может быть полезным.

По сути, этот вопрос состоит в том, как задать код PHP с помощью регулярных выражений.Это может показаться не столь очевидным, но это то, что Eval намерен достичь.

С учетом сказанного, вместо того, чтобы выполнить передачу preg_replace и затем выполнить eval, вы можете просто использовать PHP-функцию preg_replace_callback для выполнения фрагмента кода при совпадении.

См. Здесь, как работает функция: http://us.php.net/manual/en/function.preg-replace-callback.php

2 голосов
/ 24 июля 2010

Позвольте мне отстаивать другой подход. Вместо того, чтобы генерировать PHP-код динамически, а затем пытаться выяснить, как выполнить его безопасно, выполняйте его непосредственно при обнаружении тегов. Вы можете обрабатывать весь блок HTML за один проход и обрабатывать каждый тег по мере его появления немедленно.

Написать цикл, который ищет теги. Его основная структура будет выглядеть так:

  1. Найдите пользовательский тег, который вы найдете в позиции n .
  2. Все до позиции n должно быть простым HTML, поэтому либо сохраните его для обработки, либо сразу же выведите (если у вас нет тегов в стеке $tags, вам, вероятно, не нужно его сохранять в любом месте).
  3. Выполните соответствующий код для тега. Вместо генерации кода, который вызывает $tags->push, просто вызовите $tags->push напрямую.
  4. Вернитесь к шагу 1.

При таком подходе вы только вызываете функции PHP напрямую, вы никогда не создаете код PHP на лету и затем выполняете его позже. Потребность в eval исчезла.

У вас будет два случая для шага № 3. Когда вы сталкиваетесь с открывающим тегом, вы сразу же делаете push. Затем, когда вы нажмете на закрывающий тег, вы можете выполнить pop, а затем обработать тег соответствующим образом, теперь, когда вы обработали все содержимое пользовательского элемента.

Также более эффективно обрабатывать HTML таким образом. Выполнение множественного поиска и замены в длинной строке HTML неэффективно, поскольку каждый поиск и каждая замена составляют O ( n ) по длине строки. Это означает, что вы постоянно сканируете строку снова и снова, и каждый раз, когда вы делаете замену, вы должны генерировать новые строки одинаковой длины. Если у вас есть 20 КБ HTML, то каждая замена включает в себя поиск по этим 20 КБ, а затем создание новой строки размером 20 КБ.

0 голосов
/ 24 июля 2010

Да, вместо eval вы можете делать то, что делают Zend и другие основные фреймворки, используя буферизацию вывода:

ob_start();
include($template_file); //has some HTML and output generating PHP
$result = ob_get_contents();
ob_end_clean();
...