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}