16

对于一个小组项目,我正在尝试为 PHP 创建一个模板引擎,以便那些对语言缺乏经验的人可以在他们的 HTML 中使用 {name} 之类的标签,PHP 将用数组中的预定义变量替换该标签。以及支持循环。

这远远超出了项目的预期,但由于我有使用 PHP 的经验,我认为让我保持忙碌是一个很好的挑战!

我的主要问题是,我如何做解析器的循环部分,这是实现这样一个系统的最佳方式。在您只推荐现有的模板系统之前,我更愿意自己创建它以获取经验,因为我们项目中的所有内容都必须是我们自己的。

目前,基本解析是使用 regex 和 preg_replace_callback 完成的,它检查 $data[name] 是否存在以及是否替换它。

我尝试过多种不同的循环方式,但不确定我是否在正确的轨道上!

一个例子,如果解析引擎给出的数据是:

Array
(
    [title] => The Title
    [subtitle] => Subtitle
    [footer] => Foot
    [people] => Array
        (
            [0] => Array
                (
                    [name] => Steve
                    [surname] => Johnson
                )

            [1] => Array
                (
                    [name] => James
                    [surname] => Johnson
                )

            [2] => Array
                (
                    [name] => josh
                    [surname] => Smith
                )

        )

    [page] => Home
)

它正在解析的页面类似于:

<html>
<title>{title}</title>
<body>
<h1>{subtitle}</h1>
{LOOP:people}
<b>{name}</b> {surname}<br />
{ENDLOOP:people}
<br /><br />
<i>{footer}</i>
</body>
</html>

它会产生类似于:

<html>
<title>The Title</title>
<body>
<h1>Subtitle</h1>
<b>Steve</b> Johnson<br />
<b>James</b> Johnson<br />
<b>Josh</b> Smith<br />
<br /><br />
<i>Foot</i>
</body>
</html>

非常感谢您的时间!

非常感谢,

Ps 我完全不同意,因为我正在寻找类似于已经存在的经验的东西,所以我的格式良好且易于理解的问题被否决了。

Pps 看来这个话题有很多意见,请不要投反对票,因为他们对你有不同的意见。每个人都有自己的权利!

4

6 回答 6

22

一种简单的方法是将模板转换为 PHP 并运行它。

$template = preg_replace('~\{(\w+)\}~', '<?php $this->showVariable(\'$1\'); ?>', $template);
$template = preg_replace('~\{LOOP:(\w+)\}~', '<?php foreach ($this->data[\'$1\'] as $ELEMENT): $this->wrap($ELEMENT); ?>', $template);
$template = preg_replace('~\{ENDLOOP:(\w+)\}~', '<?php $this->unwrap(); endforeach; ?>', $template);

例如,这会将模板标记转换为嵌入的 PHP 标记。

您会看到我提到了$this->showVariable()$this->data和。这就是我要实施的。$this->wrap()$this->unwrap()

showVariable函数显示变量的内容。wrapunwrap在每次迭代中调用以提供闭包。

这是我的实现:

class TemplateEngine {
    function showVariable($name) {
        if (isset($this->data[$name])) {
            echo $this->data[$name];
        } else {
            echo '{' . $name . '}';
        }
    }
    function wrap($element) {
        $this->stack[] = $this->data;
        foreach ($element as $k => $v) {
            $this->data[$k] = $v;
        }
    }
    function unwrap() {
        $this->data = array_pop($this->stack);
    }
    function run() {
        ob_start ();
        eval (func_get_arg(0));
        return ob_get_clean();
    }
    function process($template, $data) {
        $this->data = $data;
        $this->stack = array();
        $template = str_replace('<', '<?php echo \'<\'; ?>', $template);
        $template = preg_replace('~\{(\w+)\}~', '<?php $this->showVariable(\'$1\'); ?>', $template);
        $template = preg_replace('~\{LOOP:(\w+)\}~', '<?php foreach ($this->data[\'$1\'] as $ELEMENT): $this->wrap($ELEMENT); ?>', $template);
        $template = preg_replace('~\{ENDLOOP:(\w+)\}~', '<?php $this->unwrap(); endforeach; ?>', $template);
        $template = '?>' . $template;
        return $this->run($template);
    }
}

在函数中wrap()unwrap()我使用堆栈来跟踪变量的当前状态。也就是将当前数据保存到栈wrap($ELEMENT)中,然后将里面的变量添加$ELEMENT到当前数据中,然后unwrap()从栈中恢复数据。

为了额外的安全性,我添加了这个额外的位来替换<PHP 回显:

$template = str_replace('<', '<?php echo \'<\'; ?>', $template);

基本上是为了防止任何类型的直接注入 PHP 代码,无论是<?<%还是<script language="php">.

用法是这样的:

$engine = new TemplateEngine();
echo $engine->process($template, $data);

这不是最好的方法,但它是可以做到的一种方法。

于 2011-02-16T15:34:22.397 回答
6

好的,首先让我解释一下,告诉你PHP 是一个模板解析器

