Skip to content

Bump PHPStan to level 4 and fix all resulting issues#1200

Open
utkarshcloudinary wants to merge 2 commits into
phpstan-level3from
phpstan-level4
Open

Bump PHPStan to level 4 and fix all resulting issues#1200
utkarshcloudinary wants to merge 2 commits into
phpstan-level3from
phpstan-level4

Conversation

@utkarshcloudinary

Copy link
Copy Markdown
Collaborator

Approach

Raise the PHPStan analysis level from 3 to 4 and resolve all 93 resulting errors. Level 4 adds "impossible/always-true/always-false condition" analysis, so most findings are checks that PHPStan can now prove redundant. The overwhelming majority are resolved safely with no runtime behavior change — the tightened PHPDoc from the level-3 pass had become the source of truth, and where it was too narrow PHPStan (correctly) flagged legitimate runtime guards as "impossible". Fixes fall into three groups:

1. Over-narrow PHPDoc widened so genuine runtime guards stay live (majority, no behavior change)

  • Nullable object properties that are lazily initialised in setup()/init_*() were declared as non-null; widened to |null so the guarding ! $this->x / empty() / is_null() checks are valid again: Cache::$file_system, File_System::$wp_file_system, Media::$sync, Media::$gallery, Api::$media, Plugin::$settings, Sync::$settings, Storage::{$media,$sync,$download,$connect}, Upload_Sync::$pusher, Setting::$component, Singleton_Trait::$instance, Relation_Trait::$query_relations, WordPress::$cloudinary_url.
  • Return types that omit a real null/false/WP_Error/array case: Cache_Point::url_to_path (string|null), Cache_Point::get_cache_point (\WP_Post|null), Api::upload_cache (array|string|\WP_Error), Media::get_public_id (string|null), Media::image_srcset (array|false), Media::create_attachment (int, not int|WP_Error), Sync::generate_signature (mixed — it returns apply_filters() output).
  • @param types that are really mixed/broader because the value comes from WP core, apply_filters, metadata, or array keys: Delivery::filter_out_cloudinary/sanitize_url, Admin::filter_template (int|string key), Admin::add_admin_notice (string|array), Text-family sanitize_value overrides (File_Folder, React), Component::{is_struct_array,add_content}, Params_Trait::{set_params,is_public}, File_System::get_file_urls (string|null), Storage::get (bool), Sync::set_signature_item (string|null), plus wp_get_attachment_metadata() results annotated array|false (Upgrade, Download_Sync).
  • Sync::$managers widened to array<string, ...|null> so the managers['download'] fallback is reachable; Connect::$usage to array|mixed.
  • Plugin::get_component() return union gains Cron so the WPML instanceof Cron guards type-check.

2. Genuinely redundant/dead code removed (no behavior change)

  • Duplicate conditions already guaranteed by an enclosing if or an earlier is_wp_error() early-return: Analytics (is_array/null !==/$user &&), Connect::usage_stats (is_array after is_wp_error), Delivery (&& $relation['slashed']), Media::prepare_size (is_array), Api::cloudinary_url (is_array), Upload_Sync::add_inline_action (re-checked is_syncable/is_uploadable_media), Settings::set_pending (if ( $found )find_setting with $create=true always returns an object), Opt_Level (is_wp_error on a Setting).
  • Guards for WP APIs that exist at the plugin's minimum supported version (5.6): Delivery (wp_get_registered_image_subsizes), Gallery (WP_Screen::is_block_editor).
  • Provably-unused symbols removed: Media::$cloudinary_ids (shadowed by a static local), Video::queue_video_config() + its now-orphaned $attachments property, Upload_Sync::$enabled property and its dead constructor parameter.

3. Deliberate cross-version/optional-integration guards kept and suppressed inline

  • method_exists() checks that defend against optional Elementor or an old autoloaded Utils during a plugin update are kept and annotated with // @phpstan-ignore function.alreadyNarrowedType (the stub/concrete class makes them look always-true, but they are real at runtime).
  • Utils::get_post_parent keeps its is_callable( 'get_post_parent' ) fallback for WP < 6.0 (suppressed).
  • Plugin::is_wpcom_vip_prod() reads the conditionally-defined VIP constant via constant() and suppresses the undefined-constant narrowing.
  • The four parent::pre_render() / parent::enqueue_scripts() calls (base methods are empty no-ops) are kept for hierarchy consistency and suppressed with // @phpstan-ignore staticMethod.resultUnused.

Latent bugs fixed (behavior change — surfaced by the always-true/false analysis)

  • Api::prepare_optionsif ( 0 <= strpos( $transformation, 'f_auto' ) ) was always true (false coerces to 0); corrected to false !== strpos( … ) so the eager-format branch only runs when f_auto is present.
  • Media::upgrade_settingsif ( 2.4 === $previous_version ) compared a float to the version string and was always false, so the 2.4→2.5 migration never ran; corrected to '2.4' === $previous_version.
  • Cache_Point::check_versionif ( ! is_numeric( $cache_point ) ) on a WP_Post|null was always true; corrected to if ( $cache_point instanceof \WP_Post ) to match the evident intent (only act when a cache-point post exists).

QA notes

  • Run static analysis and confirm no errors at level 4:
    composer phpstan
    
    Expected: [OK] No errors.
  • Run code style on the changed files (repo-wide composer lint may OOM; scope to changed files with a raised limit):
    vendor/bin/phpcs -d memory_limit=1G $(git diff --name-only develop -- 'php/*.php')
    
    Expected: no errors/warnings.
  • The three behavior-fixing changes above warrant a light functional smoke test:
    • Eager format transformations (Api::prepare_options): upload/eager URLs still generate the expected f_avif/f_webp derivatives only when f_auto is used.
    • 2.4→2.5 upgrade path (Media::upgrade_settings): upgrading from 2.4 migrates the legacy global image/video transformation options.
    • Cache versioning (Cache_Point::check_version): cache-point post meta version updates when the plugin version changes.
  • Everything else is PHPDoc/annotation-only or dead-code removal; spot-check the touched runtime paths (settings pages, sync managers, usage stats, WPML cron, Elementor background-image replacement) render/behave as before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants