自己写php模板引擎,如何用php编写一个简单的模板引擎(附代码)

php web开发中广泛采取mvc的设计模式,controller传递给view层的数据,必须通过模板引擎才能解析出来。实现一个简单的仅仅包含if,foreach标签,解析$foo变量的模板引擎。

编写template模板类和compiler编译类。代码如下:<?phpnamespace foo\base;use foo\base\Object;use foo\base\Compiler;/**

*

*/class Template extends Object{

private $_config = [ 'suffix' => '.php',//文件后缀名

'templateDir' => '../views/',//模板所在文件夹

'compileDir' => '../runtime/cache/views/',//编译后存放的目录

'suffixCompile' => '.php',//编译后文件后缀

'isReCacheHtml' => false,//是否需要重新编译成静态html文件

'isSupportPhp' => true,//是否支持php的语法

'cacheTime' => 0,//缓存时间,单位秒

]; private $_file;//带编译模板文件

private $_valueMap = [];//键值对

private $_compiler;//编译器

public function __construct($compiler, $config = [])

{

$this->_compiler = $compiler; $this->_config = array_merge($this->_config, $config);

} /**

* [assign 存储控制器分配的键值]

* @param [type] $values [键值对集合]

* @return [type] [description]

*/

public function assign($values)

{

if (is_array($values)) { $this->_valueMap = $values;

} else { throw new \Exception('控制器分配给视图的值必须为数组!');

} return $this;

} /**

* [show 展现视图]

* @param [type] $file [带编译缓存的文件]

* @return [type] [description]

*/

public function show($file)

{

$this->_file = $file; if (!is_file($this->path())) { throw new \Exception('模板文件'. $file . '不存在!');

} $compileFile = $this->_config['compileDir'] . md5($file) . $this->_config['suffixCompile']; $cacheFile = $this->_config['compileDir'] . md5($file) . '.html'; //编译后文件不存在或者缓存时间已到期,重新编译,重新生成html静态缓存

if (!is_file($compileFile) || $this->isRecompile($compileFile)) { $this->_compiler->compile($this->path(), $compileFile, $this->_valueMap); $this->_config['isReCacheHtml'] = true; if ($this->isSupportPhp()) {

extract($this->_valueMap, EXTR_OVERWRITE);//从数组中将变量导入到当前的符号表

}

} if ($this->isReCacheHtml()) {

ob_start();

ob_clean(); include($compileFile);

file_put_contents($cacheFile, ob_get_contents());

ob_end_flush();

} else {

readfile($cacheFile);

}

} /**

* [isRecompile 根据缓存时间判断是否需要重新编译]

* @param [type] $compileFile [编译后的文件]

* @return boolean [description]

*/

private function isRecompile($compileFile)

{

return time() - filemtime($compileFile) > $this->_config['cacheTime'];

} /**

* [isReCacheHtml 是否需要重新缓存静态html文件]

* @return boolean [description]

*/

private function isReCacheHtml()

{

return $this->_config['isReCacheHtml'];

} /**

* [isSupportPhp 是否支持php语法]

* @return boolean [description]

*/

private function isSupportPhp()

{

return $this->_config['isSupportPhp'];

} /**

* [path 获得模板文件路径]

* @return [type] [description]

*/

private function path()

{

return $this->_config['templateDir'] . $this->_file . $this->_config['suffix'];

}

}<?phpnamespace foo\base;use foo\base\Object;/**

*

*/class Compiler extends Object{

private $_content; private $_valueMap = []; private $_patten = [ '#\{\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}#', '#\{if (.*?)\}#', '#\{(else if|elseif) (.*?)\}#', '#\{else\}#', '#\{foreach \\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)}#', '#\{\/(foreach|if)}#', '#\{\\^(k|v)\}#',

]; private $_translation = [ "<?php echo \$this->_valueMap['\\1']; ?>", '<?php if (\\1) {?>', '<?php } else if (\\2) {?>', '<?php }else {?>', "<?php foreach (\$this->_valueMap['\\1'] as \$k => \$v) {?>", '<?php }?>', '<?php echo \$\\1?>'

]; /**

* [compile 编译模板文件]

* @param [type] $source [模板文件]

* @param [type] $destFile [编译后文件]

* @param [type] $values [键值对]

* @return [type] [description]

*/

public function compile($source, $destFile, $values)

{

$this->_content = file_get_contents($source); $this->_valueMap = $values; if (strpos($this->_content, '{$') !== false) { $this->_content = preg_replace($this->_patten, $this->_translation, $this->_content);

}

file_put_contents($destFile, $this->_content);

}

}

