Skip to content

Commit f8afe79

Browse files
committed
Merge branch 'main' into copilot/introduce-wp-profile-requests
2 parents 2a709ee + 47fc059 commit f8afe79

File tree

9 files changed

+455
-149
lines changed

9 files changed

+455
-149
lines changed

.github/workflows/copilot-setup-steps.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ jobs:
3636

3737
- name: Install Composer dependencies & cache dependencies
3838
if: steps.check_composer_file.outputs.files_exists == 'true'
39-
uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # v3
39+
uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # v4
4040
env:
4141
COMPOSER_ROOT_VERSION: dev-${{ github.event.repository.default_branch }}
42-
with:
43-
# Bust the cache at least once a month - output format: YYYY-MM.
44-
custom-cache-suffix: $(date -u "+%Y-%m")

features/profile.feature

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ Feature: Basic profile usage
1515
See 'wp help profile <command>' for more information on a specific command.
1616
"""
1717

18+
# Skip when object cache is used because sqlite-object-cache fails during manual wp core install
19+
# due to missing wp_options table during cron scheduling.
20+
@skip-object-cache
1821
Scenario: Error when SAVEQUERIES is defined to false
1922
Given an empty directory
2023
And WP files

phpcs.xml.dist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
<!--
66
#############################################################################
77
COMMAND LINE ARGUMENTS
8-
For help understanding this file: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-ruleset.xml
9-
For help using PHPCS: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage
8+
For help understanding this file: https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Annotated-ruleset.xml
9+
For help using PHPCS: https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Usage
1010
#############################################################################
1111
-->
1212

phpstan.neon.dist

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
parameters:
2+
level: 9
3+
paths:
4+
- src
5+
- profile-command.php
6+
scanDirectories:
7+
- vendor/wp-cli/wp-cli/php
8+
scanFiles:
9+
- vendor/php-stubs/wordpress-stubs/wordpress-stubs.php
10+
- tests/phpstan/scan-files.php
11+
12+
treatPhpDocTypesAsCertain: false

src/Command.php

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,20 @@ class Command {
129129
*
130130
* @skipglobalargcheck
131131
* @when before_wp_load
132+
*
133+
* @param array{0?: string} $args Positional arguments.
134+
* @param array{all?: bool, spotlight?: bool, url?: string, fields?: string, format: string, order: string, orderby?: string} $assoc_args Associative arguments.
135+
* @return void
132136
*/
133137
public function stage( $args, $assoc_args ) {
134138
global $wpdb;
135139

136140
$focus = Utils\get_flag_value( $assoc_args, 'all', isset( $args[0] ) ? $args[0] : null );
137141

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

141147
$valid_stages = array( 'bootstrap', 'main_query', 'template' );
142148
if ( $focus && ( true !== $focus && ! in_array( $focus, $valid_stages, true ) ) ) {
@@ -181,6 +187,7 @@ public function stage( $args, $assoc_args ) {
181187
$fields = array_merge( $base, $metrics );
182188
$formatter = new Formatter( $assoc_args, $fields );
183189
$loggers = $profiler->get_loggers();
190+
/** @var array<string, bool|string> $assoc_args */
184191
if ( Utils\get_flag_value( $assoc_args, 'spotlight' ) ) {
185192
$loggers = self::shine_spotlight( $loggers, $metrics );
186193
}
@@ -257,13 +264,19 @@ public function stage( $args, $assoc_args ) {
257264
*
258265
* @skipglobalargcheck
259266
* @when before_wp_load
267+
*
268+
* @param array{0?: string} $args Positional arguments.
269+
* @param array{all?: bool, spotlight?: bool, url?: string, fields?: string, format: string, order: string, orderby?: string} $assoc_args
270+
* @return void
260271
*/
261272
public function hook( $args, $assoc_args ) {
262273

263274
$focus = Utils\get_flag_value( $assoc_args, 'all', isset( $args[0] ) ? $args[0] : null );
264275

265-
$order = Utils\get_flag_value( $assoc_args, 'order', 'ASC' );
266-
$orderby = Utils\get_flag_value( $assoc_args, 'orderby', null );
276+
$order_val = Utils\get_flag_value( $assoc_args, 'order', 'ASC' );
277+
$order = is_string( $order_val ) ? $order_val : 'ASC';
278+
$orderby_val = Utils\get_flag_value( $assoc_args, 'orderby', null );
279+
$orderby = ( is_string( $orderby_val ) || is_null( $orderby_val ) ) ? $orderby_val : null;
267280

268281
$profiler = new Profiler( 'hook', $focus );
269282
$profiler->run();
@@ -293,11 +306,14 @@ public function hook( $args, $assoc_args ) {
293306
$fields = array_merge( $base, $metrics );
294307
$formatter = new Formatter( $assoc_args, $fields );
295308
$loggers = $profiler->get_loggers();
309+
/** @var array<string, bool|string> $assoc_args */
296310
if ( Utils\get_flag_value( $assoc_args, 'spotlight' ) ) {
297311
$loggers = self::shine_spotlight( $loggers, $metrics );
298312
}
299-
$search = Utils\get_flag_value( $assoc_args, 'search', false );
300-
if ( false !== $search && '' !== $search ) {
313+
/** @var array<string, bool|string> $assoc_args */
314+
$search_val = Utils\get_flag_value( $assoc_args, 'search', '' );
315+
$search = is_string( $search_val ) ? $search_val : '';
316+
if ( '' !== $search ) {
301317
if ( ! $focus ) {
302318
WP_CLI::error( '--search requires --all or a specific hook.' );
303319
}
@@ -356,8 +372,12 @@ public function hook( $args, $assoc_args ) {
356372
* +-----------+----------------------------+----------+---------+
357373
* | total (2) | | | 0.3994s |
358374
* +-----------+----------------------------+----------+---------+
359-
*
375+
* @skipglobalargcheck
360376
* @when before_wp_load
377+
*
378+
* @param array<string> $args Positional arguments. Unused.
379+
* @param array{url?: string, fields?: string, format: string, order: string, orderby?: string} $assoc_args Associative arguments.
380+
* @return void
361381
*/
362382
public function requests( $args, $assoc_args ) {
363383
$order = Utils\get_flag_value( $assoc_args, 'order', 'ASC' );
@@ -429,13 +449,19 @@ public function requests( $args, $assoc_args ) {
429449
* | 0.1009s | 100% | 1 |
430450
* +---------+-------------+---------------+
431451
*
452+
* @param array{0: string} $args Positional arguments.
453+
* @param array{hook?: bool|string, fields: string, format: string, order: string, orderby?: string} $assoc_args Associative arguments.
454+
* @return void
455+
*
432456
* @subcommand eval
433457
*/
434458
public function eval_( $args, $assoc_args ) {
435459
$statement = $args[0];
436460

437-
$order = Utils\get_flag_value( $assoc_args, 'order', 'ASC' );
438-
$orderby = Utils\get_flag_value( $assoc_args, 'orderby', null );
461+
$order_val = Utils\get_flag_value( $assoc_args, 'order', 'ASC' );
462+
$order = is_string( $order_val ) ? $order_val : 'ASC';
463+
$orderby_val = Utils\get_flag_value( $assoc_args, 'orderby', null );
464+
$orderby = ( is_string( $orderby_val ) || is_null( $orderby_val ) ) ? $orderby_val : null;
439465

440466
self::profile_eval_ish(
441467
$assoc_args,
@@ -498,14 +524,20 @@ function () use ( $statement ) {
498524
* | 0.1009s | 100% | 1 |
499525
* +---------+-------------+---------------+
500526
*
527+
* @param array{0: string} $args Positional arguments.
528+
* @param array{hook?: string|bool, fields?: string, format: string, order: string, orderby?: string} $assoc_args Associative arguments.
529+
* @return void
530+
*
501531
* @subcommand eval-file
502532
*/
503533
public function eval_file( $args, $assoc_args ) {
504534

505535
$file = $args[0];
506536

507-
$order = Utils\get_flag_value( $assoc_args, 'order', 'ASC' );
508-
$orderby = Utils\get_flag_value( $assoc_args, 'orderby', null );
537+
$order_val = Utils\get_flag_value( $assoc_args, 'order', 'ASC' );
538+
$order = is_string( $order_val ) ? $order_val : 'ASC';
539+
$orderby_val = Utils\get_flag_value( $assoc_args, 'orderby', null );
540+
$orderby = ( is_string( $orderby_val ) || is_null( $orderby_val ) ) ? $orderby_val : null;
509541

510542
if ( ! file_exists( $file ) ) {
511543
WP_CLI::error( "'$file' does not exist." );
@@ -523,6 +555,12 @@ function () use ( $file ) {
523555

524556
/**
525557
* Profile an eval or eval-file statement.
558+
*
559+
* @param array{hook?: string|bool} $assoc_args
560+
* @param callable $profile_callback
561+
* @param string $order
562+
* @param string|null $orderby
563+
* @return void
526564
*/
527565
private static function profile_eval_ish( $assoc_args, $profile_callback, $order = 'ASC', $orderby = null ) {
528566
$hook = Utils\get_flag_value( $assoc_args, 'hook' );
@@ -572,6 +610,7 @@ private static function profile_eval_ish( $assoc_args, $profile_callback, $order
572610
* Include a file without exposing it to current scope
573611
*
574612
* @param string $file
613+
* @return void
575614
*/
576615
private static function include_file( $file ) {
577616
include $file;
@@ -580,9 +619,9 @@ private static function include_file( $file ) {
580619
/**
581620
* Filter loggers with zero-ish values.
582621
*
583-
* @param array $loggers
584-
* @param array $metrics
585-
* @return array
622+
* @param array<\WP_CLI\Profile\Logger> $loggers
623+
* @param array<string> $metrics
624+
* @return array<\WP_CLI\Profile\Logger>
586625
*/
587626
private static function shine_spotlight( $loggers, $metrics ) {
588627

@@ -622,9 +661,9 @@ private static function shine_spotlight( $loggers, $metrics ) {
622661
/**
623662
* Filter loggers to only those whose callback name matches a pattern.
624663
*
625-
* @param array $loggers
626-
* @param string $pattern
627-
* @return array
664+
* @param array<\WP_CLI\Profile\Logger> $loggers
665+
* @param string $pattern
666+
* @return array<\WP_CLI\Profile\Logger>
628667
*/
629668
private static function filter_by_callback( $loggers, $pattern ) {
630669
return array_filter(

src/Formatter.php

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,32 @@
44

55
class Formatter {
66

7+
/**
8+
* @var \WP_CLI\Formatter
9+
*/
710
private $formatter;
811

12+
/**
13+
* @var array<string, mixed>
14+
*/
915
private $args;
1016

17+
/**
18+
* @var int|null
19+
*/
1120
private $total_cell_index;
1221

22+
/**
23+
* Formatter constructor.
24+
*
25+
* @param array<mixed> $assoc_args
26+
* @param array<string>|null $fields
27+
* @param string|false $prefix
28+
*/
1329
public function __construct( &$assoc_args, $fields = null, $prefix = false ) {
30+
if ( null === $fields ) {
31+
$fields = [];
32+
}
1433
$format_args = array(
1534
'format' => 'table',
1635
'fields' => $fields,
@@ -24,10 +43,19 @@ public function __construct( &$assoc_args, $fields = null, $prefix = false ) {
2443
}
2544

2645
if ( ! is_array( $format_args['fields'] ) ) {
27-
$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 );
2849
}
2950

30-
$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+
);
3159

3260
if ( isset( $assoc_args['fields'] ) ) {
3361
if ( empty( $format_args['fields'] ) ) {
@@ -39,9 +67,9 @@ public function __construct( &$assoc_args, $fields = null, $prefix = false ) {
3967
}
4068
}
4169

42-
if ( 'time' !== $fields[0] ) {
70+
if ( ! empty( $fields ) && 'time' !== $fields[0] ) {
4371
$index = array_search( $fields[0], $format_args['fields'], true );
44-
$this->total_cell_index = ( false !== $index ) ? $index : null;
72+
$this->total_cell_index = ( false !== $index ) ? (int) $index : null;
4573
}
4674

4775
$this->args = $format_args;
@@ -51,11 +79,17 @@ public function __construct( &$assoc_args, $fields = null, $prefix = false ) {
5179
/**
5280
* Display multiple items according to the output arguments.
5381
*
54-
* @param array $items
82+
* @param array<\WP_CLI\Profile\Logger> $items
83+
* @param bool $include_total
84+
* @param string $order
85+
* @param string|null $orderby
86+
* @return void
5587
*/
5688
public function display_items( $items, $include_total, $order, $orderby ) {
5789
if ( 'table' === $this->args['format'] && empty( $this->args['field'] ) ) {
58-
$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 );
5993
} else {
6094
$this->formatter->display_items( $items );
6195
}
@@ -64,15 +98,17 @@ public function display_items( $items, $include_total, $order, $orderby ) {
6498
/**
6599
* Function to compare floats.
66100
*
67-
* @param double $a Floating number.
68-
* @param double $b Floating number.
101+
* @param float $a Floating number.
102+
* @param float $b Floating number.
103+
* @return int
69104
*/
70105
private function compare_float( $a, $b ) {
71-
$a = number_format( $a, 4 );
72-
$b = number_format( $b, 4 );
73-
if ( 0 === $a - $b ) {
106+
$a = round( $a, 4 );
107+
$b = round( $b, 4 );
108+
$diff = $a - $b;
109+
if ( 0.0 === $diff ) {
74110
return 0;
75-
} elseif ( $a - $b < 0 ) {
111+
} elseif ( $diff < 0 ) {
76112
return -1;
77113
} else {
78114
return 1;
@@ -82,8 +118,12 @@ private function compare_float( $a, $b ) {
82118
/**
83119
* Show items in a \cli\Table.
84120
*
85-
* @param array $items
86-
* @param array $fields
121+
* @param string $order
122+
* @param string|null $orderby
123+
* @param array<\WP_CLI\Profile\Logger> $items
124+
* @param array<string> $fields
125+
* @param bool $include_total
126+
* @return void
87127
*/
88128
private function show_table( $order, $orderby, $items, $fields, $include_total ) {
89129
$table = new \cli\Table();
@@ -109,7 +149,7 @@ function ( $a, $b ) use ( $order, $orderby ) {
109149
list( $first, $second ) = $orderby_array;
110150

111151
if ( is_numeric( $first->$orderby ) && is_numeric( $second->$orderby ) ) {
112-
return $this->compare_float( $first->$orderby, $second->$orderby );
152+
return $this->compare_float( (float) $first->$orderby, (float) $second->$orderby );
113153
}
114154

115155
return strcmp( $first->$orderby, $second->$orderby );
@@ -139,13 +179,16 @@ function ( $a, $b ) use ( $order, $orderby ) {
139179
}
140180
if ( stripos( $fields[ $i ], '_ratio' ) ) {
141181
if ( ! is_null( $value ) ) {
182+
assert( is_array( $totals[ $i ] ) );
142183
$totals[ $i ][] = $value;
143184
}
144185
} elseif ( is_numeric( $value ) ) {
145-
$totals[ $i ] += $value;
186+
$current_total = is_numeric( $totals[ $i ] ) ? $totals[ $i ] : 0;
187+
$totals[ $i ] = $current_total + $value;
146188
}
147189
if ( stripos( $fields[ $i ], '_time' ) || 'time' === $fields[ $i ] ) {
148-
$values[ $i ] = round( $value, 4 ) . 's';
190+
$value_num = is_numeric( $value ) ? (float) $value : 0.0;
191+
$values[ $i ] = round( $value_num, 4 ) . 's';
149192
}
150193
}
151194
$table->addRow( $values );
@@ -156,11 +199,18 @@ function ( $a, $b ) use ( $order, $orderby ) {
156199
continue;
157200
}
158201
if ( stripos( $fields[ $i ], '_time' ) || 'time' === $fields[ $i ] ) {
159-
$totals[ $i ] = round( $value, 4 ) . 's';
202+
assert( is_numeric( $value ) );
203+
$totals[ $i ] = round( (float) $value, 4 ) . 's';
160204
}
161205
if ( is_array( $value ) ) {
162206
if ( ! empty( $value ) ) {
163-
$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 ) . '%';
164214
} else {
165215
$totals[ $i ] = null;
166216
}

0 commit comments

Comments
 (0)