Skip to content

Commit c34432b

Browse files
committed
Asymmetric visibility basics
1 parent 9b86df9 commit c34432b

File tree

45 files changed

+688
-32
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+688
-32
lines changed

conf/config.level0.neon

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ rules:
9797
- PHPStan\Rules\Properties\MissingReadOnlyPropertyAssignRule
9898
- PHPStan\Rules\Properties\MissingReadOnlyByPhpDocPropertyAssignRule
9999
- PHPStan\Rules\Properties\PropertiesInInterfaceRule
100+
- PHPStan\Rules\Properties\PropertyAssignRefRule
100101
- PHPStan\Rules\Properties\PropertyAttributesRule
101102
- PHPStan\Rules\Properties\PropertyHookAttributesRule
102103
- PHPStan\Rules\Properties\PropertyInClassRule

src/Analyser/MutatingScope.php

+56-1
Original file line numberDiff line numberDiff line change
@@ -5381,12 +5381,67 @@ private function getBooleanExpressionDepth(Expr $expr, int $depth = 0): int
53815381
return $depth;
53825382
}
53835383

5384-
/** @api */
5384+
/**
5385+
* @api
5386+
* @deprecated Use canReadProperty() or canWriteProperty()
5387+
*/
53855388
public function canAccessProperty(PropertyReflection $propertyReflection): bool
53865389
{
53875390
return $this->canAccessClassMember($propertyReflection);
53885391
}
53895392

5393+
/** @api */
5394+
public function canReadProperty(ExtendedPropertyReflection $propertyReflection): bool
5395+
{
5396+
return $this->canAccessClassMember($propertyReflection);
5397+
}
5398+
5399+
/** @api */
5400+
public function canWriteProperty(ExtendedPropertyReflection $propertyReflection): bool
5401+
{
5402+
if (!$propertyReflection->isPrivateSet() && !$propertyReflection->isProtectedSet()) {
5403+
return $this->canAccessClassMember($propertyReflection);
5404+
}
5405+
5406+
if (!$this->phpVersion->supportsAsymmetricVisibility()) {
5407+
return $this->canAccessClassMember($propertyReflection);
5408+
}
5409+
5410+
$classReflectionName = $propertyReflection->getDeclaringClass()->getName();
5411+
$canAccessClassMember = static function (ClassReflection $classReflection) use ($propertyReflection, $classReflectionName) {
5412+
if ($propertyReflection->isPrivateSet()) {
5413+
return $classReflection->getName() === $classReflectionName;
5414+
}
5415+
5416+
// protected set
5417+
5418+
if (
5419+
$classReflection->getName() === $classReflectionName
5420+
|| $classReflection->isSubclassOf($classReflectionName)
5421+
) {
5422+
return true;
5423+
}
5424+
5425+
return $propertyReflection->getDeclaringClass()->isSubclassOf($classReflection->getName());
5426+
};
5427+
5428+
foreach ($this->inClosureBindScopeClasses as $inClosureBindScopeClass) {
5429+
if (!$this->reflectionProvider->hasClass($inClosureBindScopeClass)) {
5430+
continue;
5431+
}
5432+
5433+
if ($canAccessClassMember($this->reflectionProvider->getClass($inClosureBindScopeClass))) {
5434+
return true;
5435+
}
5436+
}
5437+
5438+
if ($this->isInClass()) {
5439+
return $canAccessClassMember($this->getClassReflection());
5440+
}
5441+
5442+
return false;
5443+
}
5444+
53905445
/** @api */
53915446
public function canCallMethod(MethodReflection $methodReflection): bool
53925447
{

src/Analyser/OutOfClassScope.php

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PHPStan\Reflection\ClassConstantReflection;
66
use PHPStan\Reflection\ClassMemberAccessAnswerer;
77
use PHPStan\Reflection\ClassReflection;
8+
use PHPStan\Reflection\ExtendedPropertyReflection;
89
use PHPStan\Reflection\MethodReflection;
910
use PHPStan\Reflection\PropertyReflection;
1011

@@ -31,6 +32,18 @@ public function canAccessProperty(PropertyReflection $propertyReflection): bool
3132
return $propertyReflection->isPublic();
3233
}
3334

