ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Yii 2 组件(Widget)完全指南:复用视图构建块的原理、用法与最佳实践

Yii 2 组件(Widget)完全指南:复用视图构建块的原理、用法与最佳实践 Yii 2 组件Widget完全指南复用视图构建块的原理、用法与最佳实践【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址: https://gitcode.com/gh_mirrors/yi/yii2本文基于仓库内 structure-widgets 指南含对应的 英文版编写并对照框架核心类 framework/base/Widget.php 与测试用例 tests/framework/base/WidgetTest.php 进行源码级佐证。Widget组件是 Yii 2 中面向对象地复用视图代码的核心机制本篇将带你掌握其两种使用方式widget()与begin()/end()、如何自定义属于自己的 Widget、如何用视图文件承载大段渲染内容以及自包含设计等最佳实践。什么是 WidgetWidget 是用于在 视图views 中创建复杂、可配置用户界面元素的可复用构建块以面向对象的方式组织视图代码。例如一个日期选择器date pickerWidget 可以生成漂亮的日历控件让用户挑选日期作为表单输入——你只需要在视图里插入一行代码?php use yii\jui\DatePicker; ? ? DatePicker::widget([name date]) ?Yii 2 框架内置了大量开箱即用的 Widget例如yii\widgets\ActiveFormActive Form活动表单yii\widgets\Menu菜单yii\widgets\Breadcrumbs面包屑yii\widgets\ListView/yii\widgets\DetailView列表与详情视图yii\widgets\LinkPager/yii\widgets\LinkSorter分页与排序yii\widgets\Pjax、yii\widgets\Block、yii\widgets\FragmentCache、yii\widgets\Spaceless等你可以在 framework/widgets 目录下看到这些内置 Widget 的完整源码。此外官方还提供了 jQuery UI Widget 与 Twitter Bootstrap Widget 等扩展包。下面我们先介绍 Widget 的基础知识如需了解某个特定 Widget 的详细用法请查阅其类 API 文档。关于 MVC 中视图的角色可参阅 structure-views 指南Widget 的初始化参数本质上是配置configuration数组。使用 Widget两种调用范式Widget 主要在视图views中使用Yii 提供了两种调用范式自包含式的Widget::widget()与成对包裹式的Widget::begin()/Widget::end()。方式一widget()方法调用[[yii\base\Widget::widget()]]即可在视图中使用一个 Widget。该方法接收一个配置configuration数组来初始化 Widget并返回该 Widget 的渲染结果。例如下面的代码插入一个配置为俄语界面、并把所选日期写入$model的from_date属性的日期选择器?php use yii\jui\DatePicker; ? ? DatePicker::widget([ model $model, attribute from_date, language ru, clientOptions [ dateFormat yy-mm-dd, ], ]) ?源码视角widget()的实现位于 framework/base/Widget.php。其执行流程为调用ob_start()开启输出缓冲防止run()内直接echo的内容污染返回值把调用类名写入$config[class]通过Yii::createObject($config)创建并配置实例依次调用beforeRun()、run()、afterRun($result)返回ob_get_clean() . $out即「缓冲内输出 run() 返回值」拼接的结果若执行过程中抛出异常会先清理输出缓冲再重新抛出避免破坏外层输出。其中beforeRun()/afterRun()会触发EVENT_BEFORE_RUN/EVENT_AFTER_RUN事件见下文「Widget 生命周期与事件钩子」这也是 2.0.11 引入的可扩展点。方式二begin()与end()方法有些 Widget 需要包裹一段内容块此时应把内容放在[[yii\base\Widget::begin()]]与[[yii\base\Widget::end()]]之间。例如下面的代码使用[[yii\widgets\ActiveForm]]生成一个登录表单Widget 会在begin()和end()被调用的位置分别生成form的开闭标签两者之间的所有内容原样渲染在表单内部。?php use yii\widgets\ActiveForm; use yii\helpers\Html; ? ?php $form ActiveForm::begin([id login-form]); ? ? $form-field($model, username) ? ? $form-field($model, password)-passwordInput() ? div classform-group ? Html::submitButton(Login) ? /div ?php ActiveForm::end(); ?注意与widget()不同[[yii\base\Widget::begin()]]返回的是 Widget 实例本身你可以用这个实例继续构建内容例如上面的$form-field()而end()会把run()的渲染结果直接echo出来。源码视角framework/base/Widget.phpbegin()把调用类写入配置后通过Yii::createObject()创建实例并压入静态栈Widget::$stackend()从栈顶弹出实例校验类名匹配后执行beforeRun()→run()→afterRun()并将结果echo输出若begin()与end()未正确配对例如交叉嵌套end()会抛出yii\base\InvalidCallException。测试 tests/framework/base/WidgetTest.php 中的testStackTracking与testStackTrackingDisorder分别验证了「无 begin 直接 end」与「嵌套顺序错乱」两种异常场景。重要提示部分 Widget 会在end()时借助 PHP 的输出缓冲output buffering来调整被包裹的内容因此begin()与end()应当写在同一个视图文件中违反这一规则可能导致意外的输出结果。使用 DI 容器配置 Widget 的全局默认值某种 Widget 的全局默认配置可以通过依赖注入DI容器统一设置。例如让所有LinkPager分页组件默认最多显示 5 个按钮\Yii::$container-set(yii\widgets\LinkPager, [maxButtonCount 5]);这样应用中任何地方渲染LinkPager时都会套用该默认值除非在具体调用处显式覆盖。这正是「依赖注入容器指南中的实际用法一节」所描述的场景——因为widget()与begin()内部都是通过Yii::createObject()创建实例所以 DI 容器的定义在创建阶段就会生效。创建自己的 Widget根据需求自定义 Widget 有两条创建路径二者都要求继承[[yii\base\Widget]]并重写init()和/或run()方法init()通常放置属性初始化/归一化的代码在构造函数末尾被调用见 framework/base/Widget.phprun()通常放置生成渲染结果的代码结果可以直接echo也可以作为字符串返回。路径一基于widget()的自包含 Widget下面的HelloWidget会对message属性做 HTML 编码后输出若未设置该属性则默认显示 Hello Worldnamespace app\components; use yii\base\Widget; use yii\helpers\Html; class HelloWidget extends Widget { public $message; public function init() { parent::init(); if ($this-message null) { $this-message Hello World; } } public function run() { return Html::encode($this-message); } }在视图中使用它?php use app\components\HelloWidget; ? ? HelloWidget::widget([message Good morning]) ?路径二基于begin()/end()的包裹式 Widget下面的变体把begin()与end()之间的内容捕获下来经 HTML 编码后输出namespace app\components; use yii\base\Widget; use yii\helpers\Html; class HelloWidget extends Widget { public function init() { parent::init(); ob_start(); } public function run() { $content ob_get_clean(); return Html::encode($content); } }可以看到init()中启动 PHP 输出缓冲于是init()与run()之间的任何输出都会被捕获在run()中统一处理并返回。提示调用begin()时会创建 Widget 的新实例并在构造函数的末尾立即调用init()调用end()时run()会被执行其返回值由end()直接 echo 出来。使用这个新变体?php use app\components\HelloWidget; ? ?php HelloWidget::begin(); ? 这里可以是任意内容例如包含一个或多个 strongHTML/strong pre标签/pre 如果内容过大请考虑拆分成子视图 ?php echo $this-render(viewfile); // 注意这里的 render() 属于 \yii\base\View因为此代码位于视图文件中而非 Widget 类文件中 ? ?php HelloWidget::end(); ?用视图文件承载大段内容有时 Widget 需要渲染大段内容。虽然可以把所有内容写进run()但更佳实践是放入一个视图文件再用[[yii\base\Widget::render()]]渲染public function run() { return $this-render(hello); }默认情况下Widget 的视图文件应存放在WidgetPath/views目录下WidgetPath即存放 Widget 类文件的目录。因此上例会渲染app/components/views/hello.php假设 Widget 类位于app/components目录。源码视角目录的确定逻辑在getViewPath()framework/base/Widget.php通过反射取得类文件所在目录拼接DIRECTORY_SEPARATOR . views。你可以重写该方法来自定义 Widget 视图目录。此外render()内部委托给getView()返回的视图对象默认为应用组件Yii::$app-getView()见 framework/base/Widget.phprender()支持的视图名称格式包括路径别名如app/views/site/index、以//开头的应用内绝对路径、以/开头的模块内绝对路径以及相对viewPath的相对路径未写扩展名时默认补.php见 framework/base/Widget.php。Widget 生命周期与事件钩子从源码可以确认Yii 2.0.11 为 Widget 内置了三个事件常量定义见 framework/base/Widget.php事件触发时机说明EVENT_INITinitinit()被调用时在构造函数末尾触发可用于初始化逻辑EVENT_BEFORE_RUNbeforeRun执行run()之前事件处理器可将WidgetEvent::$isValid置为false来取消本次执行EVENT_AFTER_RUNafterRun执行run()之后事件处理器可修改WidgetEvent::$result来改写渲染结果事件参数对象为yii\base\WidgetEvent源码见 framework/base/WidgetEvent.php其中$isValid默认true$result保存 Widget 返回值。测试 tests/framework/base/WidgetTest.php 中的testEvents与testPreventRun验证了这两个钩子的实际行为前者依次输出init、before-run、run 结果与after-run的拼接后者通过把isValid置为false使 Widget 完全不执行输出为空字符串。其他实用机制自动 ID 生成未显式指定id时Widget 会以static::$autoIdPrefix默认w加自增计数器生成形如w0、w1的 ID见 framework/base/Widget.php。DI 与类名解析begin()会把「调用类 → 实际创建类」的映射记录在静态变量中使end()在「通过 DI 容器将某 Widget 类替换为子类」时仍能正确配对对应测试testDependencyInjection见 tests/framework/base/WidgetTest.php。框架内置 Widget 目录全部内置实现位于 framework/widgets包括ActiveForm、Menu、ListView、DetailView、Breadcrumbs、LinkPager、Pjax等以Menu为例其类注释给出了多级菜单的用法示例含items、url、visible等配置源码见 framework/widgets/Menu.php。最佳实践Widget 是面向对象地复用视图代码的方式。创建 Widget 时应遵循以下原则遵循 MVC 模式逻辑放在 Widget 类中表现呈现放在视图views中二者职责分离。设计为自包含self-contained使用一个 Widget 时应当能「即插即用」——把它放进视图即可无需额外做任何事。这一点在 Widget 依赖外部资源CSS、JavaScript、图片等时会变得棘手幸运的是Yii 提供了资源包asset bundles机制来解决Widget 可以通过资源包声明并自动加载自己所需的静态资源从而保持自包含。纯视图型 Widget 与视图的关系当 Widget 只包含视图代码时它与一个视图view非常相似。二者的唯一区别在于Widget 是一个可分发redistributable的类而视图只是一段更愿意保留在应用内部的普通 PHP 脚本。因此如果你希望把一段视图能力打包成可复用的分发单元就做成 Widget否则直接用视图即可。小结Widget 是 Yii 2 视图层复用的基石widget()适合自包含、返回字符串的场景begin()/end()适合包裹内容块的场景继承yii\base\Widget并重写init()/run()即可创建自定义 Widget大段渲染内容推荐放入WidgetPath/views目录并由render()渲染。结合 DI 容器配置全局默认值、beforeRun/afterRun事件钩子以及资源包机制可以让 Widget 既灵活又自包含。以上原理均可在 framework/base/Widget.php 及 tests/framework/base/WidgetTest.php 中得到验证建议读者在编写自己的 Widget 前通读这两份文件。【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址: https://gitcode.com/gh_mirrors/yi/yii2创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表