最新消息: 新版网站上线了!!!

yii widget 引入到smarty的环境中

给smarty 中引入widget

转载请注明出处:(http://hi.baidu.com/yiqing95)
官网上面有一个实现了 可以参考之(http://www.smarty.net/forums/viewtopic.php?p=74711)
*
widget 是一个比较完整的功能单元,它封装了贯穿视图层到逻辑层,数据访问层的逻辑,可以在别的视图中反复使用;
常见的用途比如:页脚下的网站链接(可以从数据库中增删),最近上传的图片,最热门的评论。这样的逻辑可以被
其他视图包含,你可以在多处以黑盒的方式使用它;

使用方式:
  {widget class='TopComments' maxDisplay='15'}

*
**
实现思路:

首先定义一个smarty插件函数,  在插件函数中实例化指定的类, 并调用“虚”方法run
<?php
  function smarty_function_widget($params, $template) { 
   if (empty($params['class'])) { 
      throw new Exception('请指定Widget的类名称.'); 
   } 
   /**
   * 这个要求预定义widgets存放的路径 如果不存在用eval动态生成子类 此处不用它
   *
   if (file_exists($widgetClassFile = sprintf('%s.php', CONF_DIR_WIDGETS . strtolower($params['class'])))) { 
      require_once ($controller); 
   } else { 
      eval(sprintf('class %s extends SmartyWidget {}', $params['class'])); 
   } 
   */
    //传递smarty 给Widget作为实例化参数
    $params['smarty'] = $template->smarty;
    //调用Widget子类的工厂方法生成类实例 ,所有的Widget类都继承根类 所以都有factory方法
   $widget = call_user_func($params['class'] . '::factory', $params); 
   //调用Widget实例的run方法 子类可以复写改方法来实现特定于自己的功能
   $widget->run(); 
}

//上面的内容改自那个官方文章 好像是供smarty3用的(对3.x不熟  我在使用 2.6)下面给出
<?php 
function smarty_function_widget($params, &$smarty) { 
   if (empty($params['class'])) { 
     throw new Exception('Widget is missing name.'); 
   } 
    /*
   if (file_exists($widgetClassFile = sprintf('%s.php', CONF_DIR_WIDGETS . strtolower($params['class'])))) { 
      require_once ($controller); 
   } else { 
      eval(sprintf('class %s extends SmartyWidget {}', $params['class'])); 
   } */
   $params['smarty'] = $smarty;
           
        if (class_exists($params['class'] )) {
           $widget = call_user_func(array($params['class'] ,'factory'), $params); 
            $widget->run();
         }else{
            throw new Exception('Widget class '.$params['class'].'does not exist!.'); 
         }

}

这里有一个问题需要注意就是类的自动加载 类必须能够找到 如果你要在某个页面中(view视图)使用某个widget那么可以考虑
在改页面对应的控制器中预先(include|require)(_once)? 该类 或者使用spl的自动类加载机制。这里假设你使用类MVC设计模式
所有的视图都是通过控制器来渲染的

**
***
SmartyWidget基类的设计:
该基类是所有widget的公共父类 ,包含大量通用逻辑和模板方法(GOF95中的模板方法设计模式的实现),主要提供了工厂方法用来创建
子类 重新设置smarty的模板路径。  

Widget的文件结构遵从Yii的结构 每个widget类对应的视图路径位于和类文件同目录下的views文件夹
/MyWidget.php
/views/someViewName.php
 一下是完整类
<?php
class SmartyWidget
{

    /**
     * @var null|\Smarty
     */
    protected $smarty;
    /**
     * @var string
     */
    protected $oldSmartyTplPath ;

    /**
     * @static
     * @param array $params
     * @return mixed
     */
    public static function  factory($params = array())
    {
        /*
        * 使用了php5.3的后期静态绑定 版本限制!
        $className = get_called_class();
        return new $className($params);
        */
        $className = $params['class'];
        $smarty = isset($params['smarty']) ? $params['smarty'] : null;
        unset($params['class'], $params['smarty']);

        //实例化类
        $widget = new $className($smarty);
        $properties = $params;
        foreach ($properties as $name => $value) {
            $widget->$name = $value;
        }
        $widget->init();
        return $widget;
    }

//------------------------------------------------------------------------------------------------


    /**
     * @var array view paths for different types of widgets
     * ----------------
     * 静态变量做缓存
     * ----------------
     */
    private static $_viewPaths;

    /**
     * @param null $smarty
     */
    public function __construct($smarty = null)
    {
        if ($smarty == null) {
            include(SMARTY_DIR . 'Smarty.class.php');
            $this->smarty = new Smarty();
            $this->smarty->caching = true;
            //其他相关配置 跟主视图tpl中smarty的配置可以也可以不一致
            //比如模板编译目录 插件目录 smarty的配置路径等
        } else {
            //其实必要时可以选择clone当前smarty对象进行这样被类中的所有配置修改不会影响原始smarty实例
            // 如果你在widget中进行了修改 那么接下来的使用会受前面设置的影响(比如新的模板路径 编译,插件配置路径等)
            $this->smarty = $smarty;
        }
    }


    /**
     *初始化Widget类 这时widget其声明的公共变量已经被赋值了
     *
     */
    public function init()
    {
    }

    /**
     * 执行 widget.
     *-------------------------------------------------------------------------
     *   捕获某个widget的输出  种用法使得widget可以嵌套使用
     *      ob_start();
     *        ob_implicit_flush(false);
     *            $widget = MyWidget::factory($className,$properties);
     *      $widget->run();
     *        return ob_get_clean();
     *---------------------------------------------------------------------------
     */
    public function run()
    {
    }

    /**
     * Returns the directory containing the view files for this widget.
     * The default implementation returns the 'views' subdirectory of the directory containing the widget class file.
     * @return string the directory containing the view files for this widget.
     */
    public function getViewPath()
    {
        $className = get_class($this);
        if (isset(self::$_viewPaths[$className])) {
            return self::$_viewPaths[$className];
        } else
        {

            $class = new ReflectionClass($className);
            return self::$_viewPaths[$className] = dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
        }
    }

    /**
     *返回视图文件路径
     * 由于修改自yii 它可以用多种模板 后缀的配置是在viewRender类中进行的
     * 这里没有必要 简化起见 总是需要带后缀 或者你弄个配置文件 可以配置
     * 模板后缀 使用时只是用模板名字
     * --------------------------------
     * $extension = getFromConfig('smartyTplSuffix');
     *---------------------------------
     * @param $viewName
     * @return bool|string
     */
    public function getViewFile($viewName)
    {

        $extension = '';
        $viewFile = $this->getViewPath() . DIRECTORY_SEPARATOR . $viewName;
        if (is_file($viewFile . $extension)) {
            return $viewFile . $extension;
        } else {
            return false;
        }
    }

    /**
     * Renders a view.
     *
     * The named view refers to a PHP script (resolved via {@link getViewFile})
     * that is included by this method. If $data is an associative array,
     * it will be extracted as PHP variables and made available to the script.
     *
     * @param string $view name of the view to be rendered. See {@link getViewFile} for details
     * about how the view script is resolved.
     * @param array $data data to be extracted into PHP variables and made available to the view script
     * @param boolean $return whether the rendering result should be returned instead of being displayed to end users
     * @return string the rendering result. Null if the rendering result is not required.
     * @throws CException if the view does not exist
     * @see getViewFile
     */
    public function render($view, $data = null, $return = false)
    {
        if (($viewFile = $this->getViewFile($view)) !== false) {
            return $this->renderFile($viewFile, $data, $return);
        } else {
            throw new Exception(strtr('{widget} cannot find the view "{view}".',
                array('{widget}' => get_class($this), '{view}' => $view)));

        }
    }

   /**
    * @param $sourceFile
    * @param null $data
    * @param bool $return
    * @return string
    * @throws Exception
    */
    public function renderFile($sourceFile, $data = null, $return = false)
    {
        // 当前类的属性可以通过{this.property}访问
        $data['this'] = $this;

        //检查视图文件是否存在
        if (!is_file($sourceFile) || ($file = realpath($sourceFile)) === false) {
            throw new Exception(strtr('View file "{file}" does not exist.', array('{file}' => $sourceFile)));
        }
        //assign data
        $this->smarty->assign($data);

        //render or return
        if ($return) {

            //保存原始的模板路径 用完后恢复之
            $this->oldSmartyTplPath = $this->smarty->template_dir;
            //需要复写模板路径
            $this->smarty->template_dir = '';

            $content = $this->smarty->fetch($sourceFile);
            //恢复smarty模板路径
            $this->smarty->template_dir = $this->oldSmartyTplPath;

            return $content;
        } else {
            //保存原始的模板路径 用完后恢复之
            $this->oldSmartyTplPath = $this->smarty->template_dir;
            //需要重置模板路径
            $this->smarty->template_dir = '';

            $this->smarty->display($sourceFile);
            //恢复smarty模板路径
            $this->smarty->template_dir = $this->oldSmartyTplPath;
        }
    }

    /**
     * Renders a view file.
     * This method includes the view file as a PHP script
     * and captures the display result if required.
     * @param string $_viewFile_ view file
     * @param array $_data_ data to be extracted and made available to the view file
     * @param boolean $_return_ whether the rendering result should be returned as a string
     * @return string the rendering result. Null if the rendering result is not required.
     */
    public function renderInternal($_viewFile_, $_data_ = null, $_return_ = false)
    {
        // we use special variable names here to avoid conflict when extracting data
        if (is_array($_data_))
            extract($_data_, EXTR_PREFIX_SAME, 'data');
        else
            $data = $_data_;
        if ($_return_) {
            ob_start();
            ob_implicit_flush(false);
            require($_viewFile_);
            return ob_get_clean();
        }
        else
            require($_viewFile_);
    }
}
***
****
使用方法:
1.将插件放入smarty的插件目录:Smarty/plugins/ ,文件名function.widget.php  看看其他的插件后缀是否带php自己调整
2.新建一个专门存放widget的目录 比如ROOT_DIR.'/widgets' 根目录下建立一个专用目录,将SmartyWidget.php 基类文件放入
  其中;新建一个views目录用来存放模板。
3.新建一个类继承SmartyWidget类:
  比如(TestWidget.php):
   <?php 
class TestWidget extends SmartyWidget{
   public function run()
    {
        echo __FILE__,__METHOD__;
    }
    
}

4. 在某个视图中引入此widget 比如:
   <body>
         <div>
          hi这是widget测试!
          
           <{widget class='TestWidget'}>
         </div>
  </body>
  
5.在你的浏览其中观看结果  ,  

6.测试 带视图的情形 新建另一个测试子类:
<?php 
class TestWidget2 extends SmartyWidget{
   public function run()
    {
        $this->render('test2.html');
    }
    
    
}
 用法参考4  <{widget class='TestWidget2'}>

7. 测试公共变量传递
   <?php 
    class TestWidget3 extends SmartyWidget{
       public $someVar;

       public function run()
        {
            echo $this->someVar;
        }
        
        
    }
    
    用法同4 : <hr>
           <{widget class='TestWidget3' someVar="hi this string will passe to the widget!"}>
           
    观看输出情况 
8. 最后一个测试请自己做吧   , 任务:测试变量传递到widget的视图 并测试$this->xxx  看好用不;
  截图:
 文件结构:

 

放入smarty插件路径:

视图层用法:

   <div>
          hi这是widget测试!
          
           <{widget class='TestWidget'}>
           <hr>
           <{widget class='TestWidget2'}>
            <hr>
           <{widget class='TestWidget3' someVar="hi this string will passe to the widget!"}>
         </div>

效果图:

  

 

****
****
后记 上面的策略均来自yii  模板设计模式的应用是体现在init方法和run方法上 这两个方法推迟到子类中实现了,一般在
run方法中可以调用$this->render('viewName.tpl',array('k1'=>$v1,'k2'=>$v2.....));  上面的测试中你其实还可以从模型层

数据库查询东西 输出到widget的视图上  这个任务也留给感兴趣的朋友们了;  widget 在各个主流框架中已经作为司空见惯的东西了 这个方案只是为了使得以前的项目也可以
应用widget 大家感兴趣可以参考下yii的widget实现 ,在ThinkSns中也实现了Widget类(可以下下来看下源码 它是开源的)

 

转载请注明:谷谷点程序 » yii widget 引入到smarty的环境中