diff --git a/src/Formatter/Hook_Formatter.php b/src/Formatter/Hook_Formatter.php index 15876a4..34f2fb6 100644 --- a/src/Formatter/Hook_Formatter.php +++ b/src/Formatter/Hook_Formatter.php @@ -3,38 +3,56 @@ declare( strict_types=1 ); namespace Rarst\Laps\Formatter; +use WP_Hook; + /** - * Formatter for WordPress hook callbacks. + * Formatter to process callbacks from a hook instance. */ class Hook_Formatter { - /** @var array $truncate_paths List of base paths to strip for readability. */ - protected $truncate_paths; + /** @var array $truncate_paths Paths to truncate from includes. */ + protected $truncate_paths = []; /** - * Set up truncate paths. + * Set up object properties. */ public function __construct() { - $this->truncate_paths = array_filter( [ - wp_normalize_path( ABSPATH ), + // TODO inject paths instead of reaching for global constants. + $this->truncate_paths = [ wp_normalize_path( WP_CONTENT_DIR ), - ] ); + wp_normalize_path( ABSPATH ), + 'wp-admin/', + ]; } /** - * Convert hook callbacks array to a list of readable names. + * @psalm-type Callback = string|object|array + * @psalm-type AcceptedArgs = int + * @psalm-type Hook = array{function: Callback, accepted_args: AcceptedArgs} + * @psalm-type Priority = array-key + * @psalm-type Hooks = array> * - * @param array $hook Array of callbacks, registered for the hook. + * @param WP_Hook|array $hook Hook instance or callbacks array. + * @psalm-param WP_Hook|Hooks $hook * * @return array */ - public function format( array $hook ): array { + public function format( $hook ): array { + + $callbacks = []; + + if ( $hook instanceof WP_Hook ) { + /** @psalm-var Hooks $hook->callbacks */ + $hook = $hook->callbacks; + } if ( empty( $hook ) ) { - return []; + return $callbacks; } + ksort( $hook ); + /** * Empty priorities produce empty arrays in PHP 7.2 and unpack operator doesn't accept empty array in 7.3. * @@ -44,36 +62,48 @@ class Hook_Formatter { $hook = array_filter( $hook ); if ( empty( $hook ) ) { - return []; + return $callbacks; } - /** @var array $functions */ + /** @psalm-suppress InvalidArgument */ $functions = array_merge( ...$hook ); - return array_map( [ $this, 'format_callback' ], $functions ); + /** @psalm-var Hook[] $functions */ + foreach ( $functions as $function ) { + /** @psalm-var Hook $function */ + $callback = $this->get_callback_name( $function['function'], $function['accepted_args'] ); + + if ( false !== strpos( $callback, 'Hook_Collector' ) ) { + continue; + } + + $callbacks[] = $callback; + } + + return $callbacks; } /** - * Convert callback data array to a readable name. - * - * @param array $callback_data Callback data, registered for the hook. - * - * @return string + * @param string|object|array $callback Hook callback. + * @param int $args Number of accepted arguments. */ - protected function format_callback( array $callback_data ): string { + protected function get_callback_name( $callback, int $args ): string { - $callback = $callback_data['function']; - $args = $callback_data['accepted_args']; + switch ( gettype( $callback ) ) { + case 'object': + $name = $this->get_class_name( $callback ); + break; - if ( is_string( $callback ) ) { - $name = $callback; - } elseif ( is_array( $callback ) ) { - $class = is_object( $callback[0] ) ? $this->get_class_name( $callback[0] ) : $callback[0]; - $name = $class . '::' . $callback[1]; - } elseif ( is_object( $callback ) ) { - $name = $this->get_class_name( $callback ); - } else { - $name = 'unknown'; + case 'array': + /** @psalm-var array{0: object|string, 1: string} $callback */ + $class = is_string( $callback[0] ) + ? $callback[0] . '::' + : $this->get_class_name( $callback[0] ) . '->'; + $name = $class . $callback[1]; + break; + + default: + $name = $callback; } return $name . ( ( 1 === $args ) ? '' : "({$args})" ); @@ -118,14 +148,11 @@ class Hook_Formatter { * @return string */ protected function shorten_path( string $path ): string { - $path = wp_normalize_path( $path ); $path = str_replace( $this->truncate_paths, '', $path ); - - if ( 0 === strpos( $path, './' ) ) { + if ( isset( $path[1] ) && ':' === $path[1] ) { $path = substr( $path, 2 ); } - $path = ltrim( $path, '/' ); return $path; diff --git a/tests/Formatter/HookTest.php b/tests/Formatter/HookTest.php index 7843ff0..ab9216e 100644 --- a/tests/Formatter/HookTest.php +++ b/tests/Formatter/HookTest.php @@ -58,5 +58,26 @@ class HookTest extends LapsTestCase { $this->assertEquals( [], $formatter->format( [] ) ); } -} + /** + * @depends test__construct + */ + public function testFormatWpHook( Hook_Formatter $formatter ) { + + if ( ! class_exists( 'WP_Hook' ) ) { + eval( 'class WP_Hook { public $callbacks = []; }' ); + } + + $wp_hook = new \WP_Hook(); + $wp_hook->callbacks = [ + 10 => [ + [ 'function' => 'trim', 'accepted_args' => 1 ], + ], + ]; + + $result = $formatter->format( $wp_hook ); + + $this->assertIsArray( $result ); + $this->assertEquals( [ 'trim' ], $result ); + } +}