Skip to content

Upgrade PHPStan analysis from level 6 to level 7 #441

Closed

Description

@armanist

Description

PHPStan level 7 introduces stricter union type checking. Functions that return type|false or type|null must have their failure cases explicitly handled. Upgrading from level 6 to 7 surfaces 324 errors across 19 packages.

Motivation

Level 7 catches real bugs — unhandled false from fopen(), null dereferences on findOneBy(), unchecked json_encode() failures, etc. Fixing these improves runtime safety and code quality.

Scope

All errors fall into these categories:

Category Description Example
Null/false return handling PHP functions like fopen(), glob(), parse_url(), json_encode(), preg_replace(), preg_split(), openssl_encrypt(), file_get_contents(), md5_file(), getimagesize(), strtotime(), realpath(), proc_get_status() return false on failure fopen() result assigned to resource property without false check
Nullable property access Calling methods on properties typed as ?Type without null checks $this->fs->isDirectory() where $fs is ?FileSystem
Dynamic instantiation Factory patterns using new $adapterClass(...) produce object, not the expected interface new $adapterClass(...) passed where CacheInterface expected
PHPDoc type corrections Array key types, nullable annotations, return type mismatches array<class-string, ...> should be array<string, ...>
Cross-method type tracking PHPStan can't track property nullability across method calls ensureArchiveOpen() guarantees $this->archive is non-null, but callers still see ?Phar

Approach

  1. Update phpstan.neon.dist level from 6 to 7
  2. Work package by package — after fixing each package, run PHPStan on that package and then run its unit tests before moving on
  3. Prefer minimal, non-breaking fixes:
    • Null coalescing (?? '', ?? []) for safe defaults
    • Explicit guards (if ($x === null) return;) for nullable properties
    • instanceof checks for dynamic instantiation in factories
    • @phpstan-assert annotations for methods that guarantee type state (e.g., ensureArchiveOpen(), isMultiRequest())
    • Local variables to help PHPStan track property state after lazy initialization or recursive calls
    • @phpstan-ignore only when the error is a known PHPStan limitation (e.g., parse_str output type, dynamic class-string from validation rules)
  4. Do not change existing logic or control flow
  5. Do not widen return types just to silence errors
  6. Preserve cross-PHP-version compatibility (project targets PHP 7.4+)

Special notes

  • Mailer package requires a structural refactor: move adapter-specific properties ($httpClient, $mailer) out of MailerTrait into individual adapters, and replace $this->name == 'SMTP' branching with abstract/hook methods (prepare(), sendEmail(), getTransportErrors(), resolveMessageId(), etc.)
  • ob_implicit_flush() in HtmlAdapter.php and MailerTrait.php uses a cross-version-compatible call (PHP_VERSION_ID >= 80000 ? false : 0) — suppress with @phpstan-ignore argument.type, do not simplify
  • HttpClient.php uses @phpstan-assert on isMultiRequest() and ensureSingleRequest() to avoid redundant instanceof guards throughout the class
  • Archive adapters use @phpstan-assert on ensureArchiveOpen() for the same reason
  • Console commands (DebugBarCommand, ResourceCacheClearCommand) should initialize FileSystem in the constructor (non-nullable) rather than lazily in exec()

Acceptance criteria

  • vendor/bin/phpstan analyse src --level 7 reports 0 errors
  • Full PHPUnit suite passes with no new failures
  • No existing logic or control flow is changed
  • No return types widened just to suppress errors
  • PHP 7.4 compatibility preserved

Activity

  1. added this to the 3.0.0 milestone on Mar 27, 2026
  2. self-assigned this
    on Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingenhancementNew feature or requestgood first issueGood for newcomers

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions