PHPackages                             kristos80/hook - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. kristos80/hook

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

kristos80/hook
==============

A lightweight, WordPress-style hooks system for PHP. Add actions and filters with priority support to create extensible, event-driven applications.

2.9.2(1mo ago)0123MITPHPPHP ^8CI passing

Since Oct 3Pushed 1mo agoCompare

[ Source](https://github.com/kristos80/hook)[ Packagist](https://packagist.org/packages/kristos80/hook)[ RSS](/packages/kristos80-hook/feed)WikiDiscussions master Synced 3w ago

READMEChangelog (10)Dependencies (2)Versions (18)Used By (0)

🪝 Hook
======

[](#-hook)

A lightweight, WordPress-style hooks system for PHP. Add actions and filters with priority support to create extensible, event-driven applications.

---

[![Quality Gate Status](https://camo.githubusercontent.com/9e1e69e506a0b8768ee7fa031ccdd71afe75763cc79c98271985d2dfc6544900/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d6b726973746f7338305f686f6f6b266d65747269633d616c6572745f737461747573)](https://sonarcloud.io/summary/new_code?id=kristos80_hook)[![Bugs](https://camo.githubusercontent.com/1aa96b156d54acd04d8df108f4a0ad2021324d3e8c9d573e1717d45f3c672c32/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d6b726973746f7338305f686f6f6b266d65747269633d62756773)](https://sonarcloud.io/summary/new_code?id=kristos80_hook)[![Coverage](https://camo.githubusercontent.com/3d4de58e9cc91e4b34f5fce522dba31128f5acf2629f1db907568ad2be73c405/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d6b726973746f7338305f686f6f6b266d65747269633d636f766572616765)](https://sonarcloud.io/summary/new_code?id=kristos80_hook)[![Reliability Rating](https://camo.githubusercontent.com/f7c6d34fdd2d915f374518f0897ab0e2f3674663d4ac6aacd518347db7889698/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d6b726973746f7338305f686f6f6b266d65747269633d72656c696162696c6974795f726174696e67)](https://sonarcloud.io/summary/new_code?id=kristos80_hook)[![Security Rating](https://camo.githubusercontent.com/03a1856a6da1dea1faca1d0acf8ef7c5979d382e10ff36bb76b471d202c59e3d/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d6b726973746f7338305f686f6f6b266d65747269633d73656375726974795f726174696e67)](https://sonarcloud.io/summary/new_code?id=kristos80_hook)[![Maintainability Rating](https://camo.githubusercontent.com/61efdd60f7b49437961761e767b7165556f06667aabae53eec2bb9c58b5ca454/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d6b726973746f7338305f686f6f6b266d65747269633d7371616c655f726174696e67)](https://sonarcloud.io/summary/new_code?id=kristos80_hook)[![Vulnerabilities](https://camo.githubusercontent.com/a4f61b4c4e87e88c5033507498c43e43d1c4bf86e019080e7bda1602af2ba8c4/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d6b726973746f7338305f686f6f6b266d65747269633d76756c6e65726162696c6974696573)](https://sonarcloud.io/summary/new_code?id=kristos80_hook)

Features
--------

[](#features)

- ✅ WordPress-inspired API (`addAction`, `addFilter`, `doAction`, `applyFilter`)
- ✅ Priority-based execution order (with per-hook priority support)
- ✅ Multiple callbacks per hook
- ✅ Multiple hook names in a single call
- ✅ Supports all PHP callable types (closures, functions, static methods, instance methods, invokables)
- ✅ Optimized sorting (sorted once, cached until modified)
- ✅ Type-safe with strict types
- ✅ Interface-based design (`HookInterface`)
- ✅ Optional type hint enforcement for callbacks
- ✅ Priority introspection (`getMinPriority`, `getMaxPriority`)
- ✅ Built-in `FIRST` and `LAST` priority constants
- ✅ Zero dependencies

Installation
------------

[](#installation)

```
composer require kristos80/hook
```

Usage
-----

[](#usage)

### Basic Filter

[](#basic-filter)

```
use Kristos80\Hook\Hook;

$hook = new Hook();

// Add a filter
$hook->addFilter('format_title', function(string $title) {
    return strtoupper($title);
});

// Apply the filter
$result = $hook->applyFilter('format_title', 'hello world');
echo $result; // HELLO WORLD
```

### Priority-based Execution

[](#priority-based-execution)

Lower priority numbers run first (default is 10):

```
$hook->addFilter('modify_value', function(int $value) {
    return $value * 2;
}, 10);

$hook->addFilter('modify_value', function(int $value) {
    return $value + 5;
}, 5); // Runs first

$result = $hook->applyFilter('modify_value', 10);
echo $result; // 30 (first: 10 + 5 = 15, then: 15 * 2 = 30)
```

### Actions

[](#actions)

Actions are filters that don't return values:

```
$hook->addAction('user_login', function() {
    error_log('User logged in');
});

$hook->doAction('user_login');
```

### Multiple Arguments

[](#multiple-arguments)

```
$hook->addFilter('format_name', function(string $name, string $prefix) {
    return $prefix . ' ' . $name;
});

$result = $hook->applyFilter('format_name', 'John', 'Mr.');
echo $result; // Mr. John
```

### Multiple Hook Names

[](#multiple-hook-names)

Register the same callback to multiple hooks at once:

```
$hook->addAction(['init', 'startup', 'boot'], function() {
    // Initialization logic
});

$hook->doAction('init');    // Executes callback
$hook->doAction('startup'); // Executes callback
$hook->doAction('boot');    // Executes callback
```

### Per-Hook Priority

[](#per-hook-priority)

When registering multiple hook names, you can assign a different priority to each hook by passing an array of priorities. Each hook name is mapped to the priority at the same index, with a fallback to the first priority if the index doesn't exist:

```
// Different priority per hook: 'init' gets priority 1, 'save' gets priority 20
$hook->addFilter(['init', 'save'], function(string $data) {
    return $data;
}, [1, 20]);

// Partial array: 'init' gets priority 5, 'save' and 'cleanup' fall back to index 0 (priority 5)
$hook->addAction(['init', 'save', 'cleanup'], function() {
    // ...
}, [5]);

// Single int still works as before — all hooks get the same priority
$hook->addFilter(['init', 'save'], $callback, 10);
```

### Callable Types

[](#callable-types)

The library accepts any valid PHP callable:

```
// Closure
$hook->addFilter('my_filter', function(string $value) {
    return strtoupper($value);
});

// Function name (string)
$hook->addFilter('my_filter', 'strtoupper');

// Static method (string)
$hook->addFilter('my_filter', 'MyClass::transform');

// Static method (array)
$hook->addFilter('my_filter', [MyClass::class, 'transform']);

// Instance method (array)
$formatter = new TextFormatter();
$hook->addFilter('my_filter', [$formatter, 'format']);

// Invokable object
class MyTransformer {
    public function __invoke(string $value): string {
        return strtoupper($value);
    }
}
$hook->addFilter('my_filter', new MyTransformer());
```

### Enforcing Type Hints on Callbacks

[](#enforcing-type-hints-on-callbacks)

Use the `requireTypedParameters` named argument to enforce that all callback parameters have type hints:

```
$hook->addFilter('process_data', function(array $data): array {
    return array_map('strtoupper', $data);
});

// This will work - callback has typed parameters
$result = $hook->applyFilter('process_data', ['hello'], requireTypedParameters: true);

// Register an untyped callback
$hook->addFilter('other_filter', function($value) {
    return $value;
});

// This will throw MissingTypeHintException
$hook->applyFilter('other_filter', 'test', requireTypedParameters: true);
```

The `requireTypedParameters` argument is stripped and never passed to callbacks. This feature helps enforce stricter contracts when the hook owner wants to ensure all registered callbacks follow type safety conventions.

### Priority Constants

[](#priority-constants)

Use `HookInterface::FIRST` and `HookInterface::LAST` to guarantee a callback runs before or after all others:

```
use Kristos80\Hook\HookInterface;

// Guaranteed to run before any other callback
$hook->addFilter('process', function(string $data) {
    return trim($data);
}, HookInterface::FIRST);

// Guaranteed to run after any other callback
$hook->addFilter('process', function(string $data) {
    return htmlspecialchars($data);
}, HookInterface::LAST);
```

The constants are also accessible via `Hook::FIRST` and `Hook::LAST`. Multiple callbacks at the same constant priority follow FIFO order, consistent with the rest of the priority system.

### Priority Introspection

[](#priority-introspection)

Query the lowest or highest registered priority for a given hook:

```
$hook->addFilter('my_filter', function(string $v) { return $v; }, 5);
$hook->addFilter('my_filter', function(string $v) { return $v; }, 20);
$hook->addFilter('my_filter', function(string $v) { return $v; }, 12);

$hook->getMinPriority('my_filter'); // 5
$hook->getMaxPriority('my_filter'); // 20

// Returns null for hooks with no registered callbacks
$hook->getMinPriority('nonexistent'); // null
$hook->getMaxPriority('nonexistent'); // null
```

API Reference
-------------

[](#api-reference)

### `addFilter(string|array $hookNames, callable $callback, int|array $priority = 10): void`

[](#addfilterstringarray-hooknames-callable-callback-intarray-priority--10-void)

Add a filter callback to one or more hooks.

- `$hookNames` - Hook name(s) to attach to
- `$callback` - Callable to execute
- `$priority` - Execution priority (lower = earlier, default: 10). Pass an array to assign a different priority per hook name (falls back to index 0 for missing indices)

### `addAction(string|array $hookNames, callable $callback, int|array $priority = 10): void`

[](#addactionstringarray-hooknames-callable-callback-intarray-priority--10-void)

Alias for `addFilter()`. Use for hooks that don't return values.

> **Note:** The `$acceptedArgs` parameter exists for backwards compatibility but is deprecated and no longer used. PHP natively handles argument count validation.

### `applyFilter(string $hookName, ...$arg): mixed`

[](#applyfilterstring-hookname-arg-mixed)

Execute all callbacks registered to a filter hook.

- `$hookName` - Hook name to execute
- `...$arg` - Arguments to pass to callbacks
- `requireTypedParameters: bool` - Named argument to enforce type hints on callbacks (default: false)
- Returns the filtered value
- Throws `MissingTypeHintException` if `requireTypedParameters` is true and a callback has untyped parameters

### `doAction(string $hookName, ...$arg): void`

[](#doactionstring-hookname-arg-void)

Execute all callbacks registered to an action hook.

- `$hookName` - Hook name to execute
- `...$arg` - Arguments to pass to callbacks
- `requireTypedParameters: bool` - Named argument to enforce type hints on callbacks (default: false)
- Throws `MissingTypeHintException` if `requireTypedParameters` is true and a callback has untyped parameters

### `HookInterface::FIRST` / `HookInterface::LAST`

[](#hookinterfacefirst--hookinterfacelast)

Priority constants for guaranteed earliest (`PHP_INT_MIN`) and latest (`PHP_INT_MAX`) execution. Also accessible as `Hook::FIRST` / `Hook::LAST`.

### `getMinPriority(string $hookName): ?int`

[](#getminprioritystring-hookname-int)

Get the lowest registered priority for a hook.

- `$hookName` - Hook name to query
- Returns the minimum priority value, or `null` if the hook has no registered callbacks

### `getMaxPriority(string $hookName): ?int`

[](#getmaxprioritystring-hookname-int)

Get the highest registered priority for a hook.

- `$hookName` - Hook name to query
- Returns the maximum priority value, or `null` if the hook has no registered callbacks

Interface-based Design
----------------------

[](#interface-based-design)

The `Hook` class implements `HookInterface`, providing several benefits:

- **Dependency Injection** - Type-hint against `HookInterface` in your constructors and methods, making dependencies explicit and swappable
- **Testability** - Easily mock or stub the hook system in unit tests by creating test doubles that implement `HookInterface`
- **Decoupling** - Your code depends on an abstraction rather than a concrete implementation, following the Dependency Inversion Principle
- **Extensibility** - Create alternative implementations (e.g., a `NullHook` for disabled hooks, or a `LoggingHook` decorator) without modifying existing code
- **Contract Guarantee** - The interface defines a clear API contract, ensuring any implementation provides the expected methods

```
// Type-hint against the interface for better architecture
class UserService {
    public function __construct(
        private HookInterface $hooks
    ) {}

    public function createUser(array $data): User {
        $data = $this->hooks->applyFilter('user_data', $data);
        // ...
    }
}
```

Testing
-------

[](#testing)

```
./vendor/bin/pest
```

License
-------

[](#license)

MIT

Author
------

[](#author)

Christos Athanasiadis -

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance90

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity44

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 79.5% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~13 days

Recently: every ~21 days

Total

17

Last Release

46d ago

Major Versions

1.1.0 → 2.0.02025-12-02

PHP version history (2 changes)1.0.3PHP ^7.4 || ^8

2.0.0PHP ^8

### Community

Maintainers

![](https://www.gravatar.com/avatar/afb9f588fc03038aadb7d1b9509832b16d165e3b34310337efbcb702a37a8657?d=identicon)[kristos80](/maintainers/kristos80)

---

Top Contributors

[![kristos80](https://avatars.githubusercontent.com/u/13762144?v=4)](https://github.com/kristos80 "kristos80 (66 commits)")[![semantic-release-bot](https://avatars.githubusercontent.com/u/32174276?v=4)](https://github.com/semantic-release-bot "semantic-release-bot (17 commits)")

---

Tags

code-porteventsfilterfiltershookhooksphpwordpress

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/kristos80-hook/health.svg)

```
[![Health](https://phpackages.com/badges/kristos80-hook/health.svg)](https://phpackages.com/packages/kristos80-hook)
```

###  Alternatives

[mguinea/laravel-robots

Laravel package to manage robots in an easy way

1562.4k](/packages/mguinea-laravel-robots)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
