PHP循环模板引擎 - 从头开始

16
为了一个小组项目,我正在尝试为PHP创建一个模板引擎,以便不太熟悉该语言的人可以在HTML中使用像{name}这样的标签,而PHP将用预定义的变量数组替换该标签,并支持循环。虽然这超出了项目的期望,但由于我有PHP经验,我认为这会是个很好的挑战来让我忙起来!我的主要问题是,如何处理解析器的循环部分,以及这是否是实现这种系统的最佳方法。在你推荐现有的模板系统之前,我更喜欢自己创建它,以获得经验,并且因为我们项目中的所有内容都必须是我们自己的。目前,基本解析是通过正则表达式和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>

非常感谢你的时间!

万分感谢,

另外,我完全不同意因为我想要创建类似于已有经验的东西,我的问题就被投票降低了。我的问题格式良好且易于理解。

另外,似乎有很多关于这个话题的不同观点,请不要因为某人有与您不同的观点而投票降低他们。每个人都有自己的权利!

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 echos替换<

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

基本上为了防止直接注入PHP代码,可以使用以下任何一种方式:<?<%<script language="php">

用法类似于这样:

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

这不是最好的方法,但这是其中一种实现方式。


12
执行 (func_get_arg(0)); OUCH!! - RobertPitt
3
实际上,我不认为在PHP中使用eval是那么糟糕的事情。有些框架实际上经常使用eval - Thai
1
生成 PHP 脚本的另一个好处是可以改进为编译成 PHP 模板,然后稍后包含,这将消除使用 eval 的需要。 - Thai
1
"include" 是 eval(file_get_contents($filename)); :) - nerkn
我们可以为循环添加计数吗?谢谢。 - SayJeyHi
显示剩余3条评论

6

首先,让我解释一下,PHP 是一个模板解析器

现在你所做的事情就像是从一个模板解析器中创建另一个模板解析器,这是毫无意义的,并且说实话,像 smarty 这样的模板解析器已经变得如此擅长于执行这种毫无意义的任务了,这让我感到十分沮丧。

你应该做的是创建一个模板助手,而不是一个解析器,因为它们是多余的。从编程术语上来说,模板文件被称为 视图 (view),它们被赋予特定名称的其中一个原因是人们可以知道它们与模型、域逻辑等是分开的。

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

以下是使用两个类的示例:

  • Template(模板)
  • TemplateScope(模板作用域)

模板类的功能是将数据设置为视图并对其进行处理。

下面是一个快速示例:

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,它会将预编译的源代码存储在内存中,跳过了大部分编译/解释时间。


看起来不错,我会试一下。你是对的,我的意思是模板助手而不是模板解析器!谢谢。 - Pez Cuckow
没问题。这种技术得到了广泛支持,像Zend这样的系统也将其纳入其中。 - RobertPitt
为什么要踩我啊,讨厌那些毫无理由地这样做的人!! - RobertPitt
我认为我触及了一个意见非常广泛的话题! - Pez Cuckow

1

如果我不担心缓存或其他高级主题,这将使我使用像Smarty这样的已建立的模板引擎,我发现 PHP 本身就是一个很好的模板引擎。只需在脚本中设置变量,然后包含您的模板文件即可。

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

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

main.template.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>

Smarty 只缓存自己的混杂物。 - RobertPitt
这里有个人为你所尝试的事情写了一篇教程。我没有读足够的内容来评论代码的质量,但它看起来像是你可以从中获得灵感至少让你开始的东西:http://www.codewalkers.com/c/a/Display-Tutorials/Writing-a-Template-System-in-PHP/ - Anony372
很遗憾它不包括循环,但看起来很有趣!谢谢 - Pez Cuckow
哦,我肯定完全错过了那个!/ 我再读一遍! - Pez Cuckow
看起来那就是我正在寻找的,我现在正在根据那个和你发布的内容工作。 - Pez Cuckow
显示剩余2条评论

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函数放置所有数据即可。
在创建对象后,简单的循环可能就能实现您的目标。
祝好运。

请记住,有更好的方法来做到这一点(DOMXPath!),但听起来你不需要太多的力量。 - steve
1
你为什么要将构造函数首字母大写? - RobertPitt
6
你因为我将构造函数和析构函数的首字母大写而给我投反对票?哈哈 - steve

-5

Smarty :) ...

PHP:

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

Smarty模板:

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

还有一些其他的事情要做,但这基本上就是Smarty的样子。


请注意,您可以以同样的方式分配标题等。然后使用 {$title} 等来显示它们。 - Brian
1
在你推荐一个现有的模板系统之前,我更愿意自己创建它,以便获得经验,并且因为我们项目中的所有内容都必须是我们自己的。 - Pez Cuckow
这正是Smarty的用途,被专业人士广泛用于这种类型的解决方案。没有必要重新发明轮子! - Brian
1
我并不是想成为一名专业人士,我只是想通过创造一些东西来积累经验并掌控它的功能。无论如何,还是谢谢你的回答。 - Pez Cuckow

-12

3
在你只是建议使用一个现有的模板系统之前,我更愿意自己创建一个,以便获得经验,并且因为我们项目中的所有内容都必须是我们自己的。 - Pez Cuckow
这不是一个简单的问题。最好问问为什么人们推荐Smarty!也许他们已经有你正在寻找的经验。 - powtac
1
我知道很多人推荐Smarty,但正如我所说,我更愿意使用我自己创建并且完全理解的东西,而不是学习某些我不完全了解其工作原理的模板引擎的语法。我以前见过很多人创建类似于我正在寻找的引擎,只需要付出一点努力就可以做到。 - Pez Cuckow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接