35+
public function canReadProperty(ExtendedPropertyReflection $propertyReflection): bool
36+
{
37+
return $propertyReflection->isPublic();
38+
}
39+
40+
public function canWriteProperty(ExtendedPropertyReflection $propertyReflection): bool
41+
{
42+
return $propertyReflection->isPublic()
43+
&& !$propertyReflection->isProtectedSet()
44+
&& !$propertyReflection->isPrivateSet();
45+
}
46+
3447
public function canCallMethod(MethodReflection $methodReflection): bool
3548
{
3649
return $methodReflection->isPublic();

src/Php/PhpVersion.php

+5
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,11 @@ public function supportsPropertyHooks(): bool
357357
return $this->versionId >= 80400;
358358
}
359359

360+
public function supportsAsymmetricVisibility(): bool
361+
{
362+
return $this->versionId >= 80400;
363+
}
364+
360365
public function hasDateTimeExceptions(): bool
361366
{
362367
return $this->versionId >= 80300;

src/Reflection/Annotations/AnnotationPropertyReflection.php

+10
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,14 @@ public function getHook(string $hookType): ExtendedMethodReflection
112112
throw new ShouldNotHappenException();
113113
}
114114

115+
public function isProtectedSet(): bool
116+
{
117+
return false;
118+
}
119+
120+
public function isPrivateSet(): bool
121+
{
122+
return false;
123+
}
124+
115125
}

src/Reflection/ClassMemberAccessAnswerer.php

+7
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,15 @@ public function isInClass(): bool;
1313

1414
public function getClassReflection(): ?ClassReflection;
1515

16+
/**
17+
* @deprecated Use canReadProperty() or canWriteProperty()
18+
*/
1619
public function canAccessProperty(PropertyReflection $propertyReflection): bool;
1720

21+
public function canReadProperty(ExtendedPropertyReflection $propertyReflection): bool;
22+
23+
public function canWriteProperty(ExtendedPropertyReflection $propertyReflection): bool;
24+
1825
public function canCallMethod(MethodReflection $methodReflection): bool;
1926

2027
public function canAccessConstant(ClassConstantReflection $constantReflection): bool;

src/Reflection/ClassReflection.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco
642642
}
643643

644644
$property = $this->wrapExtendedProperty($extension->getProperty($this, $propertyName));
645-
if ($scope->canAccessProperty($property)) {
645+
if ($scope->canReadProperty($property)) {
646646
return $this->properties[$key] = $property;
647647
}
648648
$this->properties[$key] = $property;

src/Reflection/Dummy/ChangedTypePropertyReflection.php

+10
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,14 @@ public function getHook(string $hookType): ExtendedMethodReflection
111111
return $this->reflection->getHook($hookType);
112112
}
113113

114+
public function isProtectedSet(): bool
115+
{
116+
return $this->reflection->isProtectedSet();
117+
}
118+
119+
public function isPrivateSet(): bool
120+
{
121+
return $this->reflection->isPrivateSet();
122+
}
123+
114124
}

src/Reflection/Dummy/DummyPropertyReflection.php

+10
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,14 @@ public function getHook(string $hookType): ExtendedMethodReflection
107107
throw new ShouldNotHappenException();
108108
}
109109

110+
public function isProtectedSet(): bool
111+
{
112+
return false;
113+
}
114+
115+
public function isPrivateSet(): bool
116+
{
117+
return false;
118+
}
119+
110120
}

src/Reflection/ExtendedPropertyReflection.php

+4
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,8 @@ public function hasHook(string $hookType): bool;
4141
*/
4242
public function getHook(string $hookType): ExtendedMethodReflection;
4343

44+
public function isProtectedSet(): bool;
45+
46+
public function isPrivateSet(): bool;
47+
4448
}

src/Reflection/Php/EnumPropertyReflection.php

