diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5ebe21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +/vendor/ \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..5e2f244 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Hadi Akbarzadeh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fbd0468 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Hooker for PHP + +> A simple hook system library (actions and filters) based on Symfony EventDispatcher. + +## 🫡 Usage + +### 🚀 Installation + +You can install the package via composer: + +```bash +composer require nabeghe/hooker +``` + +### Examples + +Check the examples folder in the repositiry. + +#### Action + +```php +require 'vendor/autoload.php'; + +use Nabeghe\Hooker\Hooker; +use Nabeghe\Hooker\Action; + +$hooker = new Hooker(); + +$hooker->setDefaultArgToHooks('protocol', 'https://'); + +$hooker->listen('your_custom_action_name', function (Action $action) { + echo $action['protocol'].$action['url'].PHP_EOL; +}, 2); + +$hooker->action('your_custom_action_name', ['url' => 'https://github.com/nabeghe/hooker']); +``` + +#### Filter: + +```php +require 'vendor/autoload.php'; + +use Nabeghe\Hooker\Hooker; +use Nabeghe\Hooker\Filter; + +$hooker = new Hooker(); + +$hooker->listen('your_custom_action_name', function (Filter $filter) { + if ($filter->getValue() === null) { + $filter->setValue(8 + $filter['default']); + } +}); + +$value = $hooker->filter('your_custom_action_name', null, ['default' => 5]); +echo $value; // 13 +``` \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c0f9d55 --- /dev/null +++ b/composer.json @@ -0,0 +1,24 @@ +{ + "name": "nabeghe/hooker", + "description": "A simple hook system library (actions and filters) based on Symfony EventDispatcher.", + "type": "library", + "version": "0.2.0", + "homepage": "https://github.com/nabeghe/hooker", + "license": "MIT", + "autoload": { + "psr-4": { + "Nabeghe\\Hooker\\": "src/" + } + }, + "authors": [ + { + "name": "Hadi Akbarzadeh", + "email": "hadicoder@gmail.com", + "homepage": "https://elatel.ir", + "role": "Developer" + } + ], + "require": { + "symfony/event-dispatcher": "^7.1" + } +} \ No newline at end of file diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..06f6697 --- /dev/null +++ b/composer.lock @@ -0,0 +1,225 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "f4f15e798e20f102cf1983bca3db4ee6", + "packages": [ + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/examples/1-action.php b/examples/1-action.php new file mode 100644 index 0000000..14b6e70 --- /dev/null +++ b/examples/1-action.php @@ -0,0 +1,30 @@ +setDefaultArgToHooks('protocol', 'https://'); + +$hooker->listen('your_custom_action_name', function (Action $action) { + echo 'Priority 2: '.$action['protocol'].$action['url'].PHP_EOL; +}, 2); // priority 2 + +$hooker->listen('your_custom_action_name', function (Action $action) { + echo 'Priority 4: '.$action['protocol'].$action['url'].PHP_EOL; +}, 4); // priority 4 + +$hooker->listen('your_custom_action_name', function (Action $action) { + echo 'Priority 3: '.$action['protocol'].$action['url'].PHP_EOL; +}, 3); // priority 3 + +$hooker->listen('your_custom_action_name', function (Action $action) { + echo 'Priority 1: '.$action['protocol'].$action['url'].PHP_EOL; +}, 1); // priority 1 + +$hooker->listen('your_custom_action_name', function (Action $action) { + echo 'Priority 5: '.$action['protocol'].$action['url'].PHP_EOL; +}, 5); // priority 5 + +$hooker->action('your_custom_action_name', ['url' => 'https://github.com/nabeghe/hooker']); \ No newline at end of file diff --git a/examples/2-filter.php b/examples/2-filter.php new file mode 100644 index 0000000..48280d0 --- /dev/null +++ b/examples/2-filter.php @@ -0,0 +1,23 @@ +listen('your_custom_action_name', function (Filter $filter) { + if ($filter->getValue() === null) { + $filter->setValue($filter['default']); + } +}); + +$hooker->listen('your_custom_action_name', function (Filter $filter) { + $filter->setValue($filter->getValue() + 2); +}); + +$hooker->listen('your_custom_action_name', function (Filter $filter) { + $filter->setValue($filter->getValue() + 3); +}); + +$value = $hooker->filter('your_custom_action_name', null, ['default' => 9]); +echo $value; // 13 \ No newline at end of file diff --git a/examples/3-custom-hooker.php b/examples/3-custom-hooker.php new file mode 100644 index 0000000..239d8bf --- /dev/null +++ b/examples/3-custom-hooker.php @@ -0,0 +1,16 @@ +listen('your_custom_action_name', function (Action $action) { + echo $action['url'].PHP_EOL; +}); + +$hooker->action('your_custom_action_name', ['url' => 'https://github.com/nabeghe/hooker']); \ No newline at end of file diff --git a/src/Action.php b/src/Action.php new file mode 100644 index 0000000..c573079 --- /dev/null +++ b/src/Action.php @@ -0,0 +1,5 @@ +value; + } + + /** + * Changes filtered value. + * @param mixed $value + */ + public function setValue(mixed $value): void + { + $this->value = $value; + } + + /** + * @param mixed|null $value + * @return static|mixed + */ + public function value(mixed $value = null): mixed + { + if (func_num_args() === 0) { + return $this->value; + } + $this->value = $value; + return $this; + } + + public function __construct(string $name, mixed $value, ?array $args = null) + { + $this->value = $value; + parent::__construct($name, $args); + } +} diff --git a/src/Hook.php b/src/Hook.php new file mode 100644 index 0000000..dbfa6fe --- /dev/null +++ b/src/Hook.php @@ -0,0 +1,63 @@ +hookName; + } + + /** + * Constructor. + * @param string $hookName Hook name. + * @param array|null $hookArgs Optional. Hook arguments. The keys of this array can be accessed through dynamic fields as well as by index. Default null. + * @param \Tueen\Tueen|null $bot Optional. Bot object. Default null. + */ + public function __construct(protected string $hookName, protected array $hookArgs = []) + { + } + + public function offsetExists(mixed $offset): bool + { + return isset($this->hookArgs[$offset]); + } + + public function offsetGet(mixed $offset): mixed + { + return $this->hookArgs[$offset] ?? null; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + $this->hookArgs[$offset] = $value; + } + + public function offsetUnset(mixed $offset): void + { + unset($this->$offset); + } + + public function __get(string $name) + { + return $this->hookArgs[$name] ?? null; + } + + public function __set(string $name, $value): void + { + $this->hookArgs[$name] = $value; + } + + public function __call(string $name, array $arguments) + { + if (isset($arguments[0])) { + $this->hookArgs[$name] = $arguments[0]; + return $this; + } + return $this->hookArgs[$name] ?? null; + } +} \ No newline at end of file diff --git a/src/Hooker.php b/src/Hooker.php new file mode 100644 index 0000000..e70ecc4 --- /dev/null +++ b/src/Hooker.php @@ -0,0 +1,13 @@ +dispatcher = $dispatcher; + } +} \ No newline at end of file diff --git a/src/HookerTrait.php b/src/HookerTrait.php new file mode 100644 index 0000000..62102a7 --- /dev/null +++ b/src/HookerTrait.php @@ -0,0 +1,72 @@ +dispatcher)) { + $this->dispatcher = new EventDispatcher(); + } + } + + public function setDefaultArgToHooks(mixed $name, mixed $value): void + { + $this->hooksDefaultArgs[$name] = $value; + } + + public function removeDefaultArgFromHooks(mixed $name, mixed $value): void + { + unset($this->hooksDefaultArgs[$name]); + } + + public function clearHooksDefaultArgs(mixed $name, mixed $value): void + { + $this->hooksDefaultArgs = []; + } + + private function modifyHookArgs(array &$args): void + { + if ($this->hooksDefaultArgs) { + foreach ($this->hooksDefaultArgs as $name => $value) { + $args[$name] = $value; + } + } + } + + public function action(string $name, ?array $args = []): void + { + $this->initHookerTrait(); + $this->modifyHookArgs($args); + $this->dispatcher->dispatch(new Action($name, $args), $name); + } + + public function filter(string $name, mixed $value, array $args = []): mixed + { + $this->initHookerTrait(); + $this->modifyHookArgs($args); + $filter = new Filter($name, $value, $args); + $filter = $this->dispatcher->dispatch($filter, $name); + return $filter->getValue(); + } + + public function listen(string $eventName, callable|array $listener, int $priority = 0): void + { + $this->initHookerTrait(); + $this->dispatcher->addListener($eventName, $listener, $priority); + } + + public function addAction(string $name, callable|array $callback, int $priority = 0): void + { + $this->listen($name, $callback, $priority); + } + + public function addFilter(string $name, callable|array $callback, int $priority = 0): void + { + $this->listen($name, $callback, $priority); + } +} \ No newline at end of file