abei1982 2017-04-12 07:56:29 10160次浏览 4条回复 20 9 0

yii开发中,用到了大量的widget,我们也能自己建立一个widget,今天为你讲解widget的运行机理,让你游刃有余的操作它。

ActiveForm、Breadcrumbs、DetailView、LinkPager、ListView 等等,这些内置的挂件让我们开发变的如此简单。

本节教你自定义一个widget,顺便说下widget的运行机理和几大重要函数。

老规矩,先说目录。

  • 什么时候用widget
  • 新建一个widget
  • begin & end 方法
  • beforeRun & afterRun 方法

begin...

什么时候用widget

场景还是蛮简单的、当我们发现页面有的区块重复出现了,比如TOP10、比如内容编辑器等、就要考虑是否要做成一个widget了。

新建一个widget

我们还是举例把,新建一个Top10的区块。首先我们要在应用程序的components下新建一个Top10Widget的类,根据psr-4规则,布局应该是这样的

  • components
  • components\Top10Widget.php

Top10Widget.php的内容

namespace app\components;

use yii\base\Widget;
class Top10Widget extends Widget {
	public $catId;
	
	public function init(){
		parent::init();
		if(empty($this->catId)){
			$this->catId = 0;
		}
	}
	
	public function run(){		
		parent::run();
	}
}

作为一个widget,你必须要继承于yii\base\Widget才可以,就像我们的Top10Widget一样。类的变量代表将来视图使用该挂件时对其传递的参数,除此之外我们要重载Widget的init和run两个方法。

要注意的一点是,在方法里需要先调用父类的对应方法,如上面代码一样,这个要记住。

init() 方法旨在对传递过来的变量进行验证、筛选及处理,所以对于init而言,如果参数出现异常代表前台并没有传递参数正确,这个时候你可以使用throw new Exception("xxx");来抛出异常,页面会报错,当然为了美观,我们可以使用try ... catch....将这个异常接住然后更友好的显示。

小提示:对于throw异常的方法,在run中也一样,我建议try .... catch ....它们。

好的,上面说了init的初衷,接下来说下run函数(有些挂件并没有使用init,而是将所有数据都放到run里处理也是可以的,不过我一般不这么干)。

run() 方法代表该关键的执行逻辑,针对于init处理过的变量,进行逻辑处理,最后显示给客户想要的东西。就和action一样,这里面可以执行很多很多。

最后我们可以通过return 或 echo 将结果返回给浏览器

namespace app\components;

use yii\base\Widget;
class Top10Widget extends Widget {
	public $catId;
	
	// 省略的代码....
	
	public function run(){		
		parent::run();
		return $this->catId; // 不完全等价于 echo $this>-catId
	}
}

那么一个问题出现了,如果我仅仅是想像是一些数字结果,而是希望它的样子更丰富,我要如何做那? 我想你也想到了,我们可以对这个挂件加载一个视图,然后在视图里处理。

namespace app\components;

use yii\base\Widget;
class Top10Widget extends Widget {
	public $catId;
	
	// 省略的代码....
	
	public function run(){		
		parent::run();
		return $this->render('top10',[
			'catId'=>$this->catId
		]);
	}
}

top10.php文件在哪建立?按照约定,我们需要在app\components\views下建立这个视图。

use app\components\Top10Widget;
<?= Top10Widget::widget(['catId'=>98]);?>

看到了吧,和控制的视图一样,先use,在使用。

到这里我想大家通过yii的手册都已经知道了,新建一个widget是如此简单。

从上面我们知道,我们通过使用Top10Widget::widget()方法将参数传递给挂件并处理,但是有的时候可能数组很多,用传参方法不太适合,那么我们要如何去做那?见下段。

begin & end 方法

我们可以使用begin和end方法将要传递的内容整体传递给挂件做处理。 如下视图

<?php
use app\components\Top10Widget;
?>

<?php Top10Widget::begin();?>
<b>hello widget</b>
<?php Top10Widget::end();?>

在挂件里,我们如下处理

namespace app\components;

use yii\base\Widget;
class Top10Widget extends Widget {
	public $catId;
	
	public function init(){
		parent::init();
		ob_start();
	}
	
	public function run(){		
		parent::run();
		$content = ob_get_clean();
		return "<div style='color:red'>{$content}</div>";
	}
}

通过 ob_start和ob_get_clean方法,我们获得关键包围的内容,更多关于缓冲区的内容可以看php文档 http://php.net/manual/zh/ref.outcontrol.php

到此为止,我想你对挂件的建立已经没有任何害怕了,它们如此简单,各种情况也都有对策。但是如果你是一个技术发烧友,你一定发现yii\base\Widget下还有两个函数beforeRun和afterRun,似乎他们在run函数前后进行了什么?接下来给你说说它们俩。

beforeRun & afterRun 方法

如果用一句话概括就是我们可以通过beforeRun的返回值是真是假来决定挂件是否执行run函数,而afterRun则能对run的处理结果再一次处理后返给浏览器,看下widget实现源码就知道了

public static function widget($config = [])
{
    ob_start();
    ob_implicit_flush(false);
    try {
        /* @var $widget Widget */
        $config['class'] = get_called_class();
        $widget = Yii::createObject($config);
        $out = '';
        if ($widget->beforeRun()) {
            $result = $widget->run();
            $out = $widget->afterRun($result);
        }
    } catch (\Exception $e) {
        // close the output buffer opened above if it has not been closed already
        if (ob_get_level() > 0) {
            ob_end_clean();
        }
        throw $e;
    }

    return ob_get_clean() . $out;
}

你一定看到了这段代码

if ($widget->beforeRun()) {
    $result = $widget->run();
    $out = $widget->afterRun($result);
}

聪明的你明白了,换句话说,我们可以在自定义widget时候通过重载beforeRun来进一步控制run是否处理,进而让我们的挂件更加灵活。

那么回过头来我们思考,为何要插入这样一个beforeRun开关函数那,原因在于init函数并没有返回值,它控制不了run是否执行,而run函数的功能是接收数据进行数据处理,因此在它之前加一个函数来进行再一次判断决定是否往下走是有必要的。

此篇讲完,希望对你有用。

北哥工兵连 每1-2天一篇干货 http://nai8.me

觉得很赞
您需要登录后才可以回复。登录 | 立即注册