PHP 微型模板引擎的尝试

227次阅读
没有评论

共计 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;

正文完
 0
Eric chan
版权声明:本站原创文章,由 Eric chan 于2020-05-13发表,共计7538字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。