glhd/hooks

维护者

详细信息

github.com/glhd/hooks

源代码

问题

0.5.0 2024-03-13 20:00 UTC

This package is auto-updated.

Last update: 2024-09-13 21:05:31 UTC


README

Build Status Coverage Status Latest Stable Release MIT Licensed Follow @inxilpro on Twitter

钩子

安装

composer require glhd/hooks

用法

hooks 包提供两种类型的钩子:钩入类执行,以及钩入视图渲染。

在类内

要使一个类“可钩”,你需要使用 Hookable 特性。在你的代码中,你可以在任何你想允许外部代码执行的地方添加 callHook() 调用。例如,如果你正在实现一个 Session 类,你可能想允许代码在会话开始之前和会话保存之前钩入。

use Glhd\Hooks\Hookable;

class MySessionClass implements SessionHandlerInterface
{
    use Hookable;
    
    public function public open(string $path, string $name): bool
    {
        $this->callHook('beforeOpened', $name);
        // ...
    }
    
    public function write(string $id, string $data): bool
    {
        $this->callHook('beforeWritten');
        // ..
    }
}

现在,你可以从你的应用程序的其他地方钩入这些点

// Get all the available hook points
$hooks = Session::hook();

// Register your custom code to execute at those points
$hooks->beforeOpened(function($name) {
    Log::info("Starting session '$name'");
});

$hooks->beforeWritten(function() {
    Log::info('Writing session to storage');
});

现在,每当调用 MySessionClass::open 时,将会记录一个 "Starting session '<session name>'" 消息,每当调用 MySessionClass::write 时,将会记录一个 "Writing session to storage" 消息。

钩子优先级

你可以向你的钩子传递一个额外的 int 优先级,以处理同一点的多个钩子。例如

$hooks->beforeOpened(fn($name) => Log::info('Registered First'), 500);
$hooks->beforeOpened(fn($name) => Log::info('Registered Second'), 100);

将导致 "Registered Second" 在 "Registered First" 之前记录。如果你没有传递优先级,将使用默认的 1000。具有相同优先级的所有钩子将按注册顺序执行。

停止传播

钩子可以使用特殊的 stopPropagation 调用停止进一步钩子的执行(就像 JavaScript 一样)。所有钩子都接收一个 Context 对象作为最后一个参数。在对象上调用 stopPropagation 将停止任何未来的钩子执行。

use Glhd\Hooks\Context;

$hooks->beforeOpened(function($name) {
    Log::info('Lower-priority hook');
}, 500);

$hooks->beforeOpened(function($name, Context $context) {
    Log::info('Higher-priority hook');
    $context->stopPropagation();
}, 100);

在上面的例子中,由于一个更高优先级的钩子在它运行之前阻止了传播,因此永远不会记录 'Lower-priority hook' 消息。

在代码和钩子之间传递数据

数据以三种不同的方式在钩子中传递进和出来

  1. 将参数 传入 钩子(单向)
  2. 从钩子 返回 值(单向)
  3. 将数据传入可以由钩子修改的钩子(双向)

单向数据

选项 1 和 2 相对简单。传递给 callHook 的任何位置参数将被原样转发到钩子。在我们的示例中,beforeOpened 调用将其 $name 传递给其钩子,我们的钩子接受 $name 作为其第一个参数。

我们钩子的返回值集合可用于调用代码。例如,如果我们想允许钩子添加额外的收件人到我们 Mailer 类发送的所有电子邮件中,我们可能会这样做:

use Glhd\Hooks\Hookable;

class Mailer
{
    use Hookable;
    
    protected function setRecipients() {
        $recipients = $this->callHook('preparingRecipients')
            ->filter()
            ->append($this->to);
            
        $this->service->setTo($recipients);
    }
}
// Always add QA to recipient list in staging
if (App::environment('staging')) {
    Mailer::hook()->preparingRecipients(fn() => 'qa@myapp.com');
}

