Skip to content

Commit fad83dd

Browse files
authored
Merge pull request #204 from wp-cli/add/phpstan
PHPStan level 3
2 parents 4abc2a9 + 446e05b commit fad83dd

File tree

3 files changed

+210
-62
lines changed

3 files changed

+210
-62
lines changed

phpstan.neon.dist

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

src/Search_Replace_Command.php

Lines changed: 159 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,114 @@
99

1010
class Search_Replace_Command extends WP_CLI_Command {
1111

12+
/**
13+
* @var bool
14+
*/
1215
private $dry_run;
16+
17+
/**
18+
* @var false|resource
19+
*/
1320
private $export_handle = false;
21+
22+
/**
23+
* @var int
24+
*/
1425
private $export_insert_size;
26+
27+
/**
28+
* @var bool
29+
*/
1530
private $recurse_objects;
31+
32+
/**
33+
* @var bool
34+
*/
1635
private $regex;
36+
37+
/**
38+
* @var string
39+
*/
1740
private $regex_flags;
41+
42+
/**
43+
* @var string
44+
*/
1845
private $regex_delimiter;
46+
47+
/**
48+
* @var int
49+
*/
1950
private $regex_limit = -1;
51+
52+
/**
53+
* @var string[]
54+
*/
2055
private $skip_tables;
56+
57+
/**
58+
* @var string[]
59+
*/
2160
private $skip_columns;
61+
62+
/**
63+
* @var string[]
64+
*/
2265
private $include_columns;
66+
67+
/**
68+
* @var string
69+
*/
2370
private $format;
71+
72+
/**
73+
* @var bool
74+
*/
2475
private $report;
76+
77+
/**
78+
* @var bool
79+
*/
2580
private $verbose;
2681

82+
/**
83+
* @var bool
84+
*/
2785
private $report_changed_only;
28-
private $log_handle = null;
86+
87+
/**
88+
* @var false|resource
89+
*/
90+
private $log_handle = null;
91+
92+
/**
93+
* @var int
94+
*/
2995
private $log_before_context = 40;
30-
private $log_after_context = 40;
31-
private $log_prefixes = array( '< ', '> ' );
96+
97+
/**
98+
* @var int
99+
*/
100+
private $log_after_context = 40;
101+
102+
/**
103+
* @var array{0: string, 1: string}
104+
*/
105+
private $log_prefixes = array( '< ', '> ' );
106+
107+
/**
108+
* @var array<string, array<mixed>>
109+
*/
32110
private $log_colors;
111+
112+
/**
113+
* @var string|false
114+
*/
33115
private $log_encoding;
116+
117+
/**
118+
* @var float
119+
*/
34120
private $start_time;
35121

36122
/**
@@ -169,24 +255,28 @@ class Search_Replace_Command extends WP_CLI_Command {
169255
* else
170256
* wp search-replace 'http://example.com' 'http://example.test' --recurse-objects --skip-columns=guid --skip-tables=wp_users
171257
* fi
258+
*
259+
* @param array<string> $args Positional arguments.
260+
* @param array{'dry-run'?: bool, 'network'?: bool, 'all-tables-with-prefix'?: bool, 'all-tables'?: bool, 'export'?: string, 'export_insert_size'?: string, 'skip-tables'?: string, 'skip-columns'?: string, 'include-columns'?: string, 'precise'?: bool, 'recurse-objects'?: bool, 'verbose'?: bool, 'regex'?: bool, 'regex-flags'?: string, 'regex-delimiter'?: string, 'regex-limit'?: string, 'format': string, 'report'?: bool, 'report-changed-only'?: bool, 'log'?: string, 'before_context'?: string, 'after_context'?: string} $assoc_args Associative arguments.
172261
*/
173262
public function __invoke( $args, $assoc_args ) {
174263
global $wpdb;
175264
$old = array_shift( $args );
176265
$new = array_shift( $args );
177266
$total = 0;
178267
$report = array();
179-
$this->dry_run = Utils\get_flag_value( $assoc_args, 'dry-run' );
180-
$php_only = Utils\get_flag_value( $assoc_args, 'precise' );
268+
$this->dry_run = Utils\get_flag_value( $assoc_args, 'dry-run', false );
269+
$php_only = Utils\get_flag_value( $assoc_args, 'precise', false );
181270
$this->recurse_objects = Utils\get_flag_value( $assoc_args, 'recurse-objects', true );
182-
$this->verbose = Utils\get_flag_value( $assoc_args, 'verbose' );
271+
$this->verbose = Utils\get_flag_value( $assoc_args, 'verbose', false );
183272
$this->format = Utils\get_flag_value( $assoc_args, 'format' );
184273
$this->regex = Utils\get_flag_value( $assoc_args, 'regex', false );
185274

275+
$default_regex_delimiter = false;
276+
186277
if ( null !== $this->regex ) {
187-
$default_regex_delimiter = false;
188-
$this->regex_flags = Utils\get_flag_value( $assoc_args, 'regex-flags', false );
189-
$this->regex_delimiter = Utils\get_flag_value( $assoc_args, 'regex-delimiter', '' );
278+
$this->regex_flags = Utils\get_flag_value( $assoc_args, 'regex-flags', false );
279+
$this->regex_delimiter = Utils\get_flag_value( $assoc_args, 'regex-delimiter', '' );
190280
if ( '' === $this->regex_delimiter ) {
191281
$this->regex_delimiter = chr( 1 );
192282
$default_regex_delimiter = true;
@@ -213,7 +303,7 @@ public function __invoke( $args, $assoc_args ) {
213303
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Preventing a warning when testing the regex.
214304
if ( false === @preg_match( $search_regex, '' ) ) {
215305
$error = error_get_last();
216-
$preg_error_message = ( ! empty( $error ) && array_key_exists( 'message', $error ) ) ? "\n{$error['message']}." : '';
306+
$preg_error_message = ! empty( $error ) ? "\n{$error['message']}." : '';
217307
if ( $default_regex_delimiter ) {
218308
$flags_msg = $this->regex_flags ? "flags '$this->regex_flags'" : 'no flags';
219309
$msg = "The regex pattern '$old' with default delimiter 'chr(1)' and {$flags_msg} fails.";
@@ -242,16 +332,16 @@ public function __invoke( $args, $assoc_args ) {
242332
$this->export_handle = STDOUT;
243333
$this->verbose = false;
244334
} else {
245-
$this->export_handle = @fopen( $assoc_args['export'], 'w' );
335+
$this->export_handle = @fopen( $export, 'w' );
246336
if ( false === $this->export_handle ) {
247337
$error = error_get_last();
248-
WP_CLI::error( sprintf( 'Unable to open export file "%s" for writing: %s.', $assoc_args['export'], $error['message'] ) );
338+
WP_CLI::error( sprintf( 'Unable to open export file "%s" for writing: %s.', $export, $error['message'] ?? '(unknown error)' ) );
249339
}
250340
}
251341
$export_insert_size = Utils\get_flag_value( $assoc_args, 'export_insert_size', 50 );
252342
// phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- See the code, this is deliberate.
253343
if ( (int) $export_insert_size == $export_insert_size && $export_insert_size > 0 ) {
254-
$this->export_insert_size = $export_insert_size;
344+
$this->export_insert_size = (int) $export_insert_size;
255345
}
256346
$php_only = true;
257347
}
@@ -261,54 +351,53 @@ public function __invoke( $args, $assoc_args ) {
261351
if ( true === $log || '-' === $log ) {
262352
$this->log_handle = STDOUT;
263353
} else {
264-
$this->log_handle = @fopen( $assoc_args['log'], 'w' );
354+
$this->log_handle = @fopen( $log, 'w' );
265355
if ( false === $this->log_handle ) {
266356
$error = error_get_last();
267-
WP_CLI::error( sprintf( 'Unable to open log file "%s" for writing: %s.', $assoc_args['log'], $error['message'] ) );
357+
WP_CLI::error( sprintf( 'Unable to open log file "%s" for writing: %s.', $log, $error['message'] ?? '(unknown error)' ) );
268358
}
269359
}
270-
if ( $this->log_handle ) {
271-
$before_context = Utils\get_flag_value( $assoc_args, 'before_context' );
272-
if ( null !== $before_context && preg_match( '/^[0-9]+$/', $before_context ) ) {
273-
$this->log_before_context = (int) $before_context;
274-
}
275360

276-
$after_context = Utils\get_flag_value( $assoc_args, 'after_context' );
277-
if ( null !== $after_context && preg_match( '/^[0-9]+$/', $after_context ) ) {
278-
$this->log_after_context = (int) $after_context;
279-
}
361+
$before_context = Utils\get_flag_value( $assoc_args, 'before_context' );
362+
if ( null !== $before_context && preg_match( '/^[0-9]+$/', $before_context ) ) {
363+
$this->log_before_context = (int) $before_context;
364+
}
280365

281-
$log_prefixes = getenv( 'WP_CLI_SEARCH_REPLACE_LOG_PREFIXES' );
282-
if ( false !== $log_prefixes && preg_match( '/^([^,]*),([^,]*)$/', $log_prefixes, $matches ) ) {
283-
$this->log_prefixes = array( $matches[1], $matches[2] );
284-
}
366+
$after_context = Utils\get_flag_value( $assoc_args, 'after_context' );
367+
if ( null !== $after_context && preg_match( '/^[0-9]+$/', $after_context ) ) {
368+
$this->log_after_context = (int) $after_context;
369+
}
285370

286-
if ( STDOUT === $this->log_handle ) {
287-
$default_log_colors = array(
288-
'log_table_column_id' => '%B',
289-
'log_old' => '%R',
290-
'log_new' => '%G',
291-
);
292-
} else {
293-
$default_log_colors = array(
294-
'log_table_column_id' => '',
295-
'log_old' => '',
296-
'log_new' => '',
297-
);
298-
}
371+
$log_prefixes = getenv( 'WP_CLI_SEARCH_REPLACE_LOG_PREFIXES' );
372+
if ( false !== $log_prefixes && preg_match( '/^([^,]*),([^,]*)$/', $log_prefixes, $matches ) ) {
373+
$this->log_prefixes = array( $matches[1], $matches[2] );
374+
}
299375

300-
$log_colors = getenv( 'WP_CLI_SEARCH_REPLACE_LOG_COLORS' );
301-
if ( false !== $log_colors && preg_match( '/^([^,]*),([^,]*),([^,]*)$/', $log_colors, $matches ) ) {
302-
$default_log_colors = array(
303-
'log_table_column_id' => $matches[1],
304-
'log_old' => $matches[2],
305-
'log_new' => $matches[3],
306-
);
307-
}
376+
if ( STDOUT === $this->log_handle ) {
377+
$default_log_colors = array(
378+
'log_table_column_id' => '%B',
379+
'log_old' => '%R',
380+
'log_new' => '%G',
381+
);
382+
} else {
383+
$default_log_colors = array(
384+
'log_table_column_id' => '',
385+
'log_old' => '',
386+
'log_new' => '',
387+
);
388+
}
308389

309-
$this->log_colors = $this->get_colors( $assoc_args, $default_log_colors );
310-
$this->log_encoding = 0 === strpos( $wpdb->charset, 'utf8' ) ? 'UTF-8' : false;
390+
$log_colors = getenv( 'WP_CLI_SEARCH_REPLACE_LOG_COLORS' );
391+
if ( false !== $log_colors && preg_match( '/^([^,]*),([^,]*),([^,]*)$/', $log_colors, $matches ) ) {
392+
$default_log_colors = array(
393+
'log_table_column_id' => $matches[1],
394+
'log_old' => $matches[2],
395+
'log_new' => $matches[3],
396+
);
311397
}
398+
399+
$this->log_colors = $this->get_colors( $assoc_args, $default_log_colors );
400+
$this->log_encoding = 0 === strpos( $wpdb->charset, 'utf8' ) ? 'UTF-8' : false;
312401
}
313402

314403
$this->report = Utils\get_flag_value( $assoc_args, 'report', true );
@@ -383,6 +472,8 @@ public function __invoke( $args, $assoc_args ) {
383472
WP_CLI::log( sprintf( 'Checking: %s.%s', $table, $col ) );
384473
}
385474

475+
$serial_row = false;
476+
386477
if ( ! $php_only && ! $this->regex ) {
387478
$col_sql = self::esc_sql_ident( $col );
388479
$wpdb->last_error = '';
@@ -422,7 +513,7 @@ public function __invoke( $args, $assoc_args ) {
422513
}
423514

424515
if ( 'count' === $this->format ) {
425-
WP_CLI::line( $total );
516+
WP_CLI::line( (string) $total );
426517
return;
427518
}
428519

@@ -698,8 +789,9 @@ private function write_sql_row_fields( $table, $rows ) {
698789

699790
if ( method_exists( $wpdb, 'remove_placeholder_escape' ) ) {
700791
// since 4.8.3
701-
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- verified inputs above
702-
$sql = $wpdb->remove_placeholder_escape( $wpdb->prepare( $sql, array_values( $values ) ) );
792+
793+
// @phpstan-ignore method.nonObject
794+
$sql = $wpdb->remove_placeholder_escape( $wpdb->prepare( $sql, array_values( $values ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- verified inputs above
703795
} else {
704796
// 4.8.2 or less
705797
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- verified inputs above
@@ -766,8 +858,9 @@ private static function esc_like( $old ) {
766858
// 4.0
767859
$old = $wpdb->esc_like( $old );
768860
} else {
769-
// phpcs:ignore WordPress.WP.DeprecatedFunctions.like_escapeFound -- BC-layer for WP 3.9 or less.
770-
$old = like_escape( esc_sql( $old ) ); // Note: this double escaping is actually necessary, even though `esc_like()` will be used in a `prepare()`.
861+
// Note: this double escaping is actually necessary, even though `esc_like()` will be used in a `prepare()`.
862+
// @phpstan-ignore function.deprecated
863+
$old = like_escape( esc_sql( $old ) ); // phpcs:ignore WordPress.WP.DeprecatedFunctions.like_escapeFound -- BC-layer for WP 3.9 or less.
771864
}
772865

773866
return $old;
@@ -777,8 +870,9 @@ private static function esc_like( $old ) {
777870
* Escapes (backticks) MySQL identifiers (aka schema object names) - i.e. column names, table names, and database/index/alias/view etc names.
778871
* See https://dev.mysql.com/doc/refman/5.5/en/identifiers.html
779872
*
780-
* @param string|array $idents A single identifier or an array of identifiers.
781-
* @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings.
873+
* @param string|string[] $idents A single identifier or an array of identifiers.
874+
* @return string|string[] An escaped string if given a string, or an array of escaped strings if given an array of strings.
875+
* @phpstan-return ($idents is string ? string : string[])
782876
*/
783877
private static function esc_sql_ident( $idents ) {
784878
$backtick = static function ( $v ) {
@@ -794,8 +888,9 @@ private static function esc_sql_ident( $idents ) {
794888
/**
795889
* Puts MySQL string values in single quotes, to avoid them being interpreted as column names.
796890
*
797-
* @param string|array $values A single value or an array of values.
798-
* @return string|array A quoted string if given a string, or an array of quoted strings if given an array of strings.
891+
* @param string|string[] $values A single value or an array of values.
892+
* @return string|string[] A quoted string if given a string, or an array of quoted strings if given an array of strings.
893+
* @phpstan-return ($values is string ? string : string[])
799894
*/
800895
private static function esc_sql_value( $values ) {
801896
$quote = static function ( $v ) {
@@ -1035,6 +1130,10 @@ static function ( $m ) use ( $matches ) {
10351130
* @param array $new_bits Array of new replacement log strings.
10361131
*/
10371132
private function log_write( $col, $keys, $table, $old_bits, $new_bits ) {
1133+
if ( ! $this->log_handle ) {
1134+
return;
1135+
}
1136+
10381137
$id_log = $keys ? ( ':' . implode( ',', (array) $keys ) ) : '';
10391138
$table_column_id_log = $this->log_colors['log_table_column_id'][0] . $table . '.' . $col . $id_log . $this->log_colors['log_table_column_id'][1];
10401139

0 commit comments

Comments
 (0)