好久不来CSDN,感觉有点跟不上时代的潮流了,所以飞快的看了下推荐帖子,看见了许大大的模板引擎,所以一时心痒痒也练了下手(本人还是头一次弄这东西),只实现了if、foreach的解析,当然了,其中有很多死角,而且我感觉我写的这个很怪异,希望大家帮忙梳理下,本人喜欢被拍class Template
{
    public $template_dir    = null;     // 模板目录
    public $compile_dir     = null;     // 编译目录
    public $cache_dir       = null;     // 缓存目录
    public $cache_life      = 0;        // 缓存时间
    public $force_compile   = false;    // 是否强制编译
    public $left_delimiter  = '{';      // 左边界符
    public $right_delimiter = '}';      // 右边界符    public $var_list        = array();  // 变量集合
    public $tpl_name        = null;    /**
     * 编译并显示模板
     *
     * @param string|array $var 变量名|变量数组
     * @param all 变量值
     */
    public function assign($var, $value = null)
    {
        if (is_array($var)) {
            $this->_assign_arr($var);
        } else {
            $this->_assign_var($var, $value);
        }
    }    /**
     * 编译并显示模板
     *
     * @param string $string
     */
    public function display($tpl_name)
    {
        $this->template_dir = rtrim($this->template_dir, '/');
        $tpl = $this->template_dir . '/' . $tpl_name;        if (is_file($tpl)) {
            $this->tpl_name = $tpl_name;
            $this->fetch(file_get_contents($tpl), true);
        } else {
            throw new Exception("Template \"{$tpl_name}\" does not exist");
        }
    }    /**
     * 编译字符串
     *
     * @param   string  $content
     * @param   bool    $print      是否输出(true-输出)
     * @param   bool    $print_exit 输出终止
     * @return  echo|string
     */
    public function fetch($content, $print = false, $print_exit = true)
    {
        $contents = false;
        if ($this->cacheing) {
            $contents = $this->_get_cache();
        }
        if ($contents === false) {
            $compile_file_path = $this->_compile($content);
            extract($this->var_list);            ob_start();
            require_once($compile_file_path);
            $content = ob_get_contents();
            ob_end_clean();            if ($this->cacheing) {
                $this->_set_cache($content);
            }
        } else {
            $content = $contents;
        }        if ($print) {
            echo $content;
            if ($print_exit) exit;
        } else {
            return $content;
        }
    }    /**
     * 设置缓存
     *
     * @param   string  $content
     * @return  string
     */
    private function _set_cache($content)
    {
        $cache_file = $this->tpl_name;
        $cache_file_path = $this->cache_dir . '/' . $cache_file;
        $this->_mkdir('cache_dir', '0777', true);
        @file_put_contents($cache_file_path, $content . "<!--{/**{$this->cache_life}**/}-->");
    }    /**
     * 获取缓存
     *
     * @return bool/string
     */
    private function _get_cache()
    {
        $cache_file = $this->tpl_name;
        $cache_file_path = $this->cache_dir . '/' . $cache_file;
        if (is_file($cache_file_path)) {
            $contents = file_get_contents($cache_file_path);
            $cache_life_match = preg_match('#<\!--{\/\*\*(\d+)\*\*\/}-->#', $contents, $arr);
            if (!$cache_life_match) {
                return false;
            }
            $cache_life = $arr[1];
            $filemtime = filemtime($cache_file_path);
            if ($filemtime + $cache_life > time()) {
                return file_get_contents($cache_file_path);
            } else {
                return false;
            }
        }
        return false;
    }    /**
     * 编译
     *
     * @param   string  $content
     * @return  string
     */
    private function _compile($content)
    {
        $content = $this->_match($content);
        $compile_file = md5_file($this->template_dir . '/' . $this->tpl_name) . $this->tpl_name . '.php';
        $compile_file_path = $this->compile_dir . '/' . $compile_file;        if ($this->force_compile || !is_file($compile_file_path)) {            // 删除原编译文件
            $unlink_file = '*' . $this->tpl_name . '.php';
            if (strpos(strtolower(PHP_OS), 'win') === false) {
                $cmd = "rm -f compile_dir/{$unlink_file}";
            } else {
                $cmd = "del compile_dir\\{$unlink_file}";
            }
            exec($cmd);            $this->_mkdir('compile_dir', '0777', true);
            @file_put_contents($compile_file_path, $content);
        }        return $compile_file_path;
    }    /**
     * 匹配
     *
     * @param   string  $content
     * @return  string
     */
    private function _match($content)
    {
        // if
        $replace = array(
            '#' . $this->left_delimiter . 'if[ ]+([^' . $this->right_delimiter . ']+)' . $this->right_delimiter . '#is' => '<?php if (\1) {?>',
            '#' . $this->left_delimiter . '/if' . $this->right_delimiter . '#is' => '<?php }?>',
        );
        $content = preg_replace(array_keys($replace), array_values($replace), $content);        // foreach
        $replace = array(
            '#' . $this->left_delimiter . 'foreach[ ]+from=\$([a-z_]+\w*)[ ]+item=([a-z_]+\w*)[ ]*' . $this->right_delimiter . '#is' => '<?php foreach ($\1 as $\2) {?>',
            '#' . $this->left_delimiter . '/foreach' . $this->right_delimiter . '#is' => '<?php }?>'
        );
        $content = preg_replace(array_keys($replace), array_values($replace), $content);        // variables
        $content = preg_replace_callback('#' . $this->left_delimiter . '(((?!\?>).)*(\$(\.?\w+)+)[^' . $this->right_delimiter . ']*)' . $this->right_delimiter . '#i', array($this, '_back'), $content);        return $content;
    }    private function _back($m)
    {
        $s = $m[1];
        $r = '#\$(\.?\w+)+#i';
        preg_match_all($r, $m[1], $list);
        foreach ($list[0] as $val) {
            $arr = explode('.', $val);
            $var = '';
            foreach($arr as $v) {
                if (strpos($v, '$') === false) {
                    $var .= '[\'' . $v . '\']';
                } else {
                    $var .= $v;
                }
            }
            $s = strtr($s, array($val => $var));
        }
        return '<?php echo ' . $s . ';?>';
    }    /**
     * 创建内部目录
     *
     * @param   string  $type       创建类型(compile_dir-编译目录|cache_dir-缓存目录)
     * @param   string  $mode       权限
     * @param   bool    $recursive  是否递归创建
     * @return  bool
     */
    private function _mkdir($type, $mode = '0777', $recursive = false)
    {
        $allow_type = array('compile_dir', 'cache_dir');
        if (!in_array($type, $allow_type)) {
            throw new Exception("Not allowed to create the type of \"{$type}\".");
        }        $dir = null;
        switch ($type) {
            case 'compile_dir':
                $dir = $this->compile_dir;
                break;
            case 'cache_dir':
                $dir = $this->cache_dir;
                break;
        }        if (!$dir) {
            throw new Exception("Please specify the \"{$type}\" directory.");
        }
        if (!is_dir($dir) && !mkdir($dir, $mode, $recursive)) {
            throw new Exception("\"{$dir}\" Failed to create \"{$type}\" directory");
        }
        return true;
    }    /**
     * 检测变量名是否和服
     *
     * @param   string  $var    变量名
     * @return  bool
     */
    private function _check_var($var)
    {
        $var_rule = '#^[a-z_]+\w*$#i';
        if (preg_match($var_rule, $var)) {
            return true;
        }
        return false;
    }    /**
     * 单变量赋值
     */
    private function _assign_var($var, $value)
    {
        if ($this->_check_var($var)) {
            $this->var_list[$var] = $value;
        }
    }    /**
     * 多变量赋值
     */
    private function _assign_arr($data)
    {
        foreach ($data as $var => $value) {
            if ($this->_check_var($var)) {
                $this->var_list[$var] = $value;
            }
        }
    }
}$t = new Template;$t->template_dir    = 'template';
$t->compile_dir     = 'compile_dir';
$t->cache_dir       = 'cache_dir';
$t->cacheing        = false;
$t->cache_life      = 5;
$t->force_compile   = true;
$t->left_delimiter  = '{';
$t->right_delimiter = '}';$c = array(
    'user1' => array('username' => 'zhangsan', 'age' => 20),
    'user2' => array('username' => 'thelisis', 'age' => 22)
);
$t->assign(array('a' => 123, 'b' => 'abc', 'c' => $c, 'x' => true));
$t->display('t.html');

解决方案 »

  1.   

    下面是用法,呵呵只是最简单的,可能稍微一有点怪异的就会出问题,所以求大家给出宝贵的想法和砖头
    {$c.user1.username}
    <br><hr><br>
    {$c.user1.age + $c.user2.age}<br><hr><br>
    {foreach from=$c item=user}
        {foreach from=$user item=v}
            {$v} 
        {/foreach}
        <br>
    {/foreach}
    <br><hr><br>{if $x}{$b}{/if}
    <br><hr><br>{$x ? $a : $b}
      

  2.   

    有参考吧?
    assign赋值你这样分,不用担心覆盖?
      

  3.   

    如果控制节点出现在不能分割的标记之间
    <table>
    {foreach from=$c item=user}
      <tr>
        {foreach from=$user item=v}
            <td>{$v}</td> 
        {/foreach}
      </tr>
    {/foreach}
    </table>
    就无法整体编辑了,foreach 破坏了整体感观
    当然你也可以用注释标记<!-- -->,但是模版变量部分该如何处理?
      

  4.   


    的确是这样的,我感觉我这一点上做的一团糟,想请教大大,这块我该如何改进呢,当我页面中出现了例如
    function () {
        var = "$ljsfd";
    }的时候就会出现替换混乱的问题,而我却还想用php的东西,例如{if in_array()}等等
      

  5.   

    to #8
    其实 smarty 同样存在这种毛病你的这个思路是沿袭了 php3 的 phplib 的 template 思路(目前能见到的都是这个模式,我的除外)
    如果么有突破,很难有所建树
      

  6.   

    支持楼主~~,学习。好像跟smarty没差多少啊
      

  7.   

    呵呵,大体方向差不多,只不过我这个太简陋了,距离smarty不敢想,不过我会继续完善,希望有一天可以用到我的项目中
      

  8.   

    专来挑问题
    private function _check_var($var)
        {
            $var_rule = '#^[a-z_]+\w*$#i';
            if (preg_match($var_rule, $var)) {
                return true;
            }
            return false;
        }
    这个正则不够全面,变量名可以用 _ 开头的
      

  9.   


    private function _match($content)
    这个方法:
    $replace = array(
                '#' . $this->left_delimiter . 'if[ ]+([^' . $this->right_delimiter . ']+)' . $this->right_delimiter . '#is' => '<?php if (\1) {?>',
                '#' . $this->left_delimiter . '/if' . $this->right_delimiter . '#is' => '<?php }?>',
            );
    $this->left_delimiter  如果含有正则的特殊字符就挂了,应该 preg_quote 一下吧?
      

  10.   


    public function assign($var, $value = null)
        {
            if (is_array($var)) {
                $this->_assign_arr($var);
            } else {
                $this->_assign_var($var, $value);
            }
        }
    觉得这个里面有 点累缀
    改一下:
    public function assign(var, value)
    {
        if(is_array(var)) {
            this->_assign(var) // _assign 只接受 array
        }
        elseif(is_object) {
            this->_assign(object to array) 
        }
        else {
            this->assign(array(var=>value))
        }
    }
    觉得这样可以写少你的 _assign_var  和array的方法,这个是ci里的思路
      

  11.   

    同感
    还不能说是“代码分离”。只是将 php 代码换个格式写在模板里
      

  12.   

    PHP本身就是一个模版引擎。已经够强大了
      

  13.   


    的确是这个样子,我感觉模板引擎最大的好处是缓存还有去除了N多的php标签如<?php、?>,这个一多起来就感觉乱糟糟的
      

  14.   

    关于 _match 方法,可以统一使用回调
    因为 $this->left_delimiter 和 $this->right_delimiter 都无一例外的被替换成 <?php 和 ?>$content = preg_replace_callback("#$this->left_delimiter(.+)$this->right_delimiter#U", array($this, '_back'), $content);
    只需扫描 $content 一次,而不是你现在的三次
      

  15.   

    有时间看看这个 http://www.stu80.com/download/YouYaX.pdf同样有模板 你会知道什么叫差距
      

  16.   


    这个是tp的,如果我要是按照这种模式写的话会很简单,只不过如果和弦可以运用php原生方法才是我这里的主旨,如果如同tp那样固定模式其实是很省功夫的
      

  17.   

    to #32
    你的思路与楼主的完全不一样,你是直接将数据替换进模板里
    而楼主是要将模板处理成混合编码的php程序你的需要每一运行都进行替换
    而楼主的只需处理一次,一次以后就可以直接运行生成的 php 代码了
      

  18.   

    使用模板引擎的目的在于:使程序员无需过多的考虑数据如何展现,而把精力放在业务逻辑的实现上
    至于显示逻辑如何实现,就仁者见仁、智者见智了
    smarty 为代表的是把显示逻辑推给了美工
    而我倾向于由模板引擎自动完成,辅以 php 程序员的协助
      

  19.   

    assign赋值有问题吧  会被覆盖的
      

  20.   

    assign赋值有问题吧 会被覆盖的
      

  21.   

    算上你们两位已经是好几为提出这个问题了,不过我认为这个是合理的,因为在smarty中assign的赋值方式有两种,第一种是单个变量赋值,第二种是数组方式(多变量)赋值,那么这样来看应该是单变量赋值中的变量名和多变量赋值中的数组元素的变量名(key)是同一级关系,呵呵,这是我的理解,不知道大家说的覆盖是不是这块
      

  22.   

    to #47
    要求变量名唯一,虽然无可厚非。php 不也是这样的吗?好在你没有考虑在模板里嵌入模板。不然的话,这个不可重名可是要大伤脑筋了
      

  23.   

     先Mark 对模板引擎本身的概念还基本没搞懂