diff --git a/src/HooksTrait.php b/src/HooksTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..2318b0bf554569479dcb147433e0ee5a08e3e6fa --- /dev/null +++ b/src/HooksTrait.php @@ -0,0 +1,114 @@ +<?php +declare( strict_types=1 ); + +namespace WPDesk\Init; + +/** + * Hooks trait. + * + * Allows protected and private methods to be used as hook callbacks in PHP <8.1. Since PHP 8.1 + * you are able to take advantage of first class callable and register private methods in hooks + * without any workarounds. + * + * @author John P. Bloch + * @link https://github.com/johnpbloch/wordpress-dev/blob/master/src/Hooks.php + */ +trait HooksTrait { + + /** + * Add a WordPress filter. + * + * @param callable $method + * + * @return true + */ + protected function add_filter( + string $hook, + $method, + int $priority = 10, + int $arg_count = 1 + ): bool { + return add_filter( + $hook, + $this->map_filter( $method, $arg_count ), + $priority, + $arg_count + ); + } + + /** + * Add a WordPress action. + * + * This is an alias of add_filter(). + * + * @param callable $method + * + * @return true + */ + protected function add_action( string $hook, $method, int $priority = 10, int $arg_count = 1 ): bool { + return $this->add_filter( $hook, $method, $priority, $arg_count ); + } + + /** + * Remove a WordPress filter. + * + * @param callable $method + * + * @return bool Whether the function existed before it was removed. + */ + protected function remove_filter( + string $hook, + $method, + int $priority = 10, + int $arg_count = 1 + ): bool { + return remove_filter( + $hook, + $this->map_filter( $method, $arg_count ), + $priority + ); + } + + /** + * Remove a WordPress action. + * + * This is an alias of remove_filter(). + * + * @param callable $method + * + * @return bool Whether the function is removed. + */ + protected function remove_action( + string $hook, + $method, + int $priority = 10, + int $arg_count = 1 + ): bool { + return $this->remove_filter( $hook, $method, $priority, $arg_count ); + } + + /** + * Map a filter to a closure that inherits the class' internal scope. + * + * This allows hooks to use protected and private methods. + * + * @param string $callable + * @param int $arg_count + * + * @return \Closure The callable actually attached to a WP hook + */ + private function map_filter( $callable, int $arg_count ): \Closure { + if ( is_string( $callable ) && method_exists( $this, $callable ) ) { + $object = $this; + $method = $callable; + } + + if ( is_array( $callable ) ) { + [ $object, $method ] = $callable; + } + + return static function () use ( $object, $method, $arg_count ) { + return $object->{$method}( ...array_slice( func_get_args(), 0, $arg_count ) ); + }; + } +} diff --git a/tests/HooksTraitTest.php b/tests/HooksTraitTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b9ba4d053249a9aefb791a32cdd180baf8cd8573 --- /dev/null +++ b/tests/HooksTraitTest.php @@ -0,0 +1,19 @@ +<?php +declare( strict_types=1 ); + +namespace WPDesk\Init\Tests; + +class HooksTraitTest extends \PHPUnit\Framework\TestCase { + + /** + * How you can call a hook: + * 1. callable string: '__return_true' + * 2. closure: function () { echo "hello"; } + * 3. static call: [stdClass::class, 'hello'] + * 4. class instance call: [ $this, 'hello' ] + * 5. invokable object: $this + * ! Since PHP 8.1 + * 6. first-class callable: __return_true(...), $this->hello(...) + */ + +} \ No newline at end of file