Skip to content

Commit 7c99e0e

Browse files
committed
Level 9
1 parent 7aae9f2 commit 7c99e0e

4 files changed

Lines changed: 163 additions & 72 deletions

File tree

phpstan.neon.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
parameters:
2-
level: 8
2+
level: 9
33
paths:
44
- src
55
- profile-command.php

src/Command.php

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -137,17 +137,20 @@ class Command {
137137
public function stage( $args, $assoc_args ) {
138138
global $wpdb;
139139

140-
$focus = Utils\get_flag_value( $assoc_args, 'all', isset( $args[0] ) ? $args[0] : null );
140+
/** @var array<string, bool|string> $typed_assoc_args */
141+
$typed_assoc_args = self::get_typed_assoc_args( $assoc_args );
142+
$focus = Utils\get_flag_value( $typed_assoc_args, 'all', isset( $args[0] ) ? $args[0] : null );
141143

142-
$order = Utils\get_flag_value( $assoc_args, 'order', 'ASC' );
143-
$orderby = Utils\get_flag_value( $assoc_args, 'orderby', null );
144+
$order_val = Utils\get_flag_value( $typed_assoc_args, 'order', 'ASC' );
145+
$order = is_string( $order_val ) ? $order_val : 'ASC';
146+
$orderby_val = Utils\get_flag_value( $typed_assoc_args, 'orderby', null );
147+
$orderby = ( is_string( $orderby_val ) || is_null( $orderby_val ) ) ? $orderby_val : null;
144148

145149
$valid_stages = array( 'bootstrap', 'main_query', 'template' );
146150
if ( $focus && ( true !== $focus && ! in_array( $focus, $valid_stages, true ) ) ) {
147151
WP_CLI::error( 'Invalid stage. Must be one of ' . implode( ', ', $valid_stages ) . ', or use --all.' );
148152
}
149153

150-
assert( is_bool( $focus ) || is_string( $focus ) || is_null( $focus ) );
151154
$profiler = new Profiler( 'stage', $focus );
152155
$profiler->run();
153156

@@ -184,9 +187,10 @@ public function stage( $args, $assoc_args ) {
184187
);
185188
}
186189
$fields = array_merge( $base, $metrics );
187-
$formatter = new Formatter( $assoc_args, $fields );
190+
$formatter = new Formatter( $typed_assoc_args, $fields );
188191
$loggers = $profiler->get_loggers();
189-
if ( Utils\get_flag_value( $assoc_args, 'spotlight' ) ) {
192+
/** @var array<string, bool|string> $typed_assoc_args */
193+
if ( Utils\get_flag_value( $typed_assoc_args, 'spotlight' ) ) {
190194
$loggers = self::shine_spotlight( $loggers, $metrics );
191195
}
192196

@@ -269,10 +273,14 @@ public function stage( $args, $assoc_args ) {
269273
*/
270274
public function hook( $args, $assoc_args ) {
271275

272-
$focus = Utils\get_flag_value( $assoc_args, 'all', isset( $args[0] ) ? $args[0] : null );
276+
/** @var array<string, bool|string> $typed_assoc_args */
277+
$typed_assoc_args = self::get_typed_assoc_args( $assoc_args );
278+
$focus = Utils\get_flag_value( $typed_assoc_args, 'all', isset( $args[0] ) ? $args[0] : null );
273279

274-
$order = Utils\get_flag_value( $assoc_args, 'order', 'ASC' );
275-
$orderby = Utils\get_flag_value( $assoc_args, 'orderby', null );
280+
$order_val = Utils\get_flag_value( $typed_assoc_args, 'order', 'ASC' );
281+
$order = is_string( $order_val ) ? $order_val : 'ASC';
282+
$orderby_val = Utils\get_flag_value( $typed_assoc_args, 'orderby', null );
283+
$orderby = ( is_string( $orderby_val ) || is_null( $orderby_val ) ) ? $orderby_val : null;
276284

277285
$profiler = new Profiler( 'hook', $focus );
278286
$profiler->run();
@@ -300,13 +308,16 @@ public function hook( $args, $assoc_args ) {
300308
'request_count',
301309
);
302310
$fields = array_merge( $base, $metrics );
303-
$formatter = new Formatter( $assoc_args, $fields );
311+
$formatter = new Formatter( $typed_assoc_args, $fields );
304312
$loggers = $profiler->get_loggers();
305-
if ( Utils\get_flag_value( $assoc_args, 'spotlight' ) ) {
313+
/** @var array<string, bool|string> $typed_assoc_args */
314+
if ( Utils\get_flag_value( $typed_assoc_args, 'spotlight' ) ) {
306315
$loggers = self::shine_spotlight( $loggers, $metrics );
307316
}
308-
$search = Utils\get_flag_value( $assoc_args, 'search', false );
309-
if ( false !== $search && '' !== $search ) {
317+
/** @var array<string, bool|string> $typed_assoc_args */
318+
$search_val = Utils\get_flag_value( $typed_assoc_args, 'search', '' );
319+
$search = is_string( $search_val ) ? $search_val : '';
320+
if ( '' !== $search ) {
310321
if ( ! $focus ) {
311322
WP_CLI::error( '--search requires --all or a specific hook.' );
312323
}
@@ -375,11 +386,15 @@ public function hook( $args, $assoc_args ) {
375386
public function eval_( $args, $assoc_args ) {
376387
$statement = $args[0];
377388

378-
$order = Utils\get_flag_value( $assoc_args, 'order', 'ASC' );
379-
$orderby = Utils\get_flag_value( $assoc_args, 'orderby', null );
389+
/** @var array<string, bool|string> $typed_assoc_args */
390+
$typed_assoc_args = self::get_typed_assoc_args( $assoc_args );
391+
$order_val = Utils\get_flag_value( $typed_assoc_args, 'order', 'ASC' );
392+
$order = is_string( $order_val ) ? $order_val : 'ASC';
393+
$orderby_val = Utils\get_flag_value( $typed_assoc_args, 'orderby', null );
394+
$orderby = ( is_string( $orderby_val ) || is_null( $orderby_val ) ) ? $orderby_val : null;
380395

381396
self::profile_eval_ish(
382-
$assoc_args,
397+
$typed_assoc_args,
383398
function () use ( $statement ) {
384399
eval( $statement ); // phpcs:ignore Squiz.PHP.Eval.Discouraged -- no other way around here
385400
},
@@ -449,15 +464,20 @@ public function eval_file( $args, $assoc_args ) {
449464

450465
$file = $args[0];
451466

452-
$order = Utils\get_flag_value( $assoc_args, 'order', 'ASC' );
453-
$orderby = Utils\get_flag_value( $assoc_args, 'orderby', null );
467+
/** @var array<string, bool|string> $typed_assoc_args */
468+
/** @var array<string, bool|string> $typed_assoc_args */
469+
$typed_assoc_args = self::get_typed_assoc_args( $assoc_args );
470+
$order_val = Utils\get_flag_value( $typed_assoc_args, 'order', 'ASC' );
471+
$order = is_string( $order_val ) ? $order_val : 'ASC';
472+
$orderby_val = Utils\get_flag_value( $typed_assoc_args, 'orderby', null );
473+
$orderby = ( is_string( $orderby_val ) || is_null( $orderby_val ) ) ? $orderby_val : null;
454474

455475
if ( ! file_exists( $file ) ) {
456476
WP_CLI::error( "'$file' does not exist." );
457477
}
458478

459479
self::profile_eval_ish(
460-
$assoc_args,
480+
$typed_assoc_args,
461481
function () use ( $file ) {
462482
self::include_file( $file );
463483
},
@@ -476,10 +496,12 @@ function () use ( $file ) {
476496
* @return void
477497
*/
478498
private static function profile_eval_ish( $assoc_args, $profile_callback, $order = 'ASC', $orderby = null ) {
479-
$hook = Utils\get_flag_value( $assoc_args, 'hook' );
480-
$focus = false;
481-
$type = false;
482-
$fields = array();
499+
/** @var array<string, bool|string> $typed_assoc_args */
500+
$typed_assoc_args = self::get_typed_assoc_args( $assoc_args );
501+
$hook = Utils\get_flag_value( $typed_assoc_args, 'hook' );
502+
$focus = false;
503+
$type = false;
504+
$fields = array();
483505
if ( $hook ) {
484506
$type = 'hook';
485507
if ( true !== $hook ) {
@@ -515,7 +537,7 @@ private static function profile_eval_ish( $assoc_args, $profile_callback, $order
515537
'request_count',
516538
)
517539
);
518-
$formatter = new Formatter( $assoc_args, $fields );
540+
$formatter = new Formatter( $typed_assoc_args, $fields );
519541
$formatter->display_items( $loggers, false, $order, $orderby );
520542
}
521543

@@ -586,4 +608,20 @@ function ( $logger ) use ( $pattern ) {
586608
}
587609
);
588610
}
611+
612+
/**
613+
* Get typed assoc args for get_flag_value.
614+
*
615+
* @param array<string, mixed> $assoc_args
616+
* @return array<string, bool|string>
617+
*/
618+
private static function get_typed_assoc_args( $assoc_args ) {
619+
$typed = array();
620+
foreach ( $assoc_args as $k => $v ) {
621+
if ( is_bool( $v ) || is_string( $v ) ) {
622+
$typed[ $k ] = $v;
623+
}
624+
}
625+
return $typed;
626+
}
589627
}

src/Formatter.php

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,19 @@ public function __construct( &$assoc_args, $fields = null, $prefix = false ) {
4343
}
4444

4545
if ( ! is_array( $format_args['fields'] ) ) {
46-
$format_args['fields'] = explode( ',', $format_args['fields'] );
46+
$fields_val = $format_args['fields'];
47+
$fields_str = is_scalar( $fields_val ) ? (string) $fields_val : '';
48+
$format_args['fields'] = explode( ',', $fields_str );
4749
}
4850

49-
$format_args['fields'] = array_filter( array_map( 'trim', $format_args['fields'] ) );
51+
$format_args['fields'] = array_filter(
52+
array_map(
53+
function ( $val ) {
54+
return trim( is_scalar( $val ) ? (string) $val : '' );
55+
},
56+
$format_args['fields']
57+
)
58+
);
5059

5160
if ( isset( $assoc_args['fields'] ) ) {
5261
if ( empty( $format_args['fields'] ) ) {
@@ -78,7 +87,9 @@ public function __construct( &$assoc_args, $fields = null, $prefix = false ) {
7887
*/
7988
public function display_items( $items, $include_total, $order, $orderby ) {
8089
if ( 'table' === $this->args['format'] && empty( $this->args['field'] ) ) {
81-
$this->show_table( $order, $orderby, $items, $this->args['fields'], $include_total );
90+
/** @var array<string> $fields */
91+
$fields = $this->args['fields'];
92+
$this->show_table( $order, $orderby, $items, $fields, $include_total );
8293
} else {
8394
$this->formatter->display_items( $items );
8495
}
@@ -171,10 +182,13 @@ function ( $a, $b ) use ( $order, $orderby ) {
171182
$totals[ $i ][] = $value;
172183
}
173184
} else {
174-
$totals[ $i ] += $value;
185+
$current_total = is_numeric( $totals[ $i ] ) ? $totals[ $i ] : 0;
186+
$add_value = is_numeric( $value ) ? $value : 0;
187+
$totals[ $i ] = $current_total + $add_value;
175188
}
176189
if ( stripos( $fields[ $i ], '_time' ) || 'time' === $fields[ $i ] ) {
177-
$values[ $i ] = round( $value, 4 ) . 's';
190+
$value_num = is_numeric( $value ) ? (float) $value : 0.0;
191+
$values[ $i ] = round( $value_num, 4 ) . 's';
178192
}
179193
}
180194
$table->addRow( $values );
@@ -190,7 +204,13 @@ function ( $a, $b ) use ( $order, $orderby ) {
190204
}
191205
if ( is_array( $value ) ) {
192206
if ( ! empty( $value ) ) {
193-
$totals[ $i ] = round( ( array_sum( array_map( 'floatval', $value ) ) / count( $value ) ), 2 ) . '%';
207+
$float_values = array_map(
208+
function ( $val ) {
209+
return floatval( is_scalar( $val ) ? $val : 0 );
210+
},
211+
$value
212+
);
213+
$totals[ $i ] = round( ( array_sum( $float_values ) / count( $value ) ), 2 ) . '%';
194214
} else {
195215
$totals[ $i ] = null;
196216
}

src/Profiler.php

Lines changed: 74 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,9 @@ public function wp_hook_begin() {
259259
$callbacks = self::get_filter_callbacks( $current_filter );
260260
if ( false !== $callbacks ) {
261261
foreach ( $callbacks as $priority => $cbs ) {
262-
$callback_count += count( $cbs );
262+
if ( is_array( $cbs ) ) {
263+
$callback_count += count( $cbs );
264+
}
263265
}
264266
}
265267
$this->loggers[ $current_filter ] = new Logger(
@@ -305,24 +307,38 @@ private function wrap_current_filter_callbacks( $current_filter ) {
305307
$this->previous_filter_callbacks = $callbacks;
306308

307309
foreach ( $callbacks as $priority => $priority_callbacks ) {
308-
foreach ( $priority_callbacks as $i => $the_ ) {
309-
$callbacks[ $priority ][ $i ] = array(
310-
'function' => function () use ( $the_, $i ) {
311-
if ( ! isset( $this->loggers[ $i ] ) ) {
312-
$this->loggers[ $i ] = new Logger(
313-
array(
314-
'callback' => $the_['function'],
315-
)
316-
);
317-
}
318-
assert( $this->loggers[ $i ] instanceof Logger );
319-
$this->loggers[ $i ]->start();
320-
$value = call_user_func_array( $the_['function'], func_get_args() );
321-
$this->loggers[ $i ]->stop();
322-
return $value;
323-
},
324-
'accepted_args' => $the_['accepted_args'],
325-
);
310+
if ( is_array( $priority_callbacks ) ) {
311+
$new_priority_callbacks = $priority_callbacks;
312+
foreach ( $priority_callbacks as $i => $the_ ) {
313+
if ( is_array( $the_ ) && isset( $the_['function'] ) && isset( $the_['accepted_args'] ) ) {
314+
$func = $the_['function'];
315+
$new_priority_callbacks[ $i ] = array(
316+
'function' => function () use ( $func, $i ) {
317+
if ( ! isset( $this->loggers[ $i ] ) ) {
318+
$this->loggers[ $i ] = new Logger(
319+
array(
320+
'callback' => $func,
321+
)
322+
);
323+
}
324+
assert( $this->loggers[ $i ] instanceof Logger );
325+
$this->loggers[ $i ]->start();
326+
327+
$args = func_get_args();
328+
if ( is_callable( $func ) ) {
329+
$value = call_user_func_array( $func, $args );
330+
} else {
331+
$value = null;
332+
}
333+
334+
$this->loggers[ $i ]->stop();
335+
return $value;
336+
},
337+
'accepted_args' => $the_['accepted_args'],
338+
);
339+
}
340+
}
341+
$callbacks[ $priority ] = $new_priority_callbacks;
326342
}
327343
}
328344
self::set_filter_callbacks( $current_filter, $callbacks );
@@ -388,27 +404,41 @@ public function handle_function_tick() {
388404
);
389405
}
390406

391-
assert( is_array( $this->loggers[ $callback_hash ] ) );
392-
$this->loggers[ $callback_hash ]['time'] += $time;
393-
394-
if ( isset( $wpdb ) ) {
395-
$total_queries = count( $wpdb->queries );
396-
for ( $i = $this->tick_query_offset; $i < $total_queries; $i++ ) {
397-
$this->loggers[ $callback_hash ]['query_time'] += $wpdb->queries[ $i ][1];
398-
++$this->loggers[ $callback_hash ]['query_count'];
407+
$logger_data = $this->loggers[ $callback_hash ];
408+
if ( is_array( $logger_data ) ) {
409+
$current_time = isset( $logger_data['time'] ) && is_numeric( $logger_data['time'] ) ? $logger_data['time'] : 0.0;
410+
$logger_data['time'] = (float) $current_time + $time;
411+
412+
if ( isset( $wpdb ) ) {
413+
$total_queries = count( $wpdb->queries );
414+
for ( $i = $this->tick_query_offset; $i < $total_queries; $i++ ) {
415+
$q_time = isset( $wpdb->queries[ $i ][1] ) ? $wpdb->queries[ $i ][1] : 0.0;
416+
$current_q_time = isset( $logger_data['query_time'] ) && is_numeric( $logger_data['query_time'] ) ? $logger_data['query_time'] : 0.0;
417+
$q_time_val = is_numeric( $q_time ) ? $q_time : 0.0;
418+
$logger_data['query_time'] = (float) $current_q_time + (float) $q_time_val;
419+
420+
$current_q_count = isset( $logger_data['query_count'] ) && is_numeric( $logger_data['query_count'] ) ? $logger_data['query_count'] : 0;
421+
$logger_data['query_count'] = (int) $current_q_count + 1;
422+
}
399423
}
400-
}
401424

402-
if ( isset( $wp_object_cache ) ) {
403-
$hits = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0;
404-
$misses = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0;
405-
$this->loggers[ $callback_hash ]['cache_hits'] = ( $hits - $this->tick_cache_hit_offset ) + $this->loggers[ $callback_hash ]['cache_hits'];
406-
$this->loggers[ $callback_hash ]['cache_misses'] = ( $misses - $this->tick_cache_miss_offset ) + $this->loggers[ $callback_hash ]['cache_misses'];
407-
$total = $this->loggers[ $callback_hash ]['cache_hits'] + $this->loggers[ $callback_hash ]['cache_misses'];
408-
if ( $total ) {
409-
$ratio = ( $this->loggers[ $callback_hash ]['cache_hits'] / $total ) * 100;
410-
$this->loggers[ $callback_hash ]['cache_ratio'] = round( $ratio, 2 ) . '%';
425+
if ( isset( $wp_object_cache ) ) {
426+
$hits = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0;
427+
$misses = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0;
428+
429+
$current_hits = isset( $logger_data['cache_hits'] ) && is_numeric( $logger_data['cache_hits'] ) ? $logger_data['cache_hits'] : 0;
430+
$logger_data['cache_hits'] = ( $hits - $this->tick_cache_hit_offset ) + (int) $current_hits;
431+
432+
$current_misses = isset( $logger_data['cache_misses'] ) && is_numeric( $logger_data['cache_misses'] ) ? $logger_data['cache_misses'] : 0;
433+
$logger_data['cache_misses'] = ( $misses - $this->tick_cache_miss_offset ) + (int) $current_misses;
434+
435+
$total = $logger_data['cache_hits'] + $logger_data['cache_misses'];
436+
if ( $total ) {
437+
$ratio = ( $logger_data['cache_hits'] / $total ) * 100;
438+
$logger_data['cache_ratio'] = round( $ratio, 2 ) . '%';
439+
}
411440
}
441+
$this->loggers[ $callback_hash ] = $logger_data;
412442
}
413443
}
414444

@@ -422,8 +452,8 @@ public function handle_function_tick() {
422452
$callback = '';
423453
if ( in_array( strtolower( $frame['function'] ), array( 'include', 'require', 'include_once', 'require_once' ), true ) ) {
424454
$callback = $frame['function'];
425-
if ( isset( $frame['args'][0] ) ) {
426-
$callback .= " '" . $frame['args'][0] . "'";
455+
if ( isset( $frame['args'] ) && is_array( $frame['args'] ) && isset( $frame['args'][0] ) && is_scalar( $frame['args'][0] ) ) {
456+
$callback .= " '" . (string) $frame['args'][0] . "'";
427457
}
428458
} elseif ( isset( $frame['object'] ) && method_exists( $frame['object'], $frame['function'] ) ) {
429459
$callback = get_class( $frame['object'] ) . '->' . $frame['function'] . '()';
@@ -598,9 +628,11 @@ private static function get_name_location_from_callback( $callback ) {
598628
if ( is_array( $callback ) && is_object( $callback[0] ) ) {
599629
$reflection = new \ReflectionMethod( $callback[0], $callback[1] );
600630
$name = get_class( $callback[0] ) . '->' . $callback[1] . '()';
601-
} elseif ( is_array( $callback ) && method_exists( $callback[0], $callback[1] ) ) {
631+
} elseif ( is_array( $callback ) && isset( $callback[0] ) && isset( $callback[1] ) && ( is_object( $callback[0] ) || is_string( $callback[0] ) ) && is_string( $callback[1] ) && method_exists( $callback[0], $callback[1] ) ) {
602632
$reflection = new \ReflectionMethod( $callback[0], $callback[1] );
603-
$name = $callback[0] . '::' . $callback[1] . '()';
633+
/** @var string $class_name */
634+
$class_name = $callback[0];
635+
$name = $class_name . '::' . $callback[1] . '()';
604636
} elseif ( is_object( $callback ) && is_a( $callback, 'Closure' ) ) {
605637
$reflection = new \ReflectionFunction( $callback );
606638
$name = 'function(){}';
@@ -689,6 +721,7 @@ private static function set_filter_callbacks( $filter, $callbacks ) {
689721
}
690722

691723
if ( is_a( $wp_filter[ $filter ], 'WP_Hook' ) ) {
724+
/** @var array<mixed> $callbacks */
692725
$wp_filter[ $filter ]->callbacks = $callbacks;
693726
} else {
694727
$wp_filter[ $filter ] = $callbacks; // phpcs:ignore

0 commit comments

Comments
 (0)