Skip to content

Fix phpstan/phpstan#14063: Readonly property modification through clone() is not reported outside allowed scope#5265

Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-ub2nqgb
Open

Fix phpstan/phpstan#14063: Readonly property modification through clone() is not reported outside allowed scope#5265
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-ub2nqgb

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When using clone($obj, ['prop' => value]) (PHP 8.5 clone-with) from outside the declaring class on a readonly property, PHPStan incorrectly reported no error. In PHP, modifying readonly properties through clone-with is only allowed from within the declaring class scope.

Changes

  • src/Rules/Properties/ReadOnlyPropertyAssignRule.php: Moved the inCloneWith early-exit from before the declaring class check to after it. This ensures that clone-with from outside the declaring class still reports "Readonly property is assigned outside of its declaring class", while clone-with from inside the class correctly skips the constructor requirement.
  • src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php: Same fix applied for @readonly (PHPDoc) properties.
  • tests/PHPStan/Rules/Properties/data/bug-14063.php: New regression test reproducing the exact issue scenario.
  • Updated existing clone-with test data and expectations to cover the new error cases, including public(set) readonly properties and readonly class promoted properties.

Root cause

Both ReadOnlyPropertyAssignRule and ReadOnlyByPhpDocPropertyAssignRule had an unconditional early return when the inCloneWith attribute was set on the property fetch node. This skipped ALL checks, including the scope check that verifies the assignment is happening within the declaring class. The fix moves the inCloneWith continue inside the per-property loop, placing it after the declaring class checks but before the constructor checks, so the scope restriction is still enforced while the constructor requirement is correctly relaxed for clone-with operations.

Test

Added tests/PHPStan/Rules/Properties/data/bug-14063.php which reproduces the exact issue: a final readonly class with a promoted property, where clone() with property override is called from global scope. The test verifies that "Readonly property is assigned outside of its declaring class" is now correctly reported. Also updated existing clone-with test expectations for both native readonly and PHPDoc readonly properties.

Fixes phpstan/phpstan#14063

…de allowed scope

- ReadOnlyPropertyAssignRule and ReadOnlyByPhpDocPropertyAssignRule unconditionally
  skipped all checks when inCloneWith was true, but should still enforce the
  "assigned outside declaring class" check
- Moved the inCloneWith early-exit inside the loop, after the declaring class check,
  so that only the constructor requirement is skipped for clone-with operations
- Added regression test for phpstan/phpstan#14063
- Updated existing clone-with test expectations to include new error cases
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