+10
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,14 @@ public function getHook(string $hookType): ExtendedMethodReflection
106106
throw new ShouldNotHappenException();
107107
}
108108

109+
public function isProtectedSet(): bool
110+
{
111+
return false;
112+
}
113+
114+
public function isPrivateSet(): bool
115+
{
116+
return false;
117+
}
118+
109119
}

src/Reflection/Php/PhpPropertyReflection.php

+30
Original file line numberDiff line numberDiff line change
@@ -268,4 +268,34 @@ public function getHook(string $hookType): ExtendedMethodReflection
268268
return $this->setHook;
269269
}
270270

271+
public function isProtectedSet(): bool
272+
{
273+
if ($this->reflection->isProtectedSet()) {
274+
return true;
275+
}
276+
277+
if ($this->isReadOnly()) {
278+
return !$this->isPrivate() && !$this->reflection->isPrivateSet();
279+
}
280+
281+
return false;
282+
}
283+
284+
public function isPrivateSet(): bool
285+
{
286+
if ($this->reflection->isPrivateSet()) {
287+
return true;
288+
}
289+
290+
if ($this->reflection->isProtectedSet()) {
291+
return false;
292+
}
293+
294+
if ($this->isReadOnly()) {
295+
return $this->isPrivate();
296+
}
297+
298+
return false;
299+
}
300+
271301
}

src/Reflection/Php/SimpleXMLElementProperty.php

+10
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,14 @@ public function getHook(string $hookType): ExtendedMethodReflection
120120
throw new ShouldNotHappenException();
121121
}
122122

123+
public function isProtectedSet(): bool
124+
{
125+
return false;
126+
}
127+
128+
public function isPrivateSet(): bool
129+
{
130+
return false;
131+
}
132+
123133
}

src/Reflection/Php/UniversalObjectCrateProperty.php

+10
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,14 @@ public function getHook(string $hookType): ExtendedMethodReflection
110110
throw new ShouldNotHappenException();
111111
}
112112

113+
public function isProtectedSet(): bool
114+
{
115+
return false;
116+
}
117+
118+
public function isPrivateSet(): bool
119+
{
120+
return false;
121+
}
122+
113123
}

src/Reflection/ResolvedPropertyReflection.php

+10
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,14 @@ public function getHook(string $hookType): ExtendedMethodReflection
173173
);
174174
}
175175

176+
public function isProtectedSet(): bool
177+
{
178+
return $this->reflection->isProtectedSet();
179+
}
180+
181+
public function isPrivateSet(): bool
182+
{
183+
return $this->reflection->isPrivateSet();
184+
}
185+
176186
}

src/Reflection/Type/IntersectionTypePropertyReflection.php

+10
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,14 @@ public function getHook(string $hookType): ExtendedMethodReflection
160160
return new IntersectionTypeMethodReflection($hooks[0]->getName(), $hooks);
161161
}
162162

163+
public function isProtectedSet(): bool
164+
{
165+
return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isProtectedSet());
166+
}
167+
168+
public function isPrivateSet(): bool
169+
{
170+
return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isPrivateSet());
171+
}
172+
163173
}

src/Reflection/Type/UnionTypePropertyReflection.php

+10
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,14 @@ public function getHook(string $hookType): ExtendedMethodReflection
160160
return new UnionTypeMethodReflection($hooks[0]->getName(), $hooks);
161161
}
162162

163+
public function isProtectedSet(): bool
164+
{
165+
return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isProtectedSet());
166+
}
167+
168+
public function isPrivateSet(): bool
169+
{
170+
return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isPrivateSet());
171+
}
172+
163173
}

src/Reflection/WrappedExtendedPropertyReflection.php

+10
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,14 @@ public function getHook(string $hookType): ExtendedMethodReflection
103103
throw new ShouldNotHappenException();
104104
}
105105

106+
public function isProtectedSet(): bool
107+
{
108+
return false;
109+
}
110+
111+
public function isPrivateSet(): bool
112+
{
113+
return false;
114+
}
115+
106116
}

0 commit comments

Comments
 (0)