TemplateLayout.php 7.55 KB
<?php

/**
 * 模板视图
 * 
 * @name TemplateLayout
 * @package library/Plugin
 * @copyright yoho.inc
 * @version 1.0 (2015-9-15 14:14:02)
 * @author fei.hong <fei.hong@yoho.cn>
 */

namespace Plugin;

use Yaf\View_Interface;
use Yaf\Dispatcher;
use Yaf\Application;
use Plugin\LightnCandy;

class TemplateLayout implements View_Interface
{
    /* 模板变量 */

    protected $_tpl_vars;
    /* 模板目录 */
    protected $_tpl_dir;
    /* HTML路径 */
    protected $_html_path;

    /**
     * 传递给视图变量
     * 
     * @param mixed $name
     * @param mixed $value
     * @return void
     */
    public function assign($name, $value = null)
    {
        $this->_tpl_vars[$name] = $value;
    }

    /**
     * 清除一个视图变量
     * 
     * @param mixed $name
     * @return void
     */
    public function clear($name = null)
    {
        // 清空指定的变更值
        if (isset($this->_tpl_vars[$name])) {
            unset($this->_tpl_vars[$name]);
        }
        // 清空整个变量值
        else {
            $this->_tpl_vars = array();
        }
    }

    /**
     * 设置生成HTML及存放路径
     * 
     * 如"xxx"则生成"xxx.html"文件, 如"path/xxx"则生成"path/xxx.html"
     * 
     * @param string $htmlPath 生成的Html名字或路径 
     * @return void
     */
    public function html($htmlPath)
    {
        if (is_string($htmlPath)) {
            $this->_html_path = $htmlPath;
        }
    }

    /**
     * 渲染视图模板,并直接输出到客户端
     * 
     * @param string $tpl
     * @param array $tpl_vars
     */
    public function display($tpl, $tpl_vars = array())
    {
        echo $this->render($tpl, $tpl_vars);
    }