需要注意的是,你将始终得到一个结果集合,即使只有一个钩子附加到调用,因为你永远不知道可能注册了多少钩子。

双向数据

有时你需要调用代码和钩子以两个方向传递相同的数据。这种用法的一个常见场景是当你想允许钩子有取消执行或更改默认行为的能力时。你可以通过传递命名参数到调用来实现,这些参数将添加到作为钩子的最后一个参数传递的 Context 对象中。

例如,如果我们想允许钩子完全阻止发送邮件,我们可能会这样做:

use Glhd\Hooks\Hookable;

class Mailer
{
    use Hookable;
    
    protected function send() {
        $result = $this->callHook('beforeSend', $this->message, shouldSend: true);
        
        if ($result->shouldSend) {
            $this->service->send();
        }
    }
}
// Never send mail to mailinator addresses
Mailer::hook()->beforeSend(function($message, $context) {
    if (str_contains($message->to, '@mailinator.com')) {
        $context->shouldSend = false;
    }
});

何时使用类钩子

类钩子主要用于需要可扩展性但不知道具体如何扩展的包代码。Laravel 框架提供了类似的扩展点,例如 Queue::createPayloadUsing

一般来说,除非你处理的是特别复杂的条件逻辑,否则应避免在应用程序代码中使用类钩子。

在视图内

有时你可能希望使某些视图“可钩入”。例如,假设你有一个电子商务网站,该网站会发送电子邮件收据,而你想要偶尔在电子邮件消息中添加促销或其他上下文内容。而不是不断地添加和删除大量的 @if 调用,你可以使用一个钩子

{{-- emails/receipt.blade.php --}}
Thank you for shopping at…

<x-hook name="intro" />

Your receipt info…

<x-hook name="footer" />

现在你有两个可以钩入的地方...

// Somewhere in a `PromotionsServiceProvider` class, perhaps…

if ($this->isInCyberMondayPromotionalPeriod()) {
    View::hook('emails.receipt', 'intro', fn() => view('emails.promotions._cyber_monday_intro'));
}

if (Auth::user()->isNewRegistrant()) {
    View::hook('emails.receipt', 'footer', fn() => view('emails.promotions._thank_you_for_first_purchase'));
}

View::hook 方法接受 4 个参数。第一个是你正在钩入的视图名称;第二个是钩子的名称。第三个参数可以是视图(或任何实现了 Htmlable 协议的对象),或者是一个返回任何 Blade 可以渲染的内容的闭包。最后,第四个参数是 priority 值——优先级越低,渲染越早(如果有多个东西钩入同一个位置)。如果你不提供优先级,它将默认设置为 1000

显式设置视图名称

<x-hook> Blade 组件通常可以推断出它被渲染在哪个视图内。但是,根据你的视图是如何渲染的,你可能需要显式传递视图名称给组件。你可以通过传递一个额外的 view 属性来实现这一点。

<x-hook view="emails.receipt" name="intro" />

这是我们希望在未来的版本中改进的要求!

视图钩子属性

你可以使用常规 Blade 语法将组件属性传递到你的钩子中

<x-hook name="status" status="Demoing hooks" />

你的钩子将接收 status 值(以及你传递的任何其他属性)

View::hook('my.view', 'status', function($attributes) {
    assert($attributes['status'] === 'Demoing hooks');
});

如果你向钩子传递 Laravel 视图,任何属性都将自动转发。这意味着你可以在视图中使用 $status 变量。例如,给定以下视图

{{-- my/view.blade.php --}}
<x-hook name="status" status="Demoing hooks" />

{{-- my/hook.blade.php --}}
<div class="alert">
    Your current status is '{{ $status }}'
</div>

以下钩子代码将自动将值 "Demoing hooks" 作为 $status 属性转发到你的 my.hook 视图中

View::hook('my.view', 'status', view('my.hook'));