定制 glhd/hooks 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

glhd/hooks

Composer 安装命令:

composer require glhd/hooks

包简介

关键字:

README 文档

README

Build Status Latest Stable Release MIT Licensed Follow @cmorrell.com on bsky

Hooks

Installation

composer require glhd/hooks

Usage

The hooks package provides two types of hooks: hooking into class execution, and hooking into view rendering.

Within Classes

To make a class "hook-able" you need to use the Hookable trait. In your code, you can add callHook() calls anywhere that you want to allow outside code to execute. For example, if you were implementing a Session class, you might want to allow code to hook into before the session starts, and before the session saves:

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');
        // ..
    }
}

Now, you can hook into these points from elsewhere in your app:

// 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');
});

Now, whenever MySessionClass::open is called, a "Starting session '<session name>'" message will be logged, and whenever MySessionClass::write is called, a "Writing session to storage" message will be logged.

Hook Priority

You can pass an additional int priority to your hooks, to account for multiple hooks attached to the same point. For example:

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

Would cause "Registered Second" to log before "Registered First". If you don't pass a priority, the default of 1000 will be used. All hooks at the same priority will be executed in the order they were registered.

Stopping Propagation

Hooks can halt further hooks from running with a special stopPropagation call (just like JavaScript). All hooks receive a Context object as the last argument. Calling stopPropagation on this object will halt any future hooks from running:

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);

In the above case, the 'Lower-priority hook' message will never be logged, because a higher-priority hook stopped propagation before it could run.

Passing data between your code and hooks

There are three different ways that data gets passed in and out of hooks:

  1. Passing arguments into hooks (one-way)
  2. Returning values from hooks (one-way)
  3. Passing data into hooks that can be mutated by hooks (two-way)

One-way data

Options 1 and 2 are relatively simple. Any positional argument that you pass to callHook will be forwarded to the hook as-is. In our example above, the beforeOpened call passed $name to its hooks, and our hook accepted $name as its first argument.

A collection of returned values from our hooks is available to the calling code. For example, if we wanted to allow hooks to add extra recipients to all email sent by our Mailer class, we might do something like:

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');
}

It's important to note that you will always get a collection of results, though, even if there is only one hook attached to a call, because you never know how many hooks may be registered.

Two-way data

Sometimes you need your calling code and hooks to pass the same data in two directions. A common use-case for this is when you want your hooks to have the option to abort execution, or change some default behavior. You can do this by passing named arguments to the call, which will be added to the Context object that is passed as the last argument to your hook.

For example, what if we want hooks to have the ability to prevent mail from sending at all? We might do that with something like:

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;
    }
});

When to use class hooks

Class hooks are mostly useful for package code that needs to be extensible without knowing how it will exactly be extended. The Laravel framework provides similar extension points, like Queue::createPayloadUsing.

In general, you should avoid using class hooks in your application code unless you are dealing with particularly complex conditional logic that really warrants this approach.

Within Views

Sometimes you may want to make certain views "hook-able" as well. For example, suppose you have an ecommerce website that sends out email receipts, and you want to occasionally add promotions or other contextual content to the email message. Rather than constantly adding and removing a bunch of @if calls, you can use a hook:

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

<x-hook name="intro" />

Your receipt info…

<x-hook name="footer" />

Now you have two spots that you can hook into…

// 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'));
}

The View::hook method accepts 4 arguments. The first is the view name that you're hooking into; the second is the name of the hook itself. The third argument can either be a view (or anything that implements the Htmlable contract), or a closure that returns anything that Blade can render. Finally, the fourth argument is a priority value—the lower the priority, the earlier it will be rendered (if there are multiple things hooking into the same spot). If you do not provide a priority, it will be set the 1000 by default.

Explicitly Setting View Name

The <x-hook> Blade component can usually infer what view it's being rendered inside. Depending on how your views are rendered, though, you may need to explicitly pass the view name to the component. You can do that by passing an additional view prop:

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

This is a requirement that we hope to improve in a future release!

View Hook Attributes

It's possible to pass component attributes to your hooks, using regular Blade syntax:

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

Your hooks will then receive the status value (and any other attributes you pass):

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

If you pass the hook a Laravel view, any attributes will automatically be forwarded. This means that you can use the $status variable inside your view. For example, given the following views:

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

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

The following hook code would automatically forward the value "Demoing hooks" as the $status attribute in your my.hook view:

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

glhd/hooks 适用场景与选型建议

glhd/hooks 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 31.16k 次下载、GitHub Stars 达 17, 最近一次更新时间为 2024 年 01 月 21 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「laravel」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 glhd/hooks 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 glhd/hooks 我们能提供哪些服务?
定制开发 / 二次开发

基于 glhd/hooks 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 31.16k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 17
  • 点击次数: 7
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 17
  • Watchers: 1
  • Forks: 1
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2024-01-21