做你所做的就像从模板解析器创建模板解析器一样,毫无意义,坦率地说,它反复提醒我,模板解析器(如 smarty)在一项毫无意义的任务中变得如此出色。

您应该做的是创建一个模板帮助程序,而不是多余的解析器,在编程术语中,模板文件被称为视图,并且它们被赋予特定名称的原因之一是人们会知道那里与模型分开, 领域逻辑等

您应该做的是找到一种将所有视图数据封装在视图本身中的方法。

这方面的一个例子是使用 2 个类

  • 模板
  • 模板范围

模板类的功能是让域逻辑将数据设置到视图并对其进行处理。

这是一个简单的例子:

class Template
{
    private $_tpl_data = array();

    public function __set($key,$data)
    {
        $this->_tpl_data[$key] = $data;
    }

    public function display($template,$display = true)
    {
        $Scope = new TemplateScope($template,$this->_tpl_data); //Inject into the view
        if($display === true) 
        {
            $Scope->Display();
            exit;
        }
        return $Scope;
    }
}

这是您可以扩展的非常基本的东西,关于范围,这基本上是您的视图在解释器中编译的类,这将允许您访问 TemplateScope 类中的方法,但不能访问范围类之外的方法,即名字。

class TemplateScope
{
    private $__data = array();
    private $compiled;
    public function __construct($template,$data)
    {
        $this->__data = $data;
        if(file_exists($template))
        {
            ob_start();
            require_once $template;
            $this->compiled = ob_get_contents();
            ob_end_clean();
        }
    }

    public function __get($key)
    {
        return isset($this->__data[$key]) ? $this->__data[$key] : null;
    }

    public function _Display()
    {
        if($this->compiled !== null)
        {
             return $this->compiled;
        }
    }

    public function bold($string)
    {
        return sprintf("<strong>%s</strong>",$string);
    }

    public function _include($file)
    { 
        require_once $file; // :)
    }
}

这只是基本的,不起作用,但概念就在那里,这是一个用法示例:

$Template = new Template();

$Template->number = 1;
$Template->strings = "Hello World";
$Template->arrays = array(1,2,3,4)
$Template->resource = mysql_query("SELECT 1");
$Template->objects = new stdClass();
$Template->objects->depth - new stdClass();

$Template->display("index.php");

在模板中,您将使用传统的 php,如下所示:

<?php $this->_include("header.php") ?>
<ul>
    <?php foreach($this->arrays as $a): ?>
        <li><?php echo $this->bold($a) ?></li>
    <?php endforeach; ?>
</ul>

这也允许您在模板中包含仍然具有$this关键字访问权限的模板,然后包含它们自己,某种递归(但它不是)。

然后,不要以编程方式创建缓存,因为没有要缓存的内容,您应该使用memcached它将预编译的源代码存储在内存中,从而跳过大部分编译/解释时间

于 2011-02-16T14:59:31.833 回答
1

如果我不担心缓存或其他高级主题会将我推向一个成熟的模板引擎,如 smarty,我发现 PHP 本身就是一个很棒的模板引擎。只需像正常一样在脚本中设置变量,然后包含您的模板文件

$name = 'Eric';
$locations = array('Germany', 'Panama', 'China');

include('templates/main.template.php');

main.tempate.php 使用了另一种 php 标记语法,对于非 php 的人来说非常容易使用,只需告诉他们忽略包含在 php 标记中的任何内容 :)

<h2>Your name is <?php echo $name; ?></h2>
<?php if(!empty($locations)): ?>
  <ol>
    <?php foreach($locations as $location): ?>
    <li><?php echo $location; ?></li>
    <?php endforeach; ?>
  </ol>
<?php endif; ?>
<p> ... page continues ... </p>
于 2011-02-16T14:44:21.937 回答
1

在我开始使用 DOMXPath 之前,我对类似这样的事情有一个非常基本的答案。

类是这样的(不确定它是否像你想要的那样工作,但值得深思,因为它工作起来非常简单

<?php
class template{
private $template;

function __CONSTRUCT($template)
{
    //load a template
    $this->template = file_get_contents($template);
}

function __DESTRUCT()
{
    //echo it on object destruction
    echo $this->template;
}

function set($element,$data)
{
    //replace the element formatted however you like with whatever data
    $this->template = str_replace("[".$element."]",$data,$this->template);
}
}
?>

使用此类,您只需使用所需的任何模板创建对象,并使用 set 函数来放置所有数据。

创建对象后的简单循环可能可以实现您的目标。

祝你好运

于 2011-02-16T14:53:04.810 回答
-4

聪明的:) ...

php:

$smarty->assign("people",$peopleArray)

聪明的模板:

{foreach $people as $person}
<b>{$person.name}</b> {$person.surname}<br />
{/foreach}

结合其他事情要做,但这就是 smarty 本质上的样子。

于 2011-02-16T14:32:11.500 回答
-11

使用智能

于 2011-02-16T14:32:04.407 回答