Skip to content
14 changes: 14 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
parameters:
level: 6
paths:
- src
- doctor-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
ignoreErrors:
- '#Call to deprecated method setAccessible\(\) of class ReflectionProperty\.#'

11 changes: 10 additions & 1 deletion src/Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ abstract class Check {

/**
* Initialize the check.
*
* @param array<string, mixed> $options
*/
public function __construct( $options = array() ) {

Expand All @@ -48,6 +50,8 @@ public function __construct( $options = array() ) {

/**
* Get when the check is expected to run.
*
* @return string
*/
public function get_when() {
return $this->_when;
Expand All @@ -57,6 +61,7 @@ public function get_when() {
* Set when the check is expected to run.
*
* @param string $when
* @return void
*/
public function set_when( $when ) {
$this->_when = $when;
Expand All @@ -66,6 +71,7 @@ public function set_when( $when ) {
* Set the status of the check.
*
* @param string $status
* @return void
*/
protected function set_status( $status ) {
$this->_status = $status;
Expand All @@ -75,6 +81,7 @@ protected function set_status( $status ) {
* Set the message of the check.
*
* @param string $message
* @return void
*/
protected function set_message( $message ) {
$this->_message = $message;
Expand All @@ -85,13 +92,15 @@ protected function set_message( $message ) {
*
* Because each check checks for something different, this method must be
* subclassed. Method is expected to set $status_code and $status_message.
*
* @return void
*/
abstract public function run();

/**
* Get results of the check.
*
* @return array
* @return array<string, string>
*/
public function get_results() {
return array(
Expand Down
10 changes: 9 additions & 1 deletion src/Check/Autoload_Options_Size.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ class Autoload_Options_Size extends Check {
*/
protected $threshold_kb = 900;

/**
* @return void
*/
public function run() {
ob_start();
WP_CLI::run_command(
Expand All @@ -40,9 +43,14 @@ public function run() {
}
}

/**
* @param int|float $size Size in bytes.
* @param int $precision Precision.
* @return string
*/
private static function format_bytes( $size, $precision = 2 ) {
$base = log( $size, 1024 );
$suffixes = array( '', 'kb', 'mb', 'g', 't' );
return round( pow( 1024, $base - floor( $base ) ), $precision ) . $suffixes[ floor( $base ) ];
return round( pow( 1024, $base - floor( $base ) ), $precision ) . $suffixes[ (int) floor( $base ) ];
}
}
3 changes: 3 additions & 0 deletions src/Check/Cache_Flush.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
*/
class Cache_Flush extends File_Contents {

/**
* @return void
*/
public function run() {

// Path to wp-content directory.
Expand Down
23 changes: 17 additions & 6 deletions src/Check/Constant_Definition.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ class Constant_Definition extends Check {
/**
* Whether or not the constant is expected to be a falsy value.
*
* @var bool
* @var bool|null
*/
protected $falsy;
protected $falsy = null;

/**
* Expected value of the constant.
Expand All @@ -39,6 +39,8 @@ class Constant_Definition extends Check {

/**
* Initialize the constant check
*
* @param array<string, mixed> $options
*/
public function __construct( $options = array() ) {
parent::__construct( $options );
Expand All @@ -47,6 +49,9 @@ public function __construct( $options = array() ) {
}
}

/**
Comment thread
swissspidy marked this conversation as resolved.
Outdated
* @return void
*/
public function run() {

if ( isset( $this->falsy ) ) {
Expand Down Expand Up @@ -99,7 +104,7 @@ public function run() {
return;
}

if ( $this->defined && ! isset( $this->value ) ) {
if ( ! isset( $this->value ) ) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Removing $this->defined from this condition changes the behavior of the check. Previously, this block only executed if the configuration explicitly requested a definition check (e.g., defined: true in YAML). Now, it will execute for any constant check where a value is not specified, potentially leading to incorrect success reports if the constant is not actually defined. If PHPStan is flagging $this->defined as an undefined property, it should be explicitly declared in the class rather than removed from the logic.

if ( $this->defined && ! isset( $this->value ) ) {

$this->set_status( 'success' );
$this->set_message( "Constant '{$this->constant}' is defined." );
return;
Expand All @@ -118,12 +123,18 @@ public function run() {
}
}

/**
* @param mixed $value
* @return string
*/
private static function human_value( $value ) {
if ( true === $value ) {
$value = 'true';
return 'true';
} elseif ( false === $value ) {
$value = 'false';
return 'false';
} elseif ( is_null( $value ) ) {
return 'null';
}
return $value;
return (string) $value;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Casting $value directly to a string can cause a PHP notice or warning if the constant's value is an array (e.g., Array to string conversion). Since WordPress constants can occasionally be arrays or other non-scalar types, it's safer to handle these cases explicitly.

return is_scalar( $value ) ? (string) $value : json_encode( $value );

}
}
6 changes: 6 additions & 0 deletions src/Check/Core_Verify_Checksums.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@
*/
class Core_Verify_Checksums extends Check {

/**
* @param array<string, mixed> $options
*/
public function __construct( $options = array() ) {
parent::__construct( $options );
$this->set_when( 'before_wp_load' );
}

/**
* @return void
*/
public function run() {
$return_code = WP_CLI::runcommand(
'core verify-checksums',
Expand Down
6 changes: 6 additions & 0 deletions src/Check/Cron.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@

abstract class Cron extends Check {

/**
* @var array<mixed>|null
*/
protected static $crons;

/**
* @return array<mixed>
*/
protected static function get_crons() {

if ( isset( self::$crons ) ) {
Expand Down
3 changes: 3 additions & 0 deletions src/Check/Cron_Count.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class Cron_Count extends Cron {
*/
protected $threshold_count = 50;

/**
* @return void
*/
public function run() {
$crons = self::get_crons();
if ( count( $crons ) >= $this->threshold_count ) {
Expand Down
3 changes: 3 additions & 0 deletions src/Check/Cron_Duplicates.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class Cron_Duplicates extends Cron {
*/
protected $threshold_count = 10;

/**
* @return void
*/
public function run() {
$crons = self::get_crons();
$job_counts = array();
Expand Down
14 changes: 11 additions & 3 deletions src/Check/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ abstract class File extends Check {
/**
* File checks are run as their own group.
*/
protected $_when = false;
protected $_when = 'manual'; // Run manually via group
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Changing $_when from false to 'manual' breaks the discovery logic for file-based checks. In src/Command.php, the code identifies File checks by checking if get_when() returns a falsy value (see lines 142 and 156-161). By setting this to a string, these checks will be incorrectly treated as regular hooked checks and will not be added to the $file_checks array, causing the filesystem scan to skip them entirely. If this change was made to satisfy PHPStan's type requirements, consider updating the base class type hint or using a @phpstan-ignore instead.

protected $_when = false;


/**
* File extension to check.
Expand Down Expand Up @@ -42,14 +42,14 @@ abstract class File extends Check {
/**
* Any files matching the check.
*
* @var array
* @var array<string|\SplFileInfo>
*/
protected $_matches = array();

/**
* Get the options for this check
*
* @return string
* @return array<string, bool|string>
*/
public function get_options() {
return array(
Expand All @@ -58,4 +58,12 @@ public function get_options() {
'path' => $this->path,
);
}

/**
* Check a specific file.
*
* @param \SplFileInfo $file File to check.
* @return void
*/
abstract public function check_file( \SplFileInfo $file );
}
8 changes: 6 additions & 2 deletions src/Check/File_Contents.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ class File_Contents extends File {
/**
* Regex pattern to check against each file’s contents.
*
* @var string
* @var string|null
*/
protected $regex;
protected $regex = null;

/**
* Assert existence or absence of the regex pattern.
Expand Down Expand Up @@ -56,6 +56,10 @@ public function run() {
}
}

/**
* @param SplFileInfo $file
* @return void
*/
public function check_file( SplFileInfo $file ) {
if ( $file->isDir() || ! isset( $this->regex ) ) {
return;
Expand Down
12 changes: 7 additions & 5 deletions src/Check/File_Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace WP_CLI\Doctor\Check;

use SplFileInfo;

/**
* Checks files on the filesystem to be of a certain type.
*/
Expand All @@ -12,9 +10,9 @@ class File_Type extends File {
/**
* Assert the file type is or isn't a symlink.
*
* @var bool
* @var bool|null
*/
protected $symlink;
protected $symlink = null;

public function run() {

Expand All @@ -32,7 +30,11 @@ public function run() {
}
}

public function check_file( SplFileInfo $file ) {
/**
* @param \SplFileInfo $file
* @return void
*/
public function check_file( \SplFileInfo $file ) {
if ( isset( $this->symlink ) ) {
if ( 'link' === $file->getType() && false === $this->symlink ) {
$this->_matches[] = $file;
Expand Down
14 changes: 9 additions & 5 deletions src/Check/Network_Required_Plugins.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ class Network_Required_Plugins extends Check {
/**
* List of required plugin slugs.
*
* @var array
* @var array<string>
*/
protected $plugins = array();

/**
* @param array<string, mixed> $options
*/
public function __construct( $options = array() ) {
parent::__construct( $options );

Expand All @@ -32,6 +35,9 @@ public function __construct( $options = array() ) {
}
}

/**
* @return void
*/
public function run() {
if ( ! is_multisite() ) {
$this->set_status( 'success' );
Expand Down Expand Up @@ -80,10 +86,8 @@ public function run() {
}

/**
* Resolve a plugin slug or file to an installed plugin file path.
*
* @param string $plugin_slug Requested plugin slug/file.
* @param array $installed_plugins Installed plugins keyed by plugin file.
* @param string $plugin_slug Requested plugin slug/file.
* @param array<string, mixed> $installed_plugins Installed plugins keyed by plugin file.
* @return string|null
*/
private function get_plugin_file( $plugin_slug, $installed_plugins ) {
Expand Down
6 changes: 6 additions & 0 deletions src/Check/Network_Site_Count.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class Network_Site_Count extends Check {
*/
protected $maximum = 500;

/**
* @param array<string, mixed> $options
*/
public function __construct( $options = array() ) {
parent::__construct( $options );

Expand All @@ -34,6 +37,9 @@ public function __construct( $options = array() ) {
}
}

/**
* @return void
*/
public function run() {
if ( ! is_multisite() ) {
$this->set_status( 'success' );
Expand Down
3 changes: 3 additions & 0 deletions src/Check/Network_Site_Option_Value.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class Network_Site_Option_Value extends Check {
*/
protected $value_is_not;

/**
* @return void
*/
public function run() {
if ( ! is_multisite() ) {
$this->set_status( 'success' );
Expand Down
Loading
Loading