    /**
     * 渲染视图模板
     * 
     * @param string $tpl
     * @param array $tpl_vars
     * @return string
     */
    public function render($tpl, $tpl_vars = array())
    {
        $config = Application::app()->getConfig()->get('application');
        $useHtml = isset($this->_html_path) && isset($config->assets->path);

        // 有生成过HTML的,则先尝试获取HTML内容. 有则返回内容,不渲染模板
        if ($useHtml) {
            $result = $this->getHtml($config->assets->path, $this->_html_path);
            if (!empty($result)) {
                return $result;
            }
        }

        $request = Dispatcher::getInstance()->getRequest();
        $tplExt = $config->template->ext;
        $viewPath = $config->template->path;
        $viewName = $viewPath . '/' . strtolower($request->module) . '/' . strtolower($request->controller) . '/' . $tpl . $tplExt;
        // 判断视图模板文件是否存在, 不存在则直接返回空
        if (!file_exists($viewName)) {
            return '';
        }

        // 合并通过assign传递的参数
        if (is_array($this->_tpl_vars)) {
            $tpl_vars = array_merge($this->_tpl_vars, $tpl_vars);
        }
        // 应用版本号
        $tpl_vars['version'] = $config->version;

        // SEO相关的标题、关键词、描述内容信息
        if (!empty($tpl_vars['title_more'])) {
            $tpl_vars['title'] .= $config->seo->title;
        } elseif (!isset($tpl_vars['title'])) {
            $tpl_vars['title'] = $config->seo->title;
        }
        if (isset($tpl_vars['keywords'])) {
            $tpl_vars['keywords'] .= $config->seo->keywords;
        } else {
            $tpl_vars['keywords'] = $config->seo->keywords;
        }
        if (!empty($tpl_vars['description_more'])) {
            $tpl_vars['description'] .= $config->seo->description;
        } elseif (!isset($tpl_vars['description'])) {
            $tpl_vars['description'] = $config->seo->description;
        }

        // 取得模板的最后修改时间戳
        $lastModifyTime = filemtime($viewName);
        // 使用MD5生成唯一的键名
        $makeKey = md5($viewName . strval($lastModifyTime)); 
        // 模板编译成PHP文件所存放的目录
        $compilePath = $config->template->compile;
        // 模板编译成PHP文件所存放的文件路径
        $compilePhp = $compilePath . '/' . $config->version . '/' . $makeKey . '.php'; 

        // 已渲染过该模板,则直接引PHP文件
        if (is_readable($compilePhp)) {
            LightnCandy::getContext();
            $renderer = include($compilePhp);
        }
        // 第一次渲染该模板的流程:取得模板内容 => 预编译成PHP函数 => 写入服务器生成PHP文件
        else {
            $template = file_get_contents($viewName, false, null);
            $phpStr = LightnCandy::compile($template, array(
                // DEBUG: LightnCandy::FLAG_RENDER_DEBUG | LightnCandy::FLAG_ERROR_EXCEPTION
                'flags' => LightnCandy::FLAG_MUSTACHE | LightnCandy::FLAG_HANDLEBARS, // 使用MUSTACHE和HANDLEBARS的模板格式
                'basedir' => array($config->template->partials), // 模板里使用 {{> partial_name}} 时查找的目录
                'fileext' => array($tplExt), // 允许查找文件的后缀
                'lcrun' => 'Plugin\LCRun3', // 指定编译模板的runtime
            ));
            // 文件流方式读取PHP方法
            $renderer = LightnCandy::prepare($phpStr);
            // 将编译过的函数写入PHP文件
            file_put_contents($compilePhp, $phpStr);
        }

        // 装载内容,调用PHP函数
        try {
            $result = $renderer($tpl_vars);
        } catch (Exception $e) {
            $result = '';
        }

        // 保存成指定名字的html文件
        if ($useHtml && !empty($result)) {
            $this->toHtml($config->assets->path, $this->_html_path, $result);
        }

        $config = null;
        $request = null;
        $tpl_vars = null;

        return $result;
    }

    /**
     * 生成HTML
     * 
     * @param string $assetsPath 存放静态页面的目录
     * @param string $htmlPath 指定的html文件路径
     * @param string $result 需要存的内容
     * @return void
     */
    public function toHtml($assetsPath, $htmlPath, $result)
    {
        $htmlName = $assetsPath . '/' . ltrim($htmlPath, '/') . '.html';
        // 判断是否存在目录
        if (strstr($htmlPath, '/')) {
            $this->makedir(dirname($htmlName));
        }
        // 写入html文件
        file_put_contents($htmlName, $result);
    }

    /**
     * 获取HTML
     * 
     * @param string $assetsPath 存放静态页面的目录
     * @param string $htmlPath 指定的html文件路径
     * @return string
     */
    public function getHtml($assetsPath, $htmlPath)
    {
        $result = '';

        $htmlName = $assetsPath . '/' . ltrim($htmlPath, '/') . '.html';
        // 判断文件是否已存在, 存在则不重复生成
        if (file_exists($htmlName)) {
            $result = file_get_contents($htmlName, false, null);
        }

        return $result;
    }

    /**
     * 递归创建目录
     *
     * @param string $path 文件路径
     * @return boolean (false:失败,true:成功)
     */
    protected function makeDir($path)
    {
        return is_dir($path) || ($this->makeDir(dirname($path)) && @mkdir($path, 0755));
    }

    /**
     * 设置视图模板目录
     * 
     * @param string $path
     * @return boolean
     */
    public function setScriptPath($path)
    {
        $result = false;

        if (is_dir($path)) {
            $this->_tpl_dir = $path;
            $result = true;
        }

        return $result;
    }

    /**
     * 获取视图模板目录
     * 
     * @return string
     */
    public function getScriptPath()
    {
        return $this->_tpl_dir;
    }

}