PHP - 使用自定义标签进行模板化 - 这是 eval 的合法使用吗?

发布于 2024-09-10 22:33:29 字数 2263 浏览 6 评论 0原文

概述

大约在 2009 年底,我为 PHP/HTML 编写了一个简单的模板系统,供我们的设计人员在内部使用小册子软件类型的网站。该系统的目标是允许通过由 PHP 处理的自定义标签在其他纯 HTML 中进行模板化。例如,模板化页面可能如下所示:

<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>

除了页面和内容/容器标签之外,核心中还包含一些其他标签,用于流量控制、迭代集合等内容,输出动态值等。该框架的设计使得可以很容易地添加在另一个前缀和命名空间下注册的自己的一组标签。

自定义标签到 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的任何建议。 :)

Overview

Around the end of 2009, I wrote a simple templating system for PHP/HTML to be used in-house by our designers for brochure-ware type websites. The goal of the system is to allow templating in otherwise pure HTML via custom tags that are processed by PHP. For example, a templated page might look like this:

<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>

The template itself might look something like this:

<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>

Besides the Page and Content/Container tags, there are a few other tags included in the core for stuff like flow control, iterating over a collection, outputting dynamic values, etc. The framework is designed so it's very easy to add your own set of tags registered under another prefix and namespace.

Custom Tags to PHP

How do we parse these custom tags? Since the're no guarantee that the HTML file is well-formed XML, solutions like XSLT/XPATH won't be reliable. Instead, we use a regex to look for tags with registered prefixes, and replace those with PHP code. The PHP code is a stack-based design... upon encountering an opening tag, an object representing the tag is created pushed onto the stack, and its "initialization function" (if any) runs. Whenever a registered closing tag is encountered, the most recent object is popped off the stack, and its "rendering function" runs.

So, after the framework replaces the templating tags with PHP, our example page might look something like this (in realty it's a bit uglier):

<?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(); ?>

The good, the bad, and eval

Now, how to execute our newly-generated PHP code? I can think of a few options here. The easiest is to simply eval the string, and that works well enough. However, any programmer will tell you "eval is evil, don't use it..." so the question is, is there anything more appropriate than eval that we can use here?

I've considered using a temporary or cached file, using php:// output streams, etc, but as far as I can see these don't offer any real advantage over eval. Caching could speed things up, but in practice all the sites we have on this thing are already blazingly fast, so I see no need to make speed optimizations at this point.

Questions

For each of the things on this list: is it a good idea? Can you think of a better alternative?

  • the whole idea in general (custom tags for html / php)
  • converting tags to php code instead of processing directly
  • the stack-based approach
  • the use of eval (or similar)

Thanks for reading and TIA for any advice. :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

樱花落人离去 2024-09-17 22:33:29

让我提倡一种不同的方法。不要动态生成 PHP 代码,然后尝试找出如何安全地执行它,而是在遇到标签时直接执行它。您可以一次处理整个 HTML 块,并在遇到每个标记时立即处理

编写一个查找标签的循环。其基本结构如下所示:

  1. 查找自定义标签,您可以在位置 n 处找到该标签。
  2. 位置 n 之前的所有内容都必须是简单的 HTML,因此要么将其保存以进行处理,要么立即输出(如果您的 $tags 堆栈上没有标签,您可能不会需要将其保存在任何地方)。
  3. 为标签执行适当的代码。无需生成调用 $tags->push 的代码,只需直接调用 $tags->push 即可。
  4. 返回到步骤 1。

使用这种方法,您只需直接调用 PHP 函数,而无需即时构建 PHP 代码,然后再执行它。对eval的需求已经消失。

步骤#3 基本上有两种情况。当您遇到开始标签时,您将立即推送。然后,当您点击结束标记时,您可以执行 pop 操作,然后以适当的方式处理该标记,现在您已经处理了自定义元素的全部内容。

以这种方式处理 HTML 也更加高效。对长 HTML 字符串进行多次搜索和替换效率很低,因为每次搜索和每次替换在字符串长度上都是 O(n)。这意味着您一遍又一遍地重复扫描字符串,每次进行替换时,您都必须生成长度相似的全新字符串。如果您有 20KB 的 HTML,则每次替换都需要搜索该 20KB,然后创建一个新的 20KB 字符串。

Let me advocate a different approach. Instead of generating PHP code dynamically and then trying to figure out how to execute it safely, execute it directly as you encounter the tags. You can process the entire block of HTML in one pass and handle each tag as you encounter it immediately.

Write a loop that looks for tags. Its basic structure will look like this:

  1. Look for a custom tag, which you find at position n.
  2. Everything before position n must be simple HTML, so either save it off for processing or output it immediately (if you have no tags on your $tags stack you probably don't need to save it anywhere).
  3. Execute the appropriate code for the tag. Instead of generating code that calls $tags->push, just call $tags->push directly.
  4. Go back to step 1.

With this approach you only call PHP functions directly, you never build PHP code on the fly and then execute it later. The need for eval is gone.

You'll basically have two cases for step #3. When you encounter an opening tag you will do an immediate push. Then later when you hit the closing tag you can do a pop and then handle the tag in the appropriate manner, now that you've processed the entire contents of the custom element.

It is also more efficient to process the HTML this way. Doing multiple search and replaces on a long HTML string is inefficient as each search and each replacement is O(n) on the length of the string. Meaning you're repeatedly scanning the string over and over, and each time you do a replacement you have to generate whole new strings of similar length. If you have 20KB of HTML then each replacement involves searching through that 20KB and then creating a new 20KB string afterwards.

緦唸λ蓇 2024-09-17 22:33:29

我发布了另一个答案,因为它与第一个答案完全不同,这也可能很有价值。

本质上,这个问题是问如何使用正则表达式执行 PHP 代码。这看起来可能不是那么明显,但这就是评估想要完成的目标。

话虽如此,您可以使用 PHP 的 preg_replace_callback 函数在匹配时执行一段代码,而不是先执行 preg_replace 然后执行 eval。

请参阅此处了解该函数的工作原理:http://us. php.net/manual/en/function.preg-replace-callback.php

I posted another answer because it's radically different from the first, which might also be valuable.

Essentially, this question is asking how to execute PHP code with regex. It may not seem that obvious, but this is what the eval is intending to accomplish.

With that said, instead of doing a pass of preg_replace and then doing an eval, you could just use PHP's preg_replace_callback function to execute a piece of code when matched.

See here for how the function works: http://us.php.net/manual/en/function.preg-replace-callback.php

鹿! 2024-09-17 22:33:29

是的,除了 eval,你可以做 zend 和其他主要框架所做的事情,使用输出缓冲:

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

Yes, instead of eval, you can do what zend and other major frameworks do, use output buffering:

ob_start();
include($template_file); //has some HTML and output generating PHP
$result = ob_get_contents();
ob_end_clean();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文