{"id":36629052,"url":"https://github.com/kristos80/hook","last_synced_at":"2026-02-10T17:11:59.114Z","repository":{"id":317857398,"uuid":"1069110209","full_name":"kristos80/hook","owner":"kristos80","description":"A lightweight (2.5KB), WordPress-style hooks system for PHP. Add actions and filters with priority support to create extensible, event-driven applications.","archived":false,"fork":false,"pushed_at":"2026-02-02T14:18:02.000Z","size":94,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-02-03T02:51:06.882Z","etag":null,"topics":["code-port","events","filter","filters","hook","hooks","php","wordpress"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kristos80.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-03T12:21:33.000Z","updated_at":"2026-02-02T14:18:06.000Z","dependencies_parsed_at":"2025-10-03T14:40:36.985Z","dependency_job_id":null,"html_url":"https://github.com/kristos80/hook","commit_stats":null,"previous_names":["kristos80/hook"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/kristos80/hook","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kristos80%2Fhook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kristos80%2Fhook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kristos80%2Fhook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kristos80%2Fhook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kristos80","download_url":"https://codeload.github.com/kristos80/hook/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kristos80%2Fhook/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29309204,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-10T16:09:25.305Z","status":"ssl_error","status_checked_at":"2026-02-10T16:08:52.170Z","response_time":65,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["code-port","events","filter","filters","hook","hooks","php","wordpress"],"created_at":"2026-01-12T09:35:21.606Z","updated_at":"2026-02-10T17:11:59.109Z","avatar_url":"https://github.com/kristos80.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🪝 Hook\n\nA lightweight, WordPress-style hooks system for PHP. Add actions and filters with priority support to create extensible,\nevent-driven applications.\n\n---\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=kristos80_hook\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=kristos80_hook)\n[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=kristos80_hook\u0026metric=bugs)](https://sonarcloud.io/summary/new_code?id=kristos80_hook)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=kristos80_hook\u0026metric=coverage)](https://sonarcloud.io/summary/new_code?id=kristos80_hook)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=kristos80_hook\u0026metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=kristos80_hook)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=kristos80_hook\u0026metric=security_rating)](https://sonarcloud.io/summary/new_code?id=kristos80_hook)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=kristos80_hook\u0026metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=kristos80_hook)\n[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=kristos80_hook\u0026metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=kristos80_hook)\n\n## Features\n\n- ✅ WordPress-inspired API (`addAction`, `addFilter`, `doAction`, `applyFilter`)\n- ✅ Priority-based execution order (with per-hook priority support)\n- ✅ Multiple callbacks per hook\n- ✅ Multiple hook names in a single call\n- ✅ Supports all PHP callable types (closures, functions, static methods, instance methods, invokables)\n- ✅ Optimized sorting (sorted once, cached until modified)\n- ✅ Type-safe with strict types\n- ✅ Interface-based design (`HookInterface`)\n- ✅ Optional type hint enforcement for callbacks\n- ✅ Zero dependencies\n\n## Installation\n\n```bash\ncomposer require kristos80/hook\n```\n\n## Usage\n\n### Basic Filter\n\n```php\nuse Kristos80\\Hook\\Hook;\n\n$hook = new Hook();\n\n// Add a filter\n$hook-\u003eaddFilter('format_title', function(string $title) {\n    return strtoupper($title);\n});\n\n// Apply the filter\n$result = $hook-\u003eapplyFilter('format_title', 'hello world');\necho $result; // HELLO WORLD\n```\n\n### Priority-based Execution\n\nLower priority numbers run first (default is 10):\n\n```php\n$hook-\u003eaddFilter('modify_value', function(int $value) {\n    return $value * 2;\n}, 10);\n\n$hook-\u003eaddFilter('modify_value', function(int $value) {\n    return $value + 5;\n}, 5); // Runs first\n\n$result = $hook-\u003eapplyFilter('modify_value', 10);\necho $result; // 30 (first: 10 + 5 = 15, then: 15 * 2 = 30)\n```\n\n### Actions\n\nActions are filters that don't return values:\n\n```php\n$hook-\u003eaddAction('user_login', function() {\n    error_log('User logged in');\n});\n\n$hook-\u003edoAction('user_login');\n```\n\n### Multiple Arguments\n\n```php\n$hook-\u003eaddFilter('format_name', function(string $name, string $prefix) {\n    return $prefix . ' ' . $name;\n});\n\n$result = $hook-\u003eapplyFilter('format_name', 'John', 'Mr.');\necho $result; // Mr. John\n```\n\n### Multiple Hook Names\n\nRegister the same callback to multiple hooks at once:\n\n```php\n$hook-\u003eaddAction(['init', 'startup', 'boot'], function() {\n    // Initialization logic\n});\n\n$hook-\u003edoAction('init');    // Executes callback\n$hook-\u003edoAction('startup'); // Executes callback\n$hook-\u003edoAction('boot');    // Executes callback\n```\n\n### Per-Hook Priority\n\nWhen 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:\n\n```php\n// Different priority per hook: 'init' gets priority 1, 'save' gets priority 20\n$hook-\u003eaddFilter(['init', 'save'], function(string $data) {\n    return $data;\n}, [1, 20]);\n\n// Partial array: 'init' gets priority 5, 'save' and 'cleanup' fall back to index 0 (priority 5)\n$hook-\u003eaddAction(['init', 'save', 'cleanup'], function() {\n    // ...\n}, [5]);\n\n// Single int still works as before — all hooks get the same priority\n$hook-\u003eaddFilter(['init', 'save'], $callback, 10);\n```\n\n### Callable Types\n\nThe library accepts any valid PHP callable:\n\n```php\n// Closure\n$hook-\u003eaddFilter('my_filter', function(string $value) {\n    return strtoupper($value);\n});\n\n// Function name (string)\n$hook-\u003eaddFilter('my_filter', 'strtoupper');\n\n// Static method (string)\n$hook-\u003eaddFilter('my_filter', 'MyClass::transform');\n\n// Static method (array)\n$hook-\u003eaddFilter('my_filter', [MyClass::class, 'transform']);\n\n// Instance method (array)\n$formatter = new TextFormatter();\n$hook-\u003eaddFilter('my_filter', [$formatter, 'format']);\n\n// Invokable object\nclass MyTransformer {\n    public function __invoke(string $value): string {\n        return strtoupper($value);\n    }\n}\n$hook-\u003eaddFilter('my_filter', new MyTransformer());\n```\n\n### Enforcing Type Hints on Callbacks\n\nUse the `requireTypedParameters` named argument to enforce that all callback parameters have type hints:\n\n```php\n$hook-\u003eaddFilter('process_data', function(array $data): array {\n    return array_map('strtoupper', $data);\n});\n\n// This will work - callback has typed parameters\n$result = $hook-\u003eapplyFilter('process_data', ['hello'], requireTypedParameters: true);\n\n// Register an untyped callback\n$hook-\u003eaddFilter('other_filter', function($value) {\n    return $value;\n});\n\n// This will throw MissingTypeHintException\n$hook-\u003eapplyFilter('other_filter', 'test', requireTypedParameters: true);\n```\n\nThe `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.\n\n## API Reference\n\n### `addFilter(string|array $hookNames, callable $callback, int|array $priority = 10): void`\n\nAdd a filter callback to one or more hooks.\n\n- `$hookNames` - Hook name(s) to attach to\n- `$callback` - Callable to execute\n- `$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)\n\n### `addAction(string|array $hookNames, callable $callback, int|array $priority = 10): void`\n\nAlias for `addFilter()`. Use for hooks that don't return values.\n\n\u003e **Note:** The `$acceptedArgs` parameter exists for backwards compatibility but is deprecated and no longer used. PHP natively handles argument count validation.\n\n### `applyFilter(string $hookName, ...$arg): mixed`\n\nExecute all callbacks registered to a filter hook.\n\n- `$hookName` - Hook name to execute\n- `...$arg` - Arguments to pass to callbacks\n- `requireTypedParameters: bool` - Named argument to enforce type hints on callbacks (default: false)\n- Returns the filtered value\n- Throws `MissingTypeHintException` if `requireTypedParameters` is true and a callback has untyped parameters\n\n### `doAction(string $hookName, ...$arg): void`\n\nExecute all callbacks registered to an action hook.\n\n- `$hookName` - Hook name to execute\n- `...$arg` - Arguments to pass to callbacks\n- `requireTypedParameters: bool` - Named argument to enforce type hints on callbacks (default: false)\n- Throws `MissingTypeHintException` if `requireTypedParameters` is true and a callback has untyped parameters\n\n## Interface-based Design\n\nThe `Hook` class implements `HookInterface`, providing several benefits:\n\n- **Dependency Injection** - Type-hint against `HookInterface` in your constructors and methods, making dependencies explicit and swappable\n- **Testability** - Easily mock or stub the hook system in unit tests by creating test doubles that implement `HookInterface`\n- **Decoupling** - Your code depends on an abstraction rather than a concrete implementation, following the Dependency Inversion Principle\n- **Extensibility** - Create alternative implementations (e.g., a `NullHook` for disabled hooks, or a `LoggingHook` decorator) without modifying existing code\n- **Contract Guarantee** - The interface defines a clear API contract, ensuring any implementation provides the expected methods\n\n```php\n// Type-hint against the interface for better architecture\nclass UserService {\n    public function __construct(\n        private HookInterface $hooks\n    ) {}\n\n    public function createUser(array $data): User {\n        $data = $this-\u003ehooks-\u003eapplyFilter('user_data', $data);\n        // ...\n    }\n}\n```\n\n## Testing\n\n```bash\n./vendor/bin/pest\n```\n\n## License\n\nMIT\n\n## Author\n\nChristos Athanasiadis - [chris.k.athanasiadis@gmail.com](mailto:chris.k.athanasiadis@gmail.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkristos80%2Fhook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkristos80%2Fhook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkristos80%2Fhook/lists"}