diff --git a/.github/workflows/wp.yml b/.github/workflows/wp.yml index 40aa6e3..4699b51 100644 --- a/.github/workflows/wp.yml +++ b/.github/workflows/wp.yml @@ -67,7 +67,7 @@ jobs: strategy: fail-fast: true matrix: - php: ['7.4', '8.0', '8.1', '8.2', '8.3'] + php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] wp: ['6.*', '5.*'] services: diff --git a/README.md b/README.md index 590da06..ce56b06 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,6 @@ $registry = new Registry(); $registry->addAction('init', 'initialise'); $registry->addFilter('the_content', 'content', 100); $registry->addAction('add_option', 'option', 100, 2); -$registry->register(); ``` ### Using PHP Attributes @@ -72,27 +71,73 @@ class HelloWorld $registry = new Registry(); $registry->parse(new HelloWorld()); -$registry->register(); ``` > [!NOTE] > Attributes will only be applied to non-abstract public methods that are not PHP native methods or any methods that begin with `__` like `__constructor`, `__clone`, and `__callStatic`. > If you add the Attributes at the class level, the class should implement the `__invoke` method, as shown in the above example. -### Deregistering Hooks +### Removing Hook -You can also deregister hooks, which will remove all the actions and filters that have been registered in the `Hook` instance: +You can also remove a hook similarly to how you would with the native WordPress functions: ```php $registry = new Registry(); $registry->addAction('init', 'initialise'); $registry->addFilter('the_content', 'content', 100); -$registry->register(); // ...later in the code... -$registry->deregister(); +$registry->removeAction('init', 'initialise'); +$registry->removeFilter('the_content', 'content', 100); ``` +It is also possible to remove all hooks at once: + +```php +$registry = new Registry(); +$registry->addAction('init', 'initialise'); +$registry->addFilter('the_content', 'content', 100); + +// ...later in the code... +$registry->removeAll(); +``` + +It is possible to attach hook with method from an object instance, a static method, or a closure: + +```php +use Syntatis\WPHook\Registry; + +$helloWorld = new HelloWorld(); +$anonymous = fn () => 'Hello, World!'; + +$registry = new Registry(); +$registry->addFilter('the_content', [$helloWorld, 'content'], 100); +$registry->addAction('init', $anonymous); +``` + +However, this makes it rather tricky to remove the hook later on the code since you need to pass the same object instance or the same reference to the anonymous function to the `removeAction` and `removeFilter` methods, which is not always possible. + +To circumvent this, you can pass `id` to the `addAction` and `addFilter` methods, and refer the id using `@` symbol when removing the hook. For example: + +```php +use Syntatis\WPHook\Registry; + +$helloWorld = new HelloWorld(); +$anonymous = fn () => 'Hello, World!'; + +$registry = new Registry(); +$registry->addFilter('the_content', [$helloWorld, 'content'], 100, 1, ['id' => 'the-content-hello-world']); +$registry->addAction('init', $anonymous, 10, 1, ['id' => 'init-hello-world']); + +// ...much later in the code... + +$registry->removeAction('init', '@init-hello-world', 10); +$registry->removeFilter('the_content', '@the-content-hello-world', 100); +``` +> [!IMPORTANT] +> The ID must be all lowercase and use words separated by `-`, `.`, or `_`. It should not have any uppercase letters, spaces, or special characters. You can use a slash (`/`) to define the namespace, like `acme/hello-world`, to avoid conflicts with other plugins or themes. +> Please note that the ID added within the registry must be unique. If you're trying to add the same ID twice, it will throw an exception. + ## References - [WordPress Plugin Boilerplate](https://wppb.me/) diff --git a/app/Exceptions/RefExistsException.php b/app/Exceptions/RefExistsException.php new file mode 100644 index 0000000..449e147 --- /dev/null +++ b/app/Exceptions/RefExistsException.php @@ -0,0 +1,17 @@ + */ + protected array $options; + + /** + * @param array $options + * + * @phpstan-param non-empty-string $name + */ public function __construct( string $name, int $priority = 10, - int $acceptedArgs = 1 + int $acceptedArgs = 1, + array $options = [] ) { $this->name = $name; $this->priority = $priority; $this->acceptedArgs = $acceptedArgs; + $this->options = $options; } public function getName(): string @@ -45,4 +54,10 @@ public function getAcceptedArgs(): int { return $this->acceptedArgs; } + + /** @return array */ + public function getOptions(): array + { + return $this->options; + } } diff --git a/app/Registry.php b/app/Registry.php index ba73935..6b2dd13 100644 --- a/app/Registry.php +++ b/app/Registry.php @@ -4,8 +4,21 @@ namespace Syntatis\WPHook; +use Closure; +use InvalidArgumentException; +use Syntatis\WPHook\Exceptions\RefExistsException; +use Syntatis\WPHook\Exceptions\RefNotFoundException; use Syntatis\WPHook\Support\Parser; +use function count; +use function get_class; +use function gettype; +use function is_array; +use function is_string; +use function preg_match; +use function spl_object_hash; +use function trim; + /** * This class manages the registration of all actions and filters for the plugin. * @@ -15,71 +28,121 @@ */ final class Registry { + /** @var array */ + private array $refs = []; + + /** + * Holds aliases to refs. + * + * @var array + */ + private array $aliases = []; + /** * The array of actions registered with WordPress. * - * @var array + * @var array */ private array $actions = []; /** * The array of filters registered with WordPress. * - * @var array + * @var array */ private array $filters = []; /** * Add a new action to the collection to be registered with WordPress. * - * @param string $name The name of the WordPress action that is being registered. - * @param callable $callback The name of the function to be called with Action hook. - * @param int $priority Optional. The priority at which the function should be fired. Default is 10. - * @param int $acceptedArgs Optional. The number of arguments that should be passed to the $callback. Default is 1. + * @param string $tag The name of the WordPress action that is being registered. + * @param callable $callback The name of the function to be called with Action hook. + * @param int $priority Optional. The priority at which the function should be fired. Default is 10. + * @param int $acceptedArgs Optional. The number of arguments that should be passed to the $callback. Default is 1. + * @param array $options Optional. Additional options for the action. */ - public function addAction(string $name, callable $callback, int $priority = 10, int $acceptedArgs = 1): void - { - $this->actions = $this->add($this->actions, $name, $callback, $priority, $acceptedArgs); + public function addAction( + string $tag, + callable $callback, + int $priority = 10, + int $acceptedArgs = 1, + array $options = [] + ): void { + add_action($tag, $callback, $priority, $acceptedArgs); + + $nativeId = $this->getNativeId($callback); + $namedId = $this->getNamedId($options) ?? $nativeId; + + $this->addRef($namedId, $nativeId, ['callback' => $callback]); + + $this->actions = $this->add($this->actions, $tag, $callback, $priority, $acceptedArgs); } /** * Add a new filter to the collection to be registered with WordPress. * - * @param string $name The name of the WordPress filter that is being registered. - * @param callable $callback The name of the function to be called with Filter hook. - * @param int $priority Optional. The priority at which the function should be fired. Default is 10. - * @param int $acceptedArgs Optional. The number of arguments that should be passed to the $callback. Default is 1. + * @param string $tag The name of the WordPress filter that is being registered. + * @param callable $callback The name of the function to be called with Filter hook. + * @param int $priority Optional. The priority at which the function should be fired. Default is 10. + * @param int $acceptedArgs Optional. The number of arguments that should be passed to the $callback. Default is 1. + * @param array $options Optional. Additional options for the action. + */ + public function addFilter( + string $tag, + callable $callback, + int $priority = 10, + int $acceptedArgs = 1, + array $options = [] + ): void { + add_filter($tag, $callback, $priority, $acceptedArgs); + + $nativeId = $this->getNativeId($callback); + $namedId = $this->getNamedId($options) ?? $nativeId; + + $this->addRef($namedId, $nativeId, ['callback' => $callback]); + + $this->filters = $this->add($this->filters, $tag, $callback, $priority, $acceptedArgs); + } + + /** + * Removes an action callback function from a specified hook. + * + * @param string $tag The name of the action hook to remove the callback from. + * @param string|callable $ref The callback or ref id to remove from the action hook. + * @param int $priority Optional. The priority of the callback function. Default is 10. */ - public function addFilter(string $name, callable $callback, int $priority = 10, int $acceptedArgs = 1): void + public function removeAction(string $tag, $ref, int $priority = 10): void { - $this->filters = $this->add($this->filters, $name, $callback, $priority, $acceptedArgs); + $callback = is_string($ref) ? $this->getCallbackFromId($ref) : $ref; + + remove_action($tag, $callback, $priority); } /** - * Add the filters and actions in WordPress. + * Removes a filter callback function from a specified hook. + * + * @param string $tag The name of the filter hook to remove the callback from. + * @param string|callable $ref The callback or ref id to remove from the filter hook. + * @param int $priority Optional. The priority of the callback function. Default is 10. */ - public function register(): void + public function removeFilter(string $tag, $ref, int $priority = 10): void { - foreach ($this->filters as $hook) { - add_filter($hook['name'], $hook['callback'], $hook['priority'], $hook['accepted_args']); - } + $callback = is_string($ref) ? $this->getCallbackFromId($ref) : $ref; - foreach ($this->actions as $hook) { - add_action($hook['name'], $hook['callback'], $hook['priority'], $hook['accepted_args']); - } + remove_filter($tag, $callback, $priority); } /** * Remove all actions and filters from WordPress. */ - public function deregister(): void + public function removeAll(): void { foreach ($this->actions as $hook) { - remove_action($hook['name'], $hook['callback'], $hook['priority']); + remove_action($hook['tag'], $hook['callback'], $hook['priority']); } foreach ($this->filters as $hook) { - remove_filter($hook['name'], $hook['callback'], $hook['priority']); + remove_filter($hook['tag'], $hook['callback'], $hook['priority']); } } @@ -98,22 +161,86 @@ public function parse(object $obj): void /** * Add a new hook (action or filter) to the collection. * - * @param array $hooks The current collection of hooks. - * @param string $name The name of the hook being registered. - * @param callable $callback The function to be called when the hook is triggered. - * @param int $priority The priority at which the function should be fired. - * @param int $acceptedArgs The number of arguments that should be passed to the callback. - * @return array + * @param array $hooks The current collection of hooks. + * @param string $tag The name of the hook being registered. + * @param callable $callback The function to be called when the hook is triggered. + * @param int $priority The priority at which the function should be fired. + * @param int $acceptedArgs The number of arguments that should be passed to the callback. + * @return array */ - private function add(array $hooks, string $name, callable $callback, int $priority, int $acceptedArgs): array + private function add(array $hooks, string $tag, callable $callback, int $priority, int $acceptedArgs): array { $hooks[] = [ 'accepted_args' => $acceptedArgs, 'callback' => $callback, - 'name' => $name, + 'tag' => $tag, 'priority' => $priority, ]; return $hooks; } + + /** @param array{callback:callable} $entry */ + private function addRef(string $id, string $nativeId, array $entry): void + { + if ($nativeId !== $id) { + $atId = '@' . $id; + + if (isset($this->refs[$atId])) { + throw new RefExistsException($atId); + } + + $this->refs[$atId] = $entry; + $this->aliases[$nativeId] = $atId; + } else { + $this->refs[$nativeId] = [ + 'callback' => $entry['callback'], + ]; + } + } + + /** @param array $options */ + private function getNamedId(array $options = []): ?string + { + if (isset($options['id']) && is_string($options['id']) && trim($options['id']) !== '') { + preg_match('/^[a-z0-9]([_.-]?[a-z0-9]+)*(\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*)?$/', $options['id'], $matches); + + if (count($matches) === 0) { + throw new InvalidArgumentException( + 'Invalid ref ID format. A ref ID should only contains letters, numbers, hyphens, dots, underscores, and backslashes.', + ); + } + + return $options['id']; + } + + return null; + } + + private function getNativeId(callable $callback): string + { + if (gettype($callback) === 'string') { + return $callback; + } + + if (is_array($callback)) { + return get_class($callback[0]) . '::' . $callback[1]; + } + + return spl_object_hash(Closure::fromCallable($callback)); + } + + /** @param string $id The callback or ref to remove from the action hook. */ + private function getCallbackFromId(string $id): callable + { + if (isset($this->aliases[$id])) { + return $this->refs[$this->aliases[$id]]['callback']; + } + + if (isset($this->refs[$id])) { + return $this->refs[$id]['callback']; + } + + throw new RefNotFoundException($id); + } } diff --git a/app/Support/Parser.php b/app/Support/Parser.php index 3bb3c59..5c6dcea 100644 --- a/app/Support/Parser.php +++ b/app/Support/Parser.php @@ -60,6 +60,7 @@ private function parseClassAttrs(): void $this->obj, $instance->getPriority(), $instance->getAcceptedArgs(), + $instance->getOptions(), ); } @@ -71,6 +72,7 @@ private function parseClassAttrs(): void $this->obj, $instance->getPriority(), $instance->getAcceptedArgs(), + $instance->getOptions(), ); } } @@ -105,6 +107,7 @@ private function parseMethodAttrs(): void $callback, $instance->getPriority(), $instance->getAcceptedArgs(), + $instance->getOptions(), ); } @@ -116,6 +119,7 @@ private function parseMethodAttrs(): void $callback, $instance->getPriority(), $instance->getAcceptedArgs(), + $instance->getOptions(), ); } } diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..32f51ab --- /dev/null +++ b/codecov.yml @@ -0,0 +1,9 @@ +coverage: + status: + project: + default: + target: 98% + threshold: 1% +comment: + layout: "condensed_header, condensed_files, condensed_footer" + hide_project_coverage: true diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4768898..ea51145 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -24,7 +24,7 @@ - ./tests/phpunit + ./tests/app diff --git a/tests/app/RegistryTest.php b/tests/app/RegistryTest.php index 1ef28e8..b3b9a60 100644 --- a/tests/app/RegistryTest.php +++ b/tests/app/RegistryTest.php @@ -5,6 +5,9 @@ namespace Syntatis\WPHook\Tests; use ArgumentCountError; +use InvalidArgumentException; +use Syntatis\WPHook\Exceptions\RefExistsException; +use Syntatis\WPHook\Exceptions\RefNotFoundException; use Syntatis\WPHook\Registry; class RegistryTest extends WPTestCase @@ -14,21 +17,10 @@ public function testAddAction(): void $func = static function (): bool { return true; }; - $hook = new Registry(); - - // No-register yet. $hook->addAction('wp', $func); - $this->assertFalse(has_action('wp', $func)); - - // Register. - $hook->addAction('init', $func); - $hook->register(); - $actual = has_action('init', $func); - $expect = 10; - - $this->assertSame($expect, $actual); + $this->assertSame(10, has_action('wp', $func)); } public function testAddActionPriority(): void @@ -36,33 +28,34 @@ public function testAddActionPriority(): void $func = static function (): bool { return true; }; - $hook = new Registry(); - $hook->addAction('init', $func, 100); - $hook->register(); - $actual = has_action('init', $func); - $expect = 100; - - $this->assertSame($expect, $actual); + $this->assertSame(100, has_action('init', $func)); } public function testAddActionAcceptedArgs(): void { $hook = new Registry(); - - $hook->addAction('auth_cookie_malformed', static function ($cookie, $scheme): void { - }, 100, 2); - $hook->register(); + $hook->addAction( + 'auth_cookie_malformed', + static function ($cookie, $scheme): void { + }, + 100, + 2, + ); do_action('auth_cookie_malformed', '123', 'auth'); - $hook->addAction('auth_cookie_malformed', static function ($cookie, $scheme): void { - }, 100); - $hook->register(); + $hook->addAction( + 'auth_cookie_malformed', + static function ($cookie, $scheme): void { + }, + 100, + ); $this->expectException(ArgumentCountError::class); + do_action('auth_cookie_malformed', '123', 'auth'); } @@ -73,19 +66,9 @@ public function testAddFilter(): void }; $hook = new Registry(); - - // No-register yet. - $hook->addFilter('the_content', $func); - $this->assertFalse(has_filter('the_content', $func)); - - // Register. $hook->addFilter('all_plugins', $func); - $hook->register(); - $actual = has_filter('all_plugins', $func); - $expect = 10; - - $this->assertSame($expect, $actual); + $this->assertSame(10, has_filter('all_plugins', $func)); } public function testAddFilterPriority(): void @@ -93,27 +76,18 @@ public function testAddFilterPriority(): void $func = static function ($value) { return $value; }; - $hook = new Registry(); - - // Register. $hook->addFilter('all_plugins', $func, 100); - $hook->register(); - - $actual = has_filter('all_plugins', $func); - $expect = 100; - $this->assertSame($expect, $actual); + $this->assertSame(100, has_filter('all_plugins', $func)); } public function testAddFilterAcceptedArgs(): void { $hook = new Registry(); - $hook->addFilter('allow_empty_comment', static function ($allowEmptyComment, $commentData) { return $allowEmptyComment; }, 100, 2); - $hook->register(); apply_filters('allow_empty_comment', false, []); @@ -121,17 +95,227 @@ public function testAddFilterAcceptedArgs(): void return $allowEmptyComment; }, 100); - $hook->register(); - $this->expectException(ArgumentCountError::class); apply_filters('allow_empty_comment', false, []); } - public function testDeregister(): void + public function testRemoveAction(): void + { + $hook = new Registry(); + $func1 = static function ($value): void { + }; + $func2 = static function ($value): void { + }; + $hook->addAction('wp', $func1, 30); + $hook->addAction('wp', $func2, 30); + + $this->assertSame(30, has_action('wp', $func1)); + $this->assertSame(30, has_action('wp', $func2)); + + $hook->removeAction('wp', $func2, 30); + + $this->assertSame(30, has_action('wp', $func1)); + $this->assertFalse(has_action('wp', $func2)); + } + + /** @group test-here */ + public function testRemoveActionNamedFunction(): void + { + $hook = new Registry(); + $hook->addAction('get_sidebar', '__return_false', 39, 1); + + $this->assertSame(39, has_action('get_sidebar', '__return_false')); + + $hook->removeAction('get_sidebar', '__return_false', 39); + + $this->assertFalse(has_action('get_sidebar', '__return_false')); + } + + public function testRemoveActionInvalidCallback(): void + { + $hook = new Registry(); + $hook->addAction('get_sidebar', '__return_true', 190); + + $this->assertSame(190, has_action('get_sidebar', '__return_true')); + + $this->expectException(RefNotFoundException::class); + + $hook->removeAction('get_sidebar', '__invalid_function__', 190); + } + + public function testRemoveActionClassMethod(): void { $hook = new Registry(); + $callback = new CallbackTest(); + $hook->addAction('admin_bar_init', [$callback, 'init'], 25); + + $this->assertSame(25, has_action('admin_bar_init', [$callback, 'init'])); + $hook->removeAction('admin_bar_init', 'Syntatis\WPHook\Tests\CallbackTest::init', 25); + + $this->assertFalse(has_action('admin_bar_init', [$callback, 'init'])); + } + + /** @group with-ref */ + public function testSetInvalidRef(): void + { + $hook = new Registry(); + $func = static fn ($value) => null; + + $this->expectException(InvalidArgumentException::class); + + $hook->addAction('wp_footer', $func, 70, 1, ['id' => '@bar']); + } + + /** @group with-ref */ + public function testRemoveActionAnonymousFunction(): void + { + $hook = new Registry(); + $func = static fn ($value) => null; + $hook->addAction('register_sidebar', $func, 50, 1, ['id' => 'bar']); + + $this->assertSame(50, has_action('register_sidebar', $func)); + + $hook->removeAction('register_sidebar', '@bar', 50); + + $this->assertFalse(has_action('register_sidebar', $func)); + + // With invalid ref. + $hook = new Registry(); + $func = static fn ($value) => null; + $hook->addAction('register_sidebar', $func, 51, 1, ['id' => 'bar']); + + $this->expectException(RefNotFoundException::class); + + $hook->removeAction('register_sidebar', '@no-bar', 50); + } + + /** @group with-ref */ + public function testRemoveActionNamedFunctionWithRef(): void + { + $hook = new Registry(); + $hook->addAction('get_sidebar', '__return_false', 39, 1, ['id' => 'false']); + + $this->assertSame(39, has_action('get_sidebar', '__return_false')); + + $hook->removeAction('get_sidebar', '__return_false', 39); + + $this->assertFalse(has_action('get_sidebar', '__return_false')); + + // Remove with ref. + $hook = new Registry(); + $hook->addAction('get_sidebar', '__return_false', 40, 1, ['id' => 'false']); + + $this->assertSame(40, has_action('get_sidebar', '__return_false')); + + $hook->removeAction('get_sidebar', '@false', 40); + + $this->assertFalse(has_action('get_sidebar', '__return_false')); + } + + /** @group with-ref */ + public function testRemoveActionNamedFunctionWithInvalidRef(): void + { + $hook = new Registry(); + $hook->addAction('get_sidebar', '__return_false', 40, 1, ['id' => 'false']); + + $this->expectException(RefNotFoundException::class); + + $hook->removeAction('get_sidebar', '@no-false', 40); + } + + /** + * @group with-ref + * @group test-here + */ + public function testRemoveActionClassMethodWithRef(): void + { + $hook = new Registry(); + $callback = new CallbackTest(); + $hook->addAction('wp_head', [$callback, 'init'], 33, 1, ['id' => 'foo']); + + $this->assertSame(33, has_action('wp_head', [$callback, 'init'])); + + $hook->removeAction('wp_head', 'Syntatis\WPHook\Tests\CallbackTest::init', 33); + + $this->assertFalse(has_action('wp_head', [$callback, 'init'])); + + // Remove with ref. + $hook = new Registry(); + $callback = new CallbackTest(); + $hook->addAction('wp_head', [$callback, 'init'], 34, 1, ['id' => 'foo']); + + $this->assertSame(34, has_action('wp_head', [$callback, 'init'])); + + $hook->removeAction('wp_head', '@foo', 34); + + $this->assertFalse(has_action('wp_head', [$callback, 'init'])); + } + + /** @group with-ref */ + public function testRemoveFilterAnonymousFunction(): void + { + $hook = new Registry(); + $func = static fn ($value) => null; + $hook->addFilter('icon_dir', $func, 10, 1, ['id' => 'body']); + + $this->assertSame(10, has_filter('icon_dir', $func)); + + $hook->removeFilter('icon_dir', '@body', 10); + + $this->assertFalse(has_filter('icon_dir', $func)); + } + + /** @group with-ref */ + public function testRemoveFilterNamedFunctionWithRef(): void + { + $hook = new Registry(); + $hook->addFilter('get_the_excerpt', '__return_empty_string', 28, 1, ['id' => 'ret-false']); + + $this->assertSame(28, has_action('get_the_excerpt', '__return_empty_string')); + + $hook->removeFilter('get_the_excerpt', '__return_empty_string', 28); + + $this->assertFalse(has_action('get_the_excerpt', '__return_empty_string')); + + // Remove with ref. + $hook = new Registry(); + $hook->addFilter('get_the_excerpt', '__return_empty_string', 200, 1, ['id' => 'ret-false']); + + $this->assertSame(200, has_action('get_the_excerpt', '__return_empty_string')); + + $hook->removeFilter('get_the_excerpt', '@ret-false', 200); + + $this->assertFalse(has_action('get_the_excerpt', '__return_empty_string')); + } + + /** @group with-ref */ + public function testRemoveFilterNamedFunctionWithInvalidRef(): void + { + $hook = new Registry(); + $hook->addFilter('get_the_archive_title', '__return_empty_string', 280, 1, ['id' => 'ret-false']); + + $this->assertSame(280, has_action('get_the_archive_title', '__return_empty_string')); + + $this->expectException(RefNotFoundException::class); + $hook->removeFilter('get_the_archive_title', '@no-ret-false', 280); + } + + /** @group with-ref */ + public function testAddRefExists(): void + { + $hook = new Registry(); + $hook->addFilter('the_content', static fn () => true, 320, 1, ['id' => 'ref-true']); + + $this->expectException(RefExistsException::class); + + $hook->addFilter('the_content_rss', static fn () => true, 320, 1, ['id' => 'ref-true']); + } + + public function testRemoveAll(): void + { + $hook = new Registry(); $func = static function ($value): void { }; $funcNative = static function ($value): void { @@ -146,7 +330,6 @@ public function testDeregister(): void $hook->addAction('init', $func); $hook->addFilter('the_content', $func); $hook->addFilter('all_plugins', $func); - $hook->register(); // Actions. $this->assertSame(10, has_action('wp', $func)); @@ -160,7 +343,8 @@ public function testDeregister(): void $this->assertSame(10, has_filter('the_content', $funcNative)); $this->assertSame(10, has_filter('all_plugins', $funcNative)); - $hook->deregister(); // These methods should de-register all actions and filters. + // These methods should de-register all actions and filters. + $hook->removeAll(); // List of actions and filters, added with `add_action` and `add_filter`. $this->assertSame(10, has_action('wp', $funcNative)); @@ -175,3 +359,15 @@ public function testDeregister(): void $this->assertFalse(has_filter('all_plugins', $func)); } } + +// phpcs:disable +class CallbackTest { + public function init(): void + { + } + + public function change(): string + { + return ''; + } +} diff --git a/tests/app/Support/ParserTest.php b/tests/app/Support/ParserTest.php index 3a4edf5..7ba9809 100644 --- a/tests/app/Support/ParserTest.php +++ b/tests/app/Support/ParserTest.php @@ -56,7 +56,6 @@ public function foo(): void $hook = new Registry(); $hasActions->hook($hook); - $hook->register(); $this->assertEquals(123, has_action('init', [$hasActions, 'foo'])); $this->assertEquals(124, has_action('init', [$hasActions, 'bar'])); @@ -96,7 +95,6 @@ public function foo(): void $hook = new Registry(); $hasFilters->hook($hook); - $hook->register(); $this->assertEquals(223, has_filter('the_content', [$hasFilters, 'foo'])); $this->assertEquals(224, has_filter('the_content', [$hasFilters, 'bar'])); @@ -119,7 +117,6 @@ public function testActionOnClass(): void $foo = new Foo(); $hook = new Registry(); $hook->parse($foo); - $hook->register(); $hooks = $GLOBALS['wp_filter']['init'][234]; $added = $hooks[array_key_first($hooks)]; @@ -133,7 +130,6 @@ public function testFilterOnClass(): void $bar = new Bar(); $hook = new Registry(); $hook->parse($bar); - $hook->register(); $hooks = $GLOBALS['wp_filter']['the_title'][432]; $added = $hooks[array_key_first($hooks)]; @@ -147,7 +143,6 @@ public function testWithConstructor(): void $instance = new WithConstructor(); $hook = new Registry(); $hook->parse($instance); - $hook->register(); $this->assertFalse(isset($GLOBALS['wp_filter']['muplugins_loaded'][100])); } @@ -157,7 +152,6 @@ public function testWithDestructor(): void $instance = new WithDestructor(); $hook = new Registry(); $hook->parse($instance); - $hook->register(); $this->assertFalse(isset($GLOBALS['wp_filter']['setup_theme'][123])); } @@ -167,7 +161,6 @@ public function testWithPrivateMethod(): void $instance = new WithPrivateMethod(); $hook = new Registry(); $hook->parse($instance); - $hook->register(); $this->assertFalse(isset($GLOBALS['wp_filter']['admin_bar_init'][99])); } @@ -177,10 +170,24 @@ public function testWithDoubleDashedMethod(): void $instance = new WithDoubleDashed(); $hook = new Registry(); $hook->parse($instance); - $hook->register(); $this->assertFalse(isset($GLOBALS['wp_filter']['wp_loaded'][345])); } + + public function testWithOptions(): void + { + $instance = new WithOptions(); + $hook = new Registry(); + $hook->parse($instance); + + $this->assertTrue(isset($GLOBALS['wp_filter']['admin_bar_init'][3210])); + $this->assertTrue(isset($GLOBALS['wp_filter']['the_content'][3210])); + + $hook->removeAction('admin_bar_init', WithOptions::class . '::bar', 3210); + + $this->assertFalse(isset($GLOBALS['wp_filter']['admin_bar_init'][3210])); + $this->assertTrue(isset($GLOBALS['wp_filter']['the_content'][3210])); + } } // phpcs:disable @@ -236,3 +243,17 @@ private function foo() } } + +class WithOptions +{ + #[Action(name: 'admin_bar_init', priority: 3210, options: ['ref' => 'bar'])] + public function bar() + { + } + + #[Filter(name: 'the_content', priority: 3210, options: ['ref' => 'content'])] + public function content() + { + return ''; + } +}