https://github.com/kristos80/hook
A lightweight (2.5KB), WordPress-style hooks system for PHP. Add actions and filters with priority support to create extensible, event-driven applications.
https://github.com/kristos80/hook
code-port events filter filters hook hooks php wordpress
Last synced: 4 months ago
JSON representation
A lightweight (2.5KB), WordPress-style hooks system for PHP. Add actions and filters with priority support to create extensible, event-driven applications.
- Host: GitHub
- URL: https://github.com/kristos80/hook
- Owner: kristos80
- License: mit
- Created: 2025-10-03T12:21:33.000Z (9 months ago)
- Default Branch: master
- Last Pushed: 2026-02-02T14:18:02.000Z (5 months ago)
- Last Synced: 2026-02-03T02:51:06.882Z (5 months ago)
- Topics: code-port, events, filter, filters, hook, hooks, php, wordpress
- Language: PHP
- Homepage:
- Size: 91.8 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# 🪝 Hook
A lightweight, WordPress-style hooks system for PHP. Add actions and filters with priority support to create extensible,
event-driven applications.
---
[](https://sonarcloud.io/summary/new_code?id=kristos80_hook)
[](https://sonarcloud.io/summary/new_code?id=kristos80_hook)
[](https://sonarcloud.io/summary/new_code?id=kristos80_hook)
[](https://sonarcloud.io/summary/new_code?id=kristos80_hook)
[](https://sonarcloud.io/summary/new_code?id=kristos80_hook)
[](https://sonarcloud.io/summary/new_code?id=kristos80_hook)
[](https://sonarcloud.io/summary/new_code?id=kristos80_hook)
## 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
- ✅ Zero dependencies
## Installation
```bash
composer require kristos80/hook
```
## Usage
### Basic Filter
```php
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
Lower priority numbers run first (default is 10):
```php
$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 are filters that don't return values:
```php
$hook->addAction('user_login', function() {
error_log('User logged in');
});
$hook->doAction('user_login');
```
### Multiple Arguments
```php
$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
Register the same callback to multiple hooks at once:
```php
$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
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:
```php
// 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
The library accepts any valid PHP callable:
```php
// 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
Use the `requireTypedParameters` named argument to enforce that all callback parameters have type hints:
```php
$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.
## API Reference
### `addFilter(string|array $hookNames, callable $callback, int|array $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`
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`
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`
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
## 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
```php
// 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
```bash
./vendor/bin/pest
```
## License
MIT
## Author
Christos Athanasiadis - [chris.k.athanasiadis@gmail.com](mailto:chris.k.athanasiadis@gmail.com)