我们的控制器就可以调用template中的assign方法进行赋值,show方法进行模板编译了。/**

* [render 渲染模板文件]

* @param [type] $file [待编译的文件]

* @param [type] $values [键值对]

* @param array $templateConfig [编译配置]

* @return [type] [description]

*/

protected function render($file, $values, $templateConfig = [])

{

$di = Container::getInstance(); //依赖注入实例化对象

$di->template = function () use ($di, $templateConfig) {

$di->compiler = 'foo\base\Compiler'; $compiler = $di->compiler; return new \foo\base\Template($compiler, $templateConfig);

}; $di->template->assign($values)->show($file);

}

Container类如下:<?phpnamespace foo\base;use foo\base\Object;class Container extends Object{

private static $_instance; private $s = []; public static $instances = []; public static function getInstance()

{

if (!(self::$_instance instanceof self)) { self::$_instance = new self();

} return self::$_instance;

} private function __construct(){} private function __clone(){} public function __set($k, $c)

{

$this->s[$k] = $c;

} public function __get($k)

{

return $this->build($this->s[$k]);

} /**

* 自动绑定(Autowiring)自动解析(Automatic Resolution)

*

* @param string $className

* @return object

* @throws Exception

*/

public function build($className)

{

// 如果是闭包函数(closures)

if ($className instanceof \Closure) { // 执行闭包函数

return $className($this);

} if (isset(self::$instances[$className])) { return self::$instances[$className];

} /** @var ReflectionClass $reflector */

$reflector = new \ReflectionClass($className); // 检查类是否可实例化, 排除抽象类abstract和对象接口interface

if (!$reflector->isInstantiable()) { throw new \Exception($reflector . ': 不能实例化该类!');

} /** @var ReflectionMethod $constructor 获取类的构造函数 */

$constructor = $reflector->getConstructor(); // 若无构造函数,直接实例化并返回

if (is_null($constructor)) { return new $className;

} // 取构造函数参数,通过 ReflectionParameter 数组返回参数列表

$parameters = $constructor->getParameters(); // 递归解析构造函数的参数

$dependencies = $this->getDependencies($parameters); // 创建一个类的新实例,给出的参数将传递到类的构造函数。

$obj = $reflector->newInstanceArgs($dependencies); self::$instances[$className] = $obj; return $obj;

} /**

* @param array $parameters

* @return array

* @throws Exception

*/

public function getDependencies($parameters)

{

$dependencies = []; /** @var ReflectionParameter $parameter */

foreach ($parameters as $parameter) { /** @var ReflectionClass $dependency */

$dependency = $parameter->getClass(); if (is_null($dependency)) { // 是变量,有默认值则设置默认值

$dependencies[] = $this->resolveNonClass($parameter);

} else { // 是一个类,递归解析

$dependencies[] = $this->build($dependency->name);

}

} return $dependencies;

} /**

* @param ReflectionParameter $parameter

* @return mixed

* @throws Exception

*/

public function resolveNonClass($parameter)

{

// 有默认值则返回默认值

if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue();

} throw new \Exception('I have no idea what to do here.');

}

}

要想以键值对的方式访问对象的属性必须实现ArrayAccess接口的四个方法,

Object基类代码如下:public function offsetExists($offset)

{

return array_key_exists($offset, get_object_vars($this));

} public function offsetUnset($key)

{

if (array_key_exists($key, get_object_vars($this)) ) { unset($this->{$key});

}

} public function offsetSet($offset, $value)

{

$this->{$offset} = $value;

} public function offsetGet($var)

{

return $this->$var;

}

在某一控制器中就可以调用父类Controller的render方法啦$this->render('test\index', ['name' => 'tom', 'age' => 20, 'friends' => ['jack', 'rose']], ['cacheTime' => 10]);

编写视图模板文件‘test\index’:

Document

展示模板文件视图

{$name}

{$age}

{if $age > 18}

已成年

{else if $age < 10}

小毛孩

{/if}

{foreach $friends}

{^v}

{/foreach}

至此,一个简单的模板编译引擎就写好了。

本文转载于:CSDN博客,如有侵犯,请联系a@php.cn删除

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值