PHP实现简单的模板引擎功能示例
本文实例讲述了PHP实现简单的模板引擎功能。分享给大家供大家参考,具体如下:
phpweb开发中广泛采取mvc的设计模式,controller传递给view层的数据,必须通过模板引擎才能解析出来。实现一个简单的仅仅包含if,foreach标签,解析$foo变量的模板引擎。
编写template模板类和compiler编译类。代码如下:
'.php',//文件后缀名 'templateDir'=>'../views/',//模板所在文件夹 'compileDir'=>'../runtime/cache/views/',//编译后存放的目录 'suffixCompile'=>'.php',//编译后文件后缀 'isReCacheHtml'=>false,//是否需要重新编译成静态html文件 'isSupportPhp'=>true,//是否支持php的语法 'cacheTime'=>0,//缓存时间,单位秒 ]; private$_file;//带编译模板文件 private$_valueMap=[];//键值对 private$_compiler;//编译器 publicfunction__construct($compiler,$config=[]) { $this->_compiler=$compiler; $this->_config=array_merge($this->_config,$config); } /** *[assign存储控制器分配的键值] *@param[type]$values[键值对集合] *@return[type][description] */ publicfunctionassign($values) { if(is_array($values)){ $this->_valueMap=$values; }else{ thrownew\Exception('控制器分配给视图的值必须为数组!'); } return$this; } /** *[show展现视图] *@param[type]$file[带编译缓存的文件] *@return[type][description] */ publicfunctionshow($file) { $this->_file=$file; if(!is_file($this->path())){ thrownew\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[编译后的文件] *@returnboolean[description] */ privatefunctionisRecompile($compileFile) { returntime()-filemtime($compileFile)>$this->_config['cacheTime']; } /** *[isReCacheHtml是否需要重新缓存静态html文件] *@returnboolean[description] */ privatefunctionisReCacheHtml() { return$this->_config['isReCacheHtml']; } /** *[isSupportPhp是否支持php语法] *@returnboolean[description] */ privatefunctionisSupportPhp() { return$this->_config['isSupportPhp']; } /** *[path获得模板文件路径] *@return[type][description] */ privatefunctionpath() { return$this->_config['templateDir'].$this->_file.$this->_config['suffix']; } }
_valueMap['\\1'];?>", '', '', '', "_valueMap['\\1']as\$k=>\$v){?>", '', '' ]; /** *[compile编译模板文件] *@param[type]$source[模板文件] *@param[type]$destFile[编译后文件] *@param[type]$values[键值对] *@return[type][description] */ publicfunctioncompile($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[键值对] *@paramarray$templateConfig[编译配置] *@return[type][description] */ protectedfunctionrender($file,$values,$templateConfig=[]) { $di=Container::getInstance(); //依赖注入实例化对象 $di->template=function()use($di,$templateConfig){ $di->compiler='foo\base\Compiler'; $compiler=$di->compiler; returnnew\foo\base\Template($compiler,$templateConfig); }; $di->template->assign($values)->show($file); }
Container类如下:
s[$k]=$c; } publicfunction__get($k) { return$this->build($this->s[$k]); } /** *自动绑定(Autowiring)自动解析(AutomaticResolution) * *@paramstring$className *@returnobject *@throwsException */ publicfunctionbuild($className) { //如果是闭包函数(closures) if($classNameinstanceof\Closure){ //执行闭包函数 return$className($this); } if(isset(self::$instances[$className])){ returnself::$instances[$className]; } /**@varReflectionClass$reflector*/ $reflector=new\ReflectionClass($className); //检查类是否可实例化,排除抽象类abstract和对象接口interface if(!$reflector->isInstantiable()){ thrownew\Exception($reflector.':不能实例化该类!'); } /**@varReflectionMethod$constructor获取类的构造函数*/ $constructor=$reflector->getConstructor(); //若无构造函数,直接实例化并返回 if(is_null($constructor)){ returnnew$className; } //取构造函数参数,通过ReflectionParameter数组返回参数列表 $parameters=$constructor->getParameters(); //递归解析构造函数的参数 $dependencies=$this->getDependencies($parameters); //创建一个类的新实例,给出的参数将传递到类的构造函数。 $obj=$reflector->newInstanceArgs($dependencies); self::$instances[$className]=$obj; return$obj; } /** *@paramarray$parameters *@returnarray *@throwsException */ publicfunctiongetDependencies($parameters) { $dependencies=[]; /**@varReflectionParameter$parameter*/ foreach($parametersas$parameter){ /**@varReflectionClass$dependency*/ $dependency=$parameter->getClass(); if(is_null($dependency)){ //是变量,有默认值则设置默认值 $dependencies[]=$this->resolveNonClass($parameter); }else{ //是一个类,递归解析 $dependencies[]=$this->build($dependency->name); } } return$dependencies; } /** *@paramReflectionParameter$parameter *@returnmixed *@throwsException */ publicfunctionresolveNonClass($parameter) { //有默认值则返回默认值 if($parameter->isDefaultValueAvailable()){ return$parameter->getDefaultValue(); } thrownew\Exception('Ihavenoideawhattodohere.'); } }
要想以键值对的方式访问对象的属性必须实现ArrayAccess接口的四个方法,
Object基类代码如下:
publicfunctionoffsetExists($offset) { returnarray_key_exists($offset,get_object_vars($this)); } publicfunctionoffsetUnset($key) { if(array_key_exists($key,get_object_vars($this))){ unset($this->{$key}); } } publicfunctionoffsetSet($offset,$value) { $this->{$offset}=$value; } publicfunctionoffsetGet($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}已成年
{elseif$age<10}小毛孩
{/if} {foreach$friends}{^v}
{/foreach}