【问题标题】:PHP Looping Template Engine - From ScratchPHP 循环模板引擎 - 从零开始
【发布时间】:2011-06-28 09:41: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>

非常感谢您的宝贵时间!

非常感谢,

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

P.p.s 似乎对这个话题有很多意见,请不要投反对票,因为他们对你有不同的意见。每个人都有自己的权利!

【问题讨论】:

    标签: php parsing templates nested-loops


    【解决方案1】:

    一个简单的方法是将模板转换成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-&gt;showVariable()$this-&gt;data$this-&gt;wrap()$this-&gt;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 回显替换 &lt;

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

    基本上是为了防止任何形式的直接注入 PHP 代码,&lt;?&lt;%&lt;script language="php"&gt;

    用法是这样的:

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

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

    【讨论】:

    • eval (func_get_arg(0)); 哎哟!!
    • 实际上,我认为eval 在 PHP 中并没有那么糟糕。一些框架实际上做了很多eval
    • 你能说出一些名字吗,仅仅因为它在一个框架内并不意味着它是完美的。
    • 生成 PHP 脚本的另一个好处是可以改进它以编译成 PHP 模板,然后再包含在内,这样就可以消除 eval 的使用。
    • "include" 是 eval(file_get_contents($filename)); :)
    【解决方案2】:

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

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

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

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

    一个例子是使用 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,它将预编译源代码存储在内存中,从而跳过大部分编译/ 解释时间

    【讨论】:

    • 看起来不错,我会玩的。你说得对,我的意思是模板助手而不是模板解析器!谢谢,
    • 没问题。这种技术得到了很好的支持,Zend 等系统也采用了它。
    • 为什么要投反对票,讨厌人们无缘无故地这样做!
    • 我想我已经触及了一个具有广泛意见的主题!
    【解决方案3】:

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

    $name = 'Eric';
    $locations = array('Germany', 'Panama', 'China');
    
    include('templates/main.template.php');
    

    main.tempate.php 使用alternative php tag syntax,这对于非 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 只缓存它的混乱。
    • 这里有人为你正在尝试做的事情写了一个教程。我没有阅读足够的内容来评论代码的质量,但看起来你可以从中汲取灵感至少让你开始:codewalkers.com/c/a/Display-Tutorials/…
    • 不幸的是不包括循环,但看起来很有趣!谢谢
    • 哦,我一定完全错过了! /me 再读一遍!
    • @Ericson578 我说的是你发送的链接 xD
    【解决方案4】:

    在我开始使用 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!),但听起来你不需要大量的肌肉
    • 为什么要大写构造函数?
    • 你对我投了反对票,因为我把我的构造函数和析构函数都大写了?大声笑
    【解决方案5】:

    聪明的:) ...

    php:

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

    智能模板:

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

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

    【讨论】:

    • 注意,你可以用同样的方式分配瓷砖等。然后 {$title} 等等来显示它们。
    • “在您只推荐现有的模板系统之前,我更愿意自己创建它以获得经验,因为我们项目中的所有内容都必须是我们自己的。”
    • 这正是 Smarty 的用途,并且被专业人士广泛用于此类解决方案。没有必要重新发明轮毂!
    • 我不是想成为一名专业人士,我只是想创造一些东西来获得经验并控制它的作用。不过还是谢谢你的回答。
    【解决方案6】:

    使用Smarty

    【讨论】:

    • “在您只推荐现有的模板系统之前,我更愿意自己创建它以获得经验,因为我们项目中的所有内容都必须是我们自己的。”
    • 这不是一个小问题。最好问问人们为什么推荐 Smarty!也许他们已经拥有您想要的体验。
    • 我知道很多人推荐 Smarty,但是正如我所说,我更喜欢使用我创建并 100% 理解的东西,而不是学习一些我不知道的模板引擎的语法全面了解其工作原理。我看到的引擎与我之前通过一些努力创建的引擎非常相似
    猜你喜欢
    • 1970-01-01
    • 2021-10-18
    • 2020-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-04
    • 2015-10-12
    相关资源
    最近更新 更多