扩展插件
通过事件扩展
这活动服务 是注入或修改核心类或其他插件功能的主要方式。可以导入此服务以在任何类中使用,方法是添加use Event;
到 PHP 文件的顶部(在命名空间语句之后)以导入 Event facade。
订阅事件
订阅事件最常见的地方是boot
一个方法插件注册文件.例如,当用户首次注册时,您可能希望将他们添加到第三方邮件列表,这可以通过订阅winter.user.register
全球事件。
public function boot()
{
Event::listen('winter.user.register', function ($user) {
// Code to register $user->email to mailing list
});
}
同样可以通过扩展模型的构造函数和使用本地事件来实现。
User::extend(function ($model) {
$model->bindEvent('user.register', function () use ($model) {
// Code to register $model->email to mailing list
});
});
声明/触发事件
您可以在全局(通过事件服务)或在本地触发事件。
通过调用触发本地事件fireEvent()
在实现的对象的实例上Winter\Storm\Support\Traits\Emitter
.由于本地事件仅在特定对象实例上触发,因此不需要为它们命名空间,因为给定项目不太可能在本地上下文中的相同对象上触发多个具有相同名称的事件。
$this->fireEvent('post.beforePost', [$firstParam, $secondParam]);
通过调用触发全局事件Event::fire()
.由于这些事件在整个应用程序中是全局的,因此最好通过在事件名称中包含供应商信息来为它们命名空间。如果您的插件作者是 ACME 并且插件名称是 Blog,那么 ACME.Blog 插件提供的任何全局事件都应该以前缀acme.blog
.
Event::fire('acme.blog.post.beforePost', [$firstParam, $secondParam]);
如果在同一位置同时提供全局事件和本地事件,则最好在全局事件之前触发本地事件,以便优先考虑本地事件。此外,全局事件应该提供触发本地事件的对象实例作为第一个参数。
$this->fireEvent('post.beforePost', [$firstParam, $secondParam]);
Event::fire('winter.blog.beforePost', [$this, $firstParam, $secondParam]);
订阅此事件后,参数可在处理程序方法中使用。例如:
// Global
Event::listen('acme.blog.post.beforePost', function ($post, $param1, $param2) {
Log::info($post->name . 'posted. Parameters: ' . $param1 . ' ' . $param2);
});
// Local
$post->bindEvent('post.beforePost', function ($param1, $param2) use ($post) {
Log::info($post->name . 'posted. Parameters: ' . $param1 . ' ' . $param2);
});
扩展后端视图
有时您可能希望允许扩展后端视图文件或部分文件,例如工具栏。这可以使用fireViewEvent
在所有后端控制器中找到的方法。
将此代码放在您的视图文件中:
<div class="footer-area-extension">
<?= $this->fireViewEvent('backend.auth.extendSigninView', [$firstParam]) ?>
</div>
这将允许其他插件通过挂钩事件并返回所需的标记将 HTML 注入到该区域。
Event::listen('backend.auth.extendSigninView', function ($controller, $firstParam) {
return '<a href="#">Sign in with Google!</a>';
});
NOTE:事件处理程序中的第一个参数将始终是调用对象(控制器)。
上面的示例将输出以下标记:
<div class="footer-area-extension">
<a href="#">Sign in with Google!</a>
</div>
使用示例
这些是如何使用事件的一些实际示例。
扩展用户模型
这个例子将修改model.getAttribute
事件的User
通过绑定到其本地事件来建模。这是在内部进行的boot
的方法插件注册文件.在这两种情况下,当$model->foo
属性被访问它将返回值bar.
class Plugin extends PluginBase
{
[...]
public function boot()
{
// Local event hook that affects all users
User::extend(function ($model) {
$model->bindEvent('model.getAttribute', function ($attribute, $value) {
if ($attribute === 'foo') {
return 'bar';
}
});
});
// Double event hook that affects user #2 only
User::extend(function ($model) {
$model->bindEvent('model.afterFetch', function () use ($model) {
if ($model->id !== 2) {
return;
}
$model->bindEvent('model.getAttribute', function ($attribute, $value) {
if ($attribute === 'foo') {
return 'bar';
}
});
});
});
}
}
扩展后端表单
有很多方法可以扩展后端表单.
这个例子将听取backend.form.extendFields
的全球事件Backend\Widget\Form
小部件并在使用表单小部件修改用户时注入一些额外的字段。这个事件也在里面订阅了boot
的方法插件注册文件.
class Plugin extends PluginBase
{
[...]
public function boot()
{
// Extend all backend form usage
Event::listen('backend.form.extendFields', function($widget) {
// Only apply this listener when the Users controller is being used
if (!$widget->getController() instanceof \Winter\User\Controllers\Users) {
return;
}
// Only apply this listener when the User model is being modified
if (!$widget->model instanceof \Winter\User\Models\User) {
return;
}
// Only apply this listener when the Form widget in question is a root-level
// Form widget (not a repeater, nestedform, etc)
if ($widget->isNested) {
return;
}
// Add an extra birthday field
$widget->addFields([
'birthday' => [
'label' => 'Birthday',
'comment' => 'Select the users birthday',
'type' => 'datepicker'
]
]);
// Remove a Surname field
$widget->removeField('surname');
});
}
}
NOTE: 在某些情况下(添加应由可翻译的字段Winter.Translate 例如),您可能希望扩展
backend.form.extendFieldsBefore
事件代替。
扩展后端列表
这个例子将修改backend.list.extendColumns
的全球事件Backend\Widget\Lists
类并在列表用于修改用户的条件下注入一些额外的列值。这个事件也在里面订阅了boot
的方法插件注册文件.
class Plugin extends PluginBase
{
[...]
public function boot()
{
// Extend all backend list usage
Event::listen('backend.list.extendColumns', function ($widget) {
// Only for the User controller
if (!$widget->getController() instanceof \Winter\User\Controllers\Users) {
return;
}
// Only for the User model
if (!$widget->model instanceof \Winter\User\Models\User) {
return;
}
// Add an extra birthday column
$widget->addColumns([
'birthday' => [
'label' => 'Birthday'
],
]);
// Remove a Surname column
$widget->removeColumn('surname');
});
}
}
扩展组件
此示例将声明一个新的全局事件winter.forum.topic.post
和本地事件称为topic.post
里面一个Topic
成分。这是在进行组件类定义.
class Topic extends ComponentBase
{
public function onPost()
{
[...]
/*
* Extensibility
*/
$this->fireEvent('topic.post', [$post, $postUrl]);
Event::fire('winter.forum.topic.post', [$this, $post, $postUrl]);
}
}
接下来,这将演示如何从内部挂钩到这个新事件页面执行生命周期.这将在onPost
事件处理程序在内部被调用Topic
组件(上)。
[topic]
slug = "{{ :slug }}"
==
function onInit()
{
$this['topic']->bindEvent('topic.post', function($post, $postUrl) {
trace_log('A post has been submitted at '.$postUrl);
});
}
扩展后台菜单
此示例将后端的 CMS 和 Pages 标签替换为....
class Plugin extends PluginBase
{
[...]
public function boot()
{
Event::listen('backend.menu.extendItems', function($manager) {
$manager->addMainMenuItems('Winter.Cms', [
'cms' => [
'label' => '...'
]
]);
$manager->addSideMenuItems('Winter.Cms', 'cms', [
'pages' => [
'label' => '...'
]
]);
});
}
}
类似地,我们可以删除具有相同事件的菜单项:
Event::listen('backend.menu.extendItems', function($manager) {
$manager->removeMainMenuItem('Winter.Cms', 'cms');
$manager->removeSideMenuItem('Winter.Cms', 'cms', 'pages');
$manager->removeSideMenuItems('Winter.Cms', 'cms', [
'pages',
'partials'
]);
});