在几乎所有情况下,扩展 October CMS 都发生在插件注册文件中,它本质上是一个 Laravel 服务提供者。该注册文件名为 Plugin.php,位于插件的根目录中。
以下扩展方法可在插件注册类中重写:
| Method | Description |
|---|---|
| register() | called when the plugin is first registered, called before boot. |
| boot() | called right before the request route, called after register. |
| registerMarkupTags() | registers additional markup tags that can be used in the CMS. |
| registerComponents() | registers any CMS components used by this plugin. |
| registerNavigation() | registers backend navigation menu items for this plugin. |
| registerPermissions() | registers any backend permissions used by this plugin. |
| registerSettings() | registers any backend configuration links used by this plugin. |
| registerFormWidgets() | registers any backend form widgets supplied by this plugin. |
| registerReportWidgets() | registers any backend report widgets, including the dashboard widgets. |
| registerListColumnTypes() | registers any custom list column types supplied by this plugin. |
| registerMailTemplates() | registers any mail view templates supplied by this plugin. |
| registerMailLayouts() | registers any mail view layouts supplied by this plugin. |
| registerMailPartials() | registers any mail view partials supplied by this plugin. |
| registerSchedule() | registers scheduled tasks that are executed on a regular basis. |
| registerContentFields() | registers content fields that are used by Tailor blueprints. |
事件服务 是注入或修改核心类或其他插件功能的主要方式。此服务可通过在 PHP 文件顶部(在命名空间声明之后)添加 use Event 来导入 Event facade以便在任何类中使用。
订阅事件最常见的位置是插件注册文件的boot方法。例如,当用户首次注册时,您可能希望将其添加到第三方邮件列表,这可以通过订阅rainlab.user.register全局事件来实现。
class Plugin extends PluginBase
{
// ...
public function boot()
{
Event::listen('rainlab.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
});
});通过在一个实现了 October\Rain\Support\Traits\Emitter 的对象实例上调用 fireEvent() 来在本地触发事件。由于本地事件只在特定的对象实例上触发,因此不需要对其进行命名空间划分,因为在本地上下文中,一个给定项目在同一个对象上触发多个同名事件的可能性较小。
$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('rainlab.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>';
});事件处理程序中的第一个参数将始终是调用对象(控制器)。
:::
上述示例将输出以下标记:
<div class="footer-area-extension">
<a href="#">Sign in with Google!</a>
</div>这些是事件如何使用的一些实用示例。
本示例将通过绑定到 User 模型的本地事件来修改其 model.getAttribute 事件。这将在插件注册文件的 boot 方法内部执行。在这两种情况下,当访问 $model->foo 属性时,它将返回值 bar。
// 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';
}
});
});
});为引入的字段添加模型验证,请挂钩到 beforeValidate 事件并抛出 ValidationException 异常。
User::extend(function ($model) {
$model->bindEvent('model.beforeValidate', function () use ($model) {
if (!$model->billing_first_name) {
throw new \ValidationException(['billing_first_name' => 'First name is required']);
}
});
});有多种方法可以扩展后端表单,请参阅后端控制器文章了解更多信息。
本例将监听 Backend\Widget\Form 小部件的 backend.form.extendFields 全局事件,并在使用 Form 小部件修改用户时注入一些额外字段。该事件也订阅在插件注册文件的 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 \RainLab\User\Controllers\Users) {
return;
}
// Only apply this listener when the User model is being modified
if (!$widget->model instanceof \RainLab\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');
});您也可以使用
backend.form.extendFieldsBefore事件来添加字段。
此示例将修改 Backend\Widget\Lists 类的 backend.list.extendColumns 全局事件,并在列表用于修改用户时注入一些额外的列值。此事件也在插件注册文件的 boot 方法中订阅。
// Extend all backend list usage
Event::listen('backend.list.extendColumns', function ($widget) {
// Only for the User controller
if (!$widget->getController() instanceof \RainLab\User\Controllers\Users) {
return;
}
// Only for the User model
if (!$widget->model instanceof \RainLab\User\Models\User) {
return;
}
// Add an extra birthday column
$widget->addColumns([
'birthday' => [
'label' => 'Birthday'
],
]);
// Remove a Surname column
$widget->removeColumn('surname');
});此示例将声明一个新的全局事件 rainlab.forum.topic.post 和一个名为 topic.post 的本地事件,位于 Topic 组件内。这在 组件类定义 中进行。
class Topic extends ComponentBase
{
public function onPost()
{
// ...
$this->fireEvent('topic.post', [$post, $postUrl]);
Event::fire('rainlab.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);
});
}
?>