共计 7538 个字符,预计需要花费 19 分钟才能阅读完成。
前言
前面做一个店铺装修的功能,自己尝试做一个微型的模板引擎来实现输出显示,然后查了资料,记录一下重点部分
要点
解析好的模板文件必须写入到某个文件里面,然后 require 那个文件
步骤
1. ob_start(); 开始截取输出
2. require 编译后的模板文件
3. $result = ob_get_clean(); 获取并清除输出的内容
代码
<?php
namespace weixinend\libs;
class MiniViewTemplate
{
/**
* 模板文件路径
* @var string
*/
protected $viewPath;
/**
* 编译文件保存路径
* @var string
*/
protected $cachePath;
/**
* 模板文件后缀名
* @var string
*/
protected $viewFileSuffix = ['.html','.php'];
/**
* 保存渲染的变量
* @var array
*/
protected $vars = [];
/**
* 当前模板真实路径
* @var string
*/
protected $viewFileRealPath;
/**
* 当前编译文件路径
* @var string
*/
protected $compileFilePath;
/**
* 保存yield section对应关系
* @var array
*/
protected $sections = [];
/**
* section堆
* @var array
*/
protected $sectionStack = [];
/**
* 记录编译次数
* @var int
*/
protected $compileCount = 0;
/**
* 替换规则
* @var array
*/
protected $rules = [
"/{{--\s*(.+?)\s*--}}/" => '<?php /* ${1} */ ?>',
"/{{\s*(.+?)\s*}}/" => '<?php echo $this->e(${1}); ?>',
"/{!!\s*(.+?)\s*!!}/" => '<?php echo ${1}; ?>',
"/@if\((.+?)\)/" => '<?php if(${1}): ?>',
"/@elseif\((.+?)\)/" => '<?php elseif(${1}): ?>',
"/@else/" => '<?php else: ?>',
"/@endif/" => '<?php endif; ?>',
"/@foreach\((.+?)\)/" => '<?php foreach(${1}): ?>',
"/@endforeach/" => '<?php endforeach; ?>',
"/@while\((.+?)\)/" => '<?php while(${1}): ?>',
"/@endwhile/" => '<?php endwhile; ?>',
"/@for\((.+?)\)/" => '<?php for(${1}): ?>',
"/@endfor/" => '<?php endfor; ?>',
];
public function __construct()
{
}
/**
* View 构造函数
* @param $viewPath
* @param $cachePath
*/
/*
public function __construct($viewPath, $cachePath)
{
$this->viewPath = $viewPath;
$this->cachePath = $cachePath;
}
*/
/**
* 使用模板
* @param $file
* @param array $vars
* @return $this
*/
/*
public function make($file, $vars = [])
{
//保存分配变量
$this->vars = array_merge($this->vars, $vars);
//获得文件真实路径
$this->viewFileRealPath = $this->getViewFileRealPath($file);
//获得编译文件路径
$this->compileFilePath = $this->getCompileFilePath($this->viewFileRealPath);
return $this;
}
*/
/**
* 渲染
* @return string
*/
/*
public function render()
{
//进行编译
$this->compile();
//解析变量
extract($this->vars);
if ($this->compileCount == 0) {
ob_start();
}
$this->compileCount++;
require $this->compileFilePath;
$this->compileCount--;
if ($this->compileCount == 0) {
$result = ob_get_clean();
}
return isset($result) ? trim($result) : '';
}
*/
/**
* 提供模板内容,进行渲染,并返回渲染接口,需要临时目录
* @param $buffer
* @param string $runtimePath 临时目录地址,默认为 项目 / runtime / tmp_cache
* @return string
*/
public function renderContent($buffer,$runtimePath = '') {
$buffer = $this->compileExtends($buffer);
$buffer = $this->compileInclude($buffer);
$buffer = $this->compileSection($buffer);
$buffer = $this->compileYield($buffer);
$buffer = $this->compileEcho($buffer);
if (empty($runtimePath)) {
$runtimePath = \Yii::$app->getRuntimePath() . DIRECTORY_SEPARATOR . 'tmp_cache' . DIRECTORY_SEPARATOR;
}
// 使用内容 MD5 做文件名称,如果内容变更的话,就生成新的文件
$fileName = md5($buffer) . '.php';
$this->compileFilePath = $runtimePath . $fileName;
if (!file_exists($this->compileFilePath)) {
// 写入到临时文件
$this->putBufferToCompileFile($buffer);
}
//解析变量
extract($this->vars);
ob_start();
require $this->compileFilePath;
$result = ob_get_clean();
return isset($result) ? trim($result) : '';
}
/**
* 判断编译文件是否过期
* @return bool
*/
protected function isExpired()
{
if (! is_file($this->compileFilePath)) {
return true;
}
$cacheLastModify = filemtime($this->compileFilePath);
$viewLastModify = filemtime($this->viewFileRealPath);
if ($cacheLastModify <= $viewLastModify) {
return true;
}
return false;
}
/**
* 编译模板
*/
protected function compile()
{
if($this->isExpired()) {
$buffer = file_get_contents($this->viewFileRealPath);
$buffer = $this->compileExtends($buffer);
$buffer = $this->compileInclude($buffer);
$buffer = $this->compileSection($buffer);
$buffer = $this->compileYield($buffer);
$buffer = $this->compileEcho($buffer);
$this->putBufferToCompileFile($buffer);
}
}
/**
* 编译extends
* @param $buffer
* @return string
*/
protected function compileExtends($buffer)
{
$pattern = "/@extends\(['|\"](.+?)['|\"]\)/";
preg_match_all($pattern, $buffer, $matches);
if(!empty($matches[0])) {
foreach ($matches[1] as $value) {
$buffer .= PHP_EOL .'<?php $this->make("'. $value .'",$this->vars)->render();?>';
}
}
$buffer = preg_replace($pattern, '', $buffer);
return trim($buffer);
}
/**
* 编译include
* @param $buffer
* @return string
*/
protected function compileInclude($buffer)
{
$pattern = "/@include\(['|\"](.+?)['|\"]\)/";
$buffer = preg_replace_callback($pattern, function($matches){
return '<?php $this->make("'. $matches[1] .'",$this->vars)->render(); ?>';
}, $buffer);
return trim($buffer);
}
/**
* 编译section
* @param $buffer
* @return string
*/
protected function compileSection($buffer)
{
$pattern = "/@section\(['|\"](.+?)['|\"]\)([\s\S]+?)@(stop|show)/";
$buffer = preg_replace_callback($pattern, function($matches){
$str = '<?php $this->startSection("'. $matches[1] .'");?>' . $matches[2];
if ($matches[3] == 'show') {
$str .= '<?php $this->stopSection(true); ?>';
} else {
$str .= '<?php $this->stopSection(); ?>';
}
return $str;
}, $buffer);
return trim($buffer);
}
/**
* 开始section
* @param $name
*/
protected function startSection($name)
{
if(ob_start()) {
$this->sectionStack[] = $name;
}
}
/**
* 结束section
* @param bool $show
* @throws Exception
*/
protected function stopSection($show = false)
{
if (empty($this->sectionStack)) {
throw new Exception("Bad stopSection");
}
$buffer = ob_get_clean();
$last = array_pop($this->sectionStack);
if(!isset($this->sections[$last])) {
if ($show) {
echo $buffer;
} else {
$this->sections[$last] = $buffer;
}
} else {
echo $this->sections[$last];
}
}
/**
* 编译yield
* @param $buffer
* @return string
*/
protected function compileYield($buffer)
{
$pattern = "/@yield\(['|\"](.+?)['|\"]\)/";
$buffer = preg_replace_callback($pattern, function($matches) {
if (isset($this->sections[$matches[1]])) {
return $this->sections[$matches[1]];
} else {
return '';
}
}, $buffer);
return trim($buffer);
}
/**
* 编译输出
* @param $buffer
* @return mixed
*/
protected function compileEcho($buffer)
{
$buffer = preg_replace(array_keys($this->rules), array_values($this->rules), $buffer);
return trim($buffer);
}
/**
* 将缓冲内容放到编译文件中
* @param $buffer
* @return int
*/
protected function putBufferToCompileFile($buffer)
{
return file_put_contents($this->compileFilePath, $buffer, LOCK_EX);
}
/**
* 分配渲染变量
* @param $key
* @param $value
* @return $this
*/
public function with($key, $value)
{
$this->vars[$key] = $value;
return $this;
}
public function setData($vars)
{
$this->vars = array_merge($this->vars, $vars);
return $this;
}
/**
* 获得模板文件完整地址
* @param $fileName
* @return string
* @throws Exception
*/
private function getViewFileRealPath($fileName)
{
$fileName = str_replace(".", DIRECTORY_SEPARATOR, $fileName);
foreach ($this->viewFileSuffix as $suffix) {
$viewRealPath = $this->viewPath . DIRECTORY_SEPARATOR . $fileName . $suffix;
if (is_file($viewRealPath)) {
return $viewRealPath;
}
}
throw new Exception($fileName . " not found");
}
/**
* 安全输出变量
* @param $value
* @return string
*/
protected function e($value)
{
return htmlentities($value, ENT_QUOTES, 'UTF-8', false);
}
/**
* 获得编译文件的完整名
* @return string
*/
private function getCompileFilePath($viewFileRealPath)
{
return $this->cachePath . DIRECTORY_SEPARATOR . md5($viewFileRealPath);
}
/**
* 添加模板后缀名
* @param $suffix
*/
public function addViewFileSuffix($suffix)
{
array_unshift($this->viewFileSuffix, $suffix);
}
/**
* 添加自定义规则
* @param $rule
* @param $replace
*/
public function addRule($rule, $replace)
{
$this->rules[$rule] = $replace;
}
/**
* 驼峰转蛇形
* @param $str
* @return string
*/
protected function snakeCase($str)
{
$array = array();
$len = strlen($str);
for($i = 0; $i < $len; $i++){
if($str[$i] == strtolower($str[$i])){
$array[] = $str[$i];
}else{
if($i>0){
$array[] = '_';
}
$array[] = strtolower($str[$i]);
}
}
$result = implode('',$array);
return $result;
}
/**
* with变量
* @param $method
* @param $parameters
* @return View
*/
public function __call($method, $parameters)
{
if(substr($method,0,4) == 'with') {
return $this->with($this->snakeCase(substr($method, 4)), $parameters[0]);
}
throw new BadMethodCallException("Method $method not found");
}
}
使用方法
$data = [
['name' => '111'],
['name' => '222'],
['name' => '333'],
];
$con = '<div>test111:{!! $test !!}</div><div>name222:{!! $name !!}</div>@foreach($data as $k => $v) <div>namediv:{!! $v[\'name\'] !!}</div> @endforeach';
$minivt = new MiniViewTemplate();
$minivt->setData([
'data' => $data
]);
$minivt->with('test', '66666')->with('name', 'test');
$con = $minivt->renderContent($con);
echo $con;
正文完