使用PHP怎么实现一个模板引擎功能
创始人
2024-12-08 08:00:18
0次
 '.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'];
 }
}
_valueMap['\\1']; ?>",
  '',
  '',
  '',
  "_valueMap['\\1'] as \$k => \$v) {?>",
  '',
  ''
 ];
 /**
  * [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类如下:

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}

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...