diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 00000000..4f71cc92 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,12 @@ +parameters: + level: 6 + paths: + - src + - profile-command.php + scanDirectories: + - vendor/wp-cli/wp-cli/php + scanFiles: + - vendor/php-stubs/wordpress-stubs/wordpress-stubs.php + - tests/phpstan/scan-files.php + + treatPhpDocTypesAsCertain: false diff --git a/src/Command.php b/src/Command.php index 8727938e..de65d4ad 100644 --- a/src/Command.php +++ b/src/Command.php @@ -129,6 +129,10 @@ class Command { * * @skipglobalargcheck * @when before_wp_load + * + * @param array $args + * @param array $assoc_args + * @return void */ public function stage( $args, $assoc_args ) { global $wpdb; @@ -257,6 +261,10 @@ public function stage( $args, $assoc_args ) { * * @skipglobalargcheck * @when before_wp_load + * + * @param array $args + * @param array $assoc_args + * @return void */ public function hook( $args, $assoc_args ) { @@ -357,6 +365,10 @@ public function hook( $args, $assoc_args ) { * | 0.1009s | 100% | 1 | * +---------+-------------+---------------+ * + * @param array $args + * @param array $assoc_args + * @return void + * * @subcommand eval */ public function eval_( $args, $assoc_args ) { @@ -426,6 +438,10 @@ function () use ( $statement ) { * | 0.1009s | 100% | 1 | * +---------+-------------+---------------+ * + * @param array $args + * @param array $assoc_args + * @return void + * * @subcommand eval-file */ public function eval_file( $args, $assoc_args ) { @@ -451,6 +467,12 @@ function () use ( $file ) { /** * Profile an eval or eval-file statement. + * + * @param array $assoc_args + * @param callable $profile_callback + * @param string $order + * @param string|null $orderby + * @return void */ private static function profile_eval_ish( $assoc_args, $profile_callback, $order = 'ASC', $orderby = null ) { $hook = Utils\get_flag_value( $assoc_args, 'hook' ); @@ -500,6 +522,7 @@ private static function profile_eval_ish( $assoc_args, $profile_callback, $order * Include a file without exposing it to current scope * * @param string $file + * @return void */ private static function include_file( $file ) { include $file; @@ -508,9 +531,9 @@ private static function include_file( $file ) { /** * Filter loggers with zero-ish values. * - * @param array $loggers - * @param array $metrics - * @return array + * @param array<\WP_CLI\Profile\Logger> $loggers + * @param array $metrics + * @return array<\WP_CLI\Profile\Logger> */ private static function shine_spotlight( $loggers, $metrics ) { @@ -550,9 +573,9 @@ private static function shine_spotlight( $loggers, $metrics ) { /** * Filter loggers to only those whose callback name matches a pattern. * - * @param array $loggers - * @param string $pattern - * @return array + * @param array<\WP_CLI\Profile\Logger> $loggers + * @param string $pattern + * @return array<\WP_CLI\Profile\Logger> */ private static function filter_by_callback( $loggers, $pattern ) { return array_filter( diff --git a/src/Formatter.php b/src/Formatter.php index 775db154..492118b8 100644 --- a/src/Formatter.php +++ b/src/Formatter.php @@ -4,12 +4,28 @@ class Formatter { + /** + * @var \WP_CLI\Formatter + */ private $formatter; + /** + * @var array + */ private $args; + /** + * @var int|null + */ private $total_cell_index; + /** + * Formatter constructor. + * + * @param array $assoc_args + * @param array|null $fields + * @param string|bool $prefix + */ public function __construct( &$assoc_args, $fields = null, $prefix = false ) { $format_args = array( 'format' => 'table', @@ -51,7 +67,11 @@ public function __construct( &$assoc_args, $fields = null, $prefix = false ) { /** * Display multiple items according to the output arguments. * - * @param array $items + * @param array<\WP_CLI\Profile\Logger> $items + * @param bool $include_total + * @param string $order + * @param string|null $orderby + * @return void */ public function display_items( $items, $include_total, $order, $orderby ) { if ( 'table' === $this->args['format'] && empty( $this->args['field'] ) ) { @@ -64,13 +84,14 @@ public function display_items( $items, $include_total, $order, $orderby ) { /** * Function to compare floats. * - * @param double $a Floating number. - * @param double $b Floating number. + * @param float $a Floating number. + * @param float $b Floating number. + * @return int */ private function compare_float( $a, $b ) { - $a = number_format( $a, 4 ); - $b = number_format( $b, 4 ); - if ( 0 === $a - $b ) { + $a = round( $a, 4 ); + $b = round( $b, 4 ); + if ( 0.0 === $a - $b ) { return 0; } elseif ( $a - $b < 0 ) { return -1; @@ -82,8 +103,12 @@ private function compare_float( $a, $b ) { /** * Show items in a \cli\Table. * - * @param array $items - * @param array $fields + * @param string $order + * @param string|null $orderby + * @param array<\WP_CLI\Profile\Logger> $items + * @param array $fields + * @param bool $include_total + * @return void */ private function show_table( $order, $orderby, $items, $fields, $include_total ) { $table = new \cli\Table(); diff --git a/src/Logger.php b/src/Logger.php index 0c3ab116..f6e53ecb 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -2,36 +2,72 @@ namespace WP_CLI\Profile; +/** + * Logger class. + * + * @property string $callback + * @property string $location + */ class Logger { - public $time = 0; - public $query_count = 0; - public $query_time = 0; - public $cache_hits = 0; - public $cache_misses = 0; - public $cache_ratio = null; - public $hook_count = 0; - public $hook_time = 0; - public $request_count = 0; - public $request_time = 0; - private $start_time = null; - private $query_offset = null; - private $cache_hit_offset = null; - private $cache_miss_offset = null; - private $hook_start_time = null; - private $hook_depth = 0; + /** @var float */ + public $time = 0; + /** @var int */ + public $query_count = 0; + /** @var float */ + public $query_time = 0; + /** @var int */ + public $cache_hits = 0; + /** @var int */ + public $cache_misses = 0; + /** @var string|null */ + public $cache_ratio = null; + /** @var int */ + public $hook_count = 0; + /** @var float */ + public $hook_time = 0; + /** @var int */ + public $request_count = 0; + /** @var float */ + public $request_time = 0; + /** @var float|null */ + private $start_time = null; + /** @var int|null */ + private $query_offset = null; + /** @var int|null */ + private $cache_hit_offset = null; + /** @var int|null */ + private $cache_miss_offset = null; + /** @var float|null */ + private $hook_start_time = null; + /** @var int */ + private $hook_depth = 0; + /** @var float|null */ private $request_start_time = null; + /** @var array */ private $definitions = array(); + /** @var array<\WP_CLI\Profile\Logger> */ public static $active_loggers = array(); + /** + * Logger constructor. + * + * @param array $definition + */ public function __construct( $definition = array() ) { foreach ( $definition as $k => $v ) { $this->definitions[ $k ] = $v; } } + /** + * Magic getter for definitions. + * + * @param string $key + * @return mixed + */ public function __get( $key ) { if ( isset( $this->definitions[ $key ] ) ) { return $this->definitions[ $key ]; @@ -40,16 +76,31 @@ public function __get( $key ) { return null; } + /** + * Magic setter for definitions. + * + * @param string $key + * @param mixed $value + * @return void + */ public function __set( $key, $value ) { $this->definitions[ $key ] = $value; } + /** + * Magic isset for definitions. + * + * @param string $key + * @return bool + */ public function __isset( $key ) { return isset( $this->definitions[ $key ] ); } /** * Start this logger + * + * @return void */ public function start() { global $wpdb, $wp_object_cache; @@ -66,6 +117,8 @@ public function start() { /** * Whether or not the logger is running + * + * @return bool */ public function running() { return ! is_null( $this->start_time ); @@ -73,6 +126,8 @@ public function running() { /** * Stop this logger + * + * @return void */ public function stop() { global $wpdb, $wp_object_cache; @@ -115,6 +170,8 @@ public function stop() { /** * Start this logger's hook timer + * + * @return void */ public function start_hook_timer() { ++$this->hook_count; @@ -128,6 +185,8 @@ public function start_hook_timer() { /** * Stop this logger's hook timer + * + * @return void */ public function stop_hook_timer() { if ( $this->hook_depth ) { @@ -142,6 +201,8 @@ public function stop_hook_timer() { /** * Start this logger's request timer + * + * @return void */ public function start_request_timer() { ++$this->request_count; @@ -150,6 +211,8 @@ public function start_request_timer() { /** * Stop this logger's request timer + * + * @return void */ public function stop_request_timer() { if ( ! is_null( $this->request_start_time ) ) { diff --git a/src/Profiler.php b/src/Profiler.php index 2658441f..f5465a9b 100644 --- a/src/Profiler.php +++ b/src/Profiler.php @@ -6,9 +6,13 @@ class Profiler { + /** @var string */ private $type; + /** @var string|bool|null */ private $focus; - private $loggers = array(); + /** @var array> */ + private $loggers = array(); + /** @var array> */ private $stage_hooks = array( 'bootstrap' => array( 'muplugins_loaded', @@ -35,26 +39,49 @@ class Profiler { ), ); - private $current_stage_hooks = array(); - private $running_hook = null; - private $previous_filter = null; + /** @var array */ + private $current_stage_hooks = array(); + /** @var string|null */ + private $running_hook = null; + /** @var string|null */ + private $previous_filter = null; + /** @var array|null */ private $previous_filter_callbacks = null; - private $filter_depth = 0; - - private $tick_callback = null; - private $tick_location = null; - private $tick_start_time = null; - private $tick_query_offset = null; - private $tick_cache_hit_offset = null; + /** @var int */ + private $filter_depth = 0; + + /** @var string|null */ + private $tick_callback = null; + /** @var string|null */ + private $tick_location = null; + /** @var float|null */ + private $tick_start_time = null; + /** @var int|null */ + private $tick_query_offset = null; + /** @var int|null */ + private $tick_cache_hit_offset = null; + /** @var int|null */ private $tick_cache_miss_offset = null; + /** @var bool */ private $is_admin_request = false; + /** + * Profiler constructor. + * + * @param string $type + * @param string|bool|null $focus + */ public function __construct( $type, $focus ) { $this->type = $type; $this->focus = $focus; } + /** + * Get the loggers. + * + * @return array<\WP_CLI\Profile\Logger> + */ public function get_loggers() { foreach ( $this->loggers as $i => $logger ) { if ( is_array( $logger ) ) { @@ -77,6 +104,8 @@ public function get_loggers() { /** * Run the profiler against WordPress + * + * @return void */ public function run() { $url = WP_CLI::get_runner()->config['url']; @@ -153,6 +182,9 @@ function () { /** * Start profiling function calls on the end of this filter + * + * @param mixed $value + * @return mixed */ public function wp_tick_profile_begin( $value = null ) { @@ -189,6 +221,9 @@ public function wp_tick_profile_begin( $value = null ) { /** * Stop profiling function calls at the beginning of this filter + * + * @param mixed $value + * @return mixed */ public function wp_tick_profile_end( $value = null ) { unregister_tick_function( array( $this, 'handle_function_tick' ) ); @@ -198,6 +233,8 @@ public function wp_tick_profile_end( $value = null ) { /** * Profiling verbosity at the beginning of every action and filter + * + * @return void */ public function wp_hook_begin() { @@ -247,6 +284,9 @@ public function wp_hook_begin() { /** * Wrap current filter callbacks with a timer + * + * @param string $current_filter + * @return void */ private function wrap_current_filter_callbacks( $current_filter ) { @@ -282,6 +322,9 @@ private function wrap_current_filter_callbacks( $current_filter ) { /** * Profiling verbosity at the end of every action and filter + * + * @param mixed $filter_value + * @return mixed */ public function wp_hook_end( $filter_value = null ) { @@ -313,6 +356,8 @@ public function wp_hook_end( $filter_value = null ) { /** * Handle the tick of a function + * + * @return void */ public function handle_function_tick() { global $wpdb, $wp_object_cache; @@ -399,6 +444,9 @@ public function handle_function_tick() { /** * Profiling request time for any active Loggers + * + * @param mixed $filter_value + * @return mixed */ public function wp_request_begin( $filter_value = null ) { foreach ( Logger::$active_loggers as $logger ) { @@ -409,6 +457,9 @@ public function wp_request_begin( $filter_value = null ) { /** * Profiling request time for any active Loggers + * + * @param mixed $filter_value + * @return mixed */ public function wp_request_end( $filter_value = null ) { foreach ( Logger::$active_loggers as $logger ) { @@ -419,6 +470,8 @@ public function wp_request_end( $filter_value = null ) { /** * Runs through the entirety of the WP bootstrap process + * + * @return void */ private function load_wordpress_with_template() { @@ -439,8 +492,8 @@ private function load_wordpress_with_template() { if ( 'bootstrap' === $this->focus ) { $this->set_stage_hooks( $this->stage_hooks['bootstrap'] ); } elseif ( ! $this->focus ) { - $logger = new Logger( array( 'stage' => 'bootstrap' ) ); - $logger->start(); + $bootstrap_logger = new Logger( array( 'stage' => 'bootstrap' ) ); + $bootstrap_logger->start(); } } WP_CLI::get_runner()->load_wordpress(); @@ -451,9 +504,9 @@ private function load_wordpress_with_template() { if ( 'hook' === $this->type && 'wp_loaded:after' === $this->focus ) { $this->wp_tick_profile_end(); } - if ( 'stage' === $this->type && ! $this->focus ) { - $logger->stop(); - $this->loggers[] = $logger; + if ( 'stage' === $this->type && ! $this->focus && isset( $bootstrap_logger ) ) { + $bootstrap_logger->stop(); + $this->loggers[] = $bootstrap_logger; } // Skip main_query and template stages for admin requests. @@ -466,8 +519,8 @@ private function load_wordpress_with_template() { if ( 'main_query' === $this->focus ) { $this->set_stage_hooks( $this->stage_hooks['main_query'] ); } elseif ( ! $this->focus ) { - $logger = new Logger( array( 'stage' => 'main_query' ) ); - $logger->start(); + $main_query_logger = new Logger( array( 'stage' => 'main_query' ) ); + $main_query_logger->start(); } } wp(); @@ -478,9 +531,9 @@ private function load_wordpress_with_template() { if ( 'hook' === $this->type && 'wp:after' === $this->focus ) { $this->wp_tick_profile_end(); } - if ( 'stage' === $this->type && ! $this->focus ) { - $logger->stop(); - $this->loggers[] = $logger; + if ( 'stage' === $this->type && ! $this->focus && isset( $main_query_logger ) ) { + $main_query_logger->stop(); + $this->loggers[] = $main_query_logger; } define( 'WP_USE_THEMES', true ); @@ -496,8 +549,8 @@ private function load_wordpress_with_template() { if ( 'template' === $this->focus ) { $this->set_stage_hooks( $this->stage_hooks['template'] ); } elseif ( ! $this->focus ) { - $logger = new Logger( array( 'stage' => 'template' ) ); - $logger->start(); + $template_logger = new Logger( array( 'stage' => 'template' ) ); + $template_logger->start(); } } ob_start(); @@ -510,14 +563,17 @@ private function load_wordpress_with_template() { if ( 'hook' === $this->type && 'wp_footer:after' === $this->focus ) { $this->wp_tick_profile_end(); } - if ( 'stage' === $this->type && ! $this->focus ) { - $logger->stop(); - $this->loggers[] = $logger; + if ( 'stage' === $this->type && ! $this->focus && isset( $template_logger ) ) { + $template_logger->stop(); + $this->loggers[] = $template_logger; } } /** * Get a human-readable name from a callback + * + * @param mixed $callback + * @return array{0: string, 1: string} */ private static function get_name_location_from_callback( $callback ) { $location = ''; @@ -566,6 +622,9 @@ private static function get_short_location( $location ) { /** * Set the hooks for the current stage + * + * @param array $hooks + * @return void */ private function set_stage_hooks( $hooks ) { $this->current_stage_hooks = $hooks; @@ -577,8 +636,8 @@ private function set_stage_hooks( $hooks ) { /** * Get the callbacks for a given filter * - * @param string - * @return array|false + * @param string $filter + * @return array|false */ private static function get_filter_callbacks( $filter ) { global $wp_filter; @@ -602,7 +661,8 @@ private static function get_filter_callbacks( $filter ) { * Set the callbacks for a given filter * * @param string $filter - * @param mixed $callbacks + * @param mixed $callbacks + * @return void */ private static function set_filter_callbacks( $filter, $callbacks ) { global $wp_filter; diff --git a/tests/phpstan/scan-files.php b/tests/phpstan/scan-files.php new file mode 100644 index 00000000..81f3d91b --- /dev/null +++ b/tests/phpstan/scan-files.php @@ -0,0 +1,6 @@ +