Yii2 自定义 Widget

使用 widget

yii 的小组件是为了实现视图的可重用,例如一些常用的日期组件、弹框插件等,yii 巧妙的使用了 widget 将其封装,在视图中可简单调用,这样良好的封装减少我们开发重复代码,是一种优秀的设计。下面我们做个示例。

例如面包屑组件的使用

1
2
3
4
5
6
7
8
9
10
// 在视图文件中引入 breadcrumb 组件
use yii\widgets\Breadcrumbs;

Breadcrumbs::widget([
'links' => [
['Home']
]
]);

// 只是简单的代码就讲 links 数组中的面包屑地址生成一段 html ,这样就减少我们代码量

所有的小组件都是继承 yii\base\Widget 类,里面定义了静态的方法 widgetbeginend,在上面例子中使用了 widget,在调用 yii\widgets\ActiveForm 组件的时候,由于有开始标签 form 和闭合标签 </form>,在标签中间也有相应的输出,yii 也提供了便捷的实现方法,在开始调用 begin,中间书写其他表单元素,最后调用 end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;


$form = ActiveForm::begin(); ?>

<?= $form->field($model, 'name') ?>

<?= $form->field($model, 'email') ?>

<div class="form-group">
<?= Html::submitButton('Submit', ['class' => 'btn btn-primary']) ?>
</div>

<?php ActiveForm::end(); ?>

创建 widget

创建 widget 必须继承 yii\base\Widget 来实现,实现 initrun 方法

由于 yii\base\Widget 继承 yii\base\Component,而 yii\base\Component 继承 yii\base\Object

yii\base\Object 在实例化方法中会调用 $this->init() 方法,所以在 widget 实例化的时候会调用 init 方法

run 方法会在 yii\base\Widget 继承的 widgetend 方法中调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#path common/widgets/Demo
<?php
namespace common\widgets;

use yii\bootstrap\Widget;

class Demo extends Widget
{
public $message;

public function init()
{
$this->message = 'hello widget';
}

public function run()
{
return $this->message;
}
}

使用方式就想上面的例子一样,在视图文件中引入,调用 widget 方法

1
2
3
4
5
6
# view file

use common\widgets\Demo;

Demo::widget(); // 不传参数
Demo::widget(['message' => 'this is widget']); // 传递参数 message

上面由于继承 yii\base\Widget,所以调用的静态方法 widget 也是这个类中,看下其的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static function widget($config = [])
{
ob_start();
ob_implicit_flush(false);
try {

$config['class'] = get_called_class(); // 获取调用类名
// 调用 Yii 的 createObject 实例化,使用的 DI 容器的 get 方法创建
// 在实例化的时候会调用 init 方法
$widget = Yii::createObject($config);
$out = $widget->run(); // 执行 run 方法,也就是上面继承实现的方法
} catch (\Exception $e) {

if (ob_get_level() > 0) {
ob_end_clean();
}
throw $e;
}

return ob_get_clean() . $out; // 返回结果
}

Yii::createObjectvendor/yiisoft/yii2/BaseYii.php 中实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static function createObject($type, array $params = [])
{
if (is_string($type)) {
return static::$container->get($type, $params);
} elseif (is_array($type) && isset($type['class'])) {
// 由于上面传递的是数组,并且里面有 class 这个键值,所以走这里的判断
$class = $type['class'];
unset($type['class']);
return static::$container->get($class, $params, $type); // 这里调用的是 `yii\di\Container` 的 `get` 方法
} elseif (is_callable($type, true)) {
return static::$container->invoke($type, $params);
} elseif (is_array($type)) {
throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
} else {
throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));
}
}

beginend 这一对的实现

主要在 ActionForm 组件中有良好的体现,其具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# begin
public static function begin($config = [])
{
$config['class'] = get_called_class();
$widget = Yii::createObject($config);
static::$stack[] = $widget; // 利用栈的后进先出的特性,实现嵌套

return $widget;
}

# end
public static function end()
{
if (!empty(static::$stack)) {
$widget = array_pop(static::$stack); // 栈的特性,后进先出,最后 begin 的先 end
if (get_class($widget) === get_called_class()) {
echo $widget->run(); // 调用定义的 run 方法
return $widget;
} else {
throw new InvalidCallException('Expecting end() of ' . get_class($widget) . ', found ' . get_called_class());
}
} else {
throw new InvalidCallException('Unexpected ' . get_called_class() . '::end() call. A matching begin() is not found.');
}
}

看了上面,就可以自己对组件进行封装,便于开发中重复使用。

©版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 & 作者信息

End

坚持原创技术分享,您的支持将鼓励我继续创作!
Flyertutor WeChat Pay

WeChat Pay

Flyertutor Alipay

Alipay