在Web端,模型层为数据库的操作;视图层就是模板,也就是Web前端;Controller就是PHP对数据和请求的各种操作。模板引擎就是为了将视图层和其他层分离开来,使php代码和html代码不会混杂在一起。因为当php代码和html代码混杂在一起时,将使代码的可读性变差,并且代码后期的维护会变得很困难。
大部分的模板引擎原理都差不多,核心就是利用正则表达式解析模板,将约定好的特定的标识语句编译成php语句,然后调用时只需要include编译后的文件,这样就讲php语句和html语句分离开来了。甚至可以更进一步将php的输出输出到缓冲区,然后将模板编译成静态的html文件,这样请求时,就是直接打开静态的html文件,请求速度大大加快。
简单的自定义模板引擎就是两个类,第一个是模板类、第二个是编译类。
首先是编译类:
class CompileClass { private $template; // 待编译文件 private $content; // 需要替换的文本 private $compile_file; // 编译后的文件 private $left = '{'; // 左定界符 private $right = '}'; // 右定界符 private $include_file = array(); // 引入的文件 private $config; // 模板的配置文件 private $T_P = array(); // 需要替换的表达式 private $T_R = array(); // 替换后的字符串 public function __construct($template, $compile_file, $config) {} public function compile() { $this->c_include(); $this->c_var(); $this->c_staticFile(); file_put_contents($this->compile_file, $this->content); } // 处理include public function c_include() {} // 处理各种赋值和基本语句 public function c_var() {} // 对静态的JavaScript进行解析 public function c_staticFile() {} }
编译类的大致结构就是上面那样,编译类的工作就是根据配置的文件,将写好的模板文件按照规则解析,替换然后输出到文件中。这个文件的内容是php和html混杂的,但在使用模板引擎进行开发时并不需要在意这个文件,因为我们要编写的是模板文件,也就是html和我们自己定义的标签混合的一个文件。这样View和其他两层就分离开来了。
在这个自定义模板引擎中,我的左右定界符就是大括号,具体的解析规则就是放在__construct()中
// 需要替换的正则表达式 $this->T_P[] = "/$this->left\s*\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)\s*$this->right/"; $this->T_P[] = "/$this->left\s*(loop|foreach)\s*\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)\s*$this->right/"; $this->T_P[] = "/$this->left\s*(loop|foreach)\s*\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)\s+" . "as\s+\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)$this->right/"; $this->T_P[] = "/$this->left\s*\/(loop|foreach|if)\s*$this->right/"; $this->T_P[] = "/$this->left\s*if(.*?)\s*$this->right/"; $this->T_P[] = "/$this->left\s*(else if|elseif)(.*?)\s*$this->right/"; $this->T_P[] = "/$this->left\s*else\s*$this->right/"; $this->T_P[] = "/$this->left\s*([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)\s*$this->right/"; // 替换后的字符串 $this->T_R[] = "<?php echo \$\\1; ?>"; $this->T_R[] = "<?php foreach((array)\$\\2 as \$K=>\$V) { ?>"; $this->T_R[] = "<?php foreach((array)\$\\2 as &\$\\3) { ?>"; $this->T_R[] = "<?php } ?>"; $this->T_R[] = "<?php if(\\1) { ?>"; $this->T_R[] = "<?php } elseif(\\2) { ?>"; $this->T_R[] = "<?php } else { ?>"; $this->T_R[] = "<?php echo \$\\1; ?>";
上面的解析规则包含了基本的输出和一些常用的语法,if、foreach等。利用preg_replace函数就能对模板文件进行替换。具体情况如下
<!--模板文件--> {$data} {foreach $vars} {if $V == 1 } <input value="{V}"> {elseif $V == 2} <input value="123123"> {else } <input value="sdfsas是aa"> {/if} {/foreach} { loop $vars as $var} <input value="{var}"> { /loop } // 解析后 <?php echo $data; ?> <?php foreach((array)$vars as $K=>$V) { ?> <?php if( $V == 1) { ?> <input value="<?php echo $V; ?>"> <?php } elseif( $V == 2) { ?> <input value="123123"> <?php } else { ?> <input value="sdfsas是aa"> <?php } ?> <?php } ?> <?php foreach((array)$vars as &$var) { ?> <input value="<?php echo $var; ?>"> <?php } ?>
编译类的工作大致就是这样,剩下的include和对JavaScript的解析都和这个大同小异。
然后就是模板类
class Template { // 配置数组 private $_arrayConfig = array( 'root' => '', // 文件根目录 'suffix' => '.html', // 模板文件后缀 'template_dir' => 'templates', // 模板所在文件夹 'compile_dir' => 'templates_c', // 编译后存放的文件夹 'cache_dir' => 'cache', // 静态html存放地址 'cache_htm' => false, // 是否编译为静态html文件 'suffix_cache' => '.htm', // 设置编译文件的后缀 'cache_time' => 7200, // 自动更新间隔 'php_turn' => true, // 是否支持原生php代码 'debug' => 'false', ); private $_value = array(); private $_compileTool; // 编译器 static private $_instance = null; public $file; // 模板文件名 public $debug = array(); // 调试信息 public function __construct($array_config=array()) {} // 单步设置配置文件 public function setConfig($key, $value=null) {} // 注入单个变量 public function assign($key, $value) {} // 注入数组变量 public function assignArray($array) {} // 是否开启缓存 public function needCache() {} // 如果需要重新编译文件 public function reCache() {} // 显示模板 public function show($file) {} }