44
55use PhpParser \Node ;
66use PhpParser \Node \Expr \PropertyFetch ;
7- use PhpParser \Node \Identifier ;
8- use PHPStan \Analyser \NullsafeOperatorHelper ;
97use PHPStan \Analyser \Scope ;
10- use PHPStan \Internal \SprintfHelper ;
11- use PHPStan \Reflection \ReflectionProvider ;
12- use PHPStan \Rules \IdentifierRuleError ;
138use PHPStan \Rules \Rule ;
14- use PHPStan \Rules \RuleErrorBuilder ;
15- use PHPStan \Rules \RuleLevelHelper ;
16- use PHPStan \Type \Constant \ConstantStringType ;
17- use PHPStan \Type \ErrorType ;
18- use PHPStan \Type \StaticType ;
19- use PHPStan \Type \Type ;
20- use PHPStan \Type \VerbosityLevel ;
21- use function array_map ;
22- use function array_merge ;
23- use function count ;
24- use function sprintf ;
259
2610/**
2711 * @implements Rule<Node\Expr\PropertyFetch>
2812 */
2913final class AccessPropertiesRule implements Rule
3014{
3115
32- public function __construct (
33- private ReflectionProvider $ reflectionProvider ,
34- private RuleLevelHelper $ ruleLevelHelper ,
35- private bool $ reportMagicProperties ,
36- private bool $ checkDynamicProperties ,
37- )
16+ public function __construct (private AccessPropertiesCheck $ check )
3817 {
3918 }
4019
@@ -45,137 +24,7 @@ public function getNodeType(): string
4524
4625 public function processNode (Node $ node , Scope $ scope ): array
4726 {
48- if ($ node ->name instanceof Identifier) {
49- $ names = [$ node ->name ->name ];
50- } else {
51- $ names = array_map (static fn (ConstantStringType $ type ): string => $ type ->getValue (), $ scope ->getType ($ node ->name )->getConstantStrings ());
52- }
53-
54- $ errors = [];
55- foreach ($ names as $ name ) {
56- $ errors = array_merge ($ errors , $ this ->processSingleProperty ($ scope , $ node , $ name ));
57- }
58-
59- return $ errors ;
60- }
61-
62- /**
63- * @return list<IdentifierRuleError>
64- */
65- private function processSingleProperty (Scope $ scope , PropertyFetch $ node , string $ name ): array
66- {
67- $ typeResult = $ this ->ruleLevelHelper ->findTypeToCheck (
68- $ scope ,
69- NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope ($ scope , $ node ->var ),
70- sprintf ('Access to property $%s on an unknown class %%s. ' , SprintfHelper::escapeFormatString ($ name )),
71- static fn (Type $ type ): bool => $ type ->canAccessProperties ()->yes () && $ type ->hasProperty ($ name )->yes (),
72- );
73- $ type = $ typeResult ->getType ();
74- if ($ type instanceof ErrorType) {
75- return $ typeResult ->getUnknownClassErrors ();
76- }
77-
78- if ($ scope ->isInExpressionAssign ($ node )) {
79- return [];
80- }
81-
82- $ typeForDescribe = $ type ;
83- if ($ type instanceof StaticType) {
84- $ typeForDescribe = $ type ->getStaticObjectType ();
85- }
86-
87- if ($ type ->canAccessProperties ()->no () || $ type ->canAccessProperties ()->maybe () && !$ scope ->isUndefinedExpressionAllowed ($ node )) {
88- return [
89- RuleErrorBuilder::message (sprintf (
90- 'Cannot access property $%s on %s. ' ,
91- $ name ,
92- $ typeForDescribe ->describe (VerbosityLevel::typeOnly ()),
93- ))->identifier ('property.nonObject ' )->build (),
94- ];
95- }
96-
97- $ has = $ type ->hasProperty ($ name );
98- if (!$ has ->no () && $ this ->canAccessUndefinedProperties ($ scope , $ node )) {
99- return [];
100- }
101-
102- if (!$ has ->yes ()) {
103- if ($ scope ->hasExpressionType ($ node )->yes ()) {
104- return [];
105- }
106-
107- $ classNames = $ type ->getObjectClassNames ();
108- if (!$ this ->reportMagicProperties ) {
109- foreach ($ classNames as $ className ) {
110- if (!$ this ->reflectionProvider ->hasClass ($ className )) {
111- continue ;
112- }
113-
114- $ classReflection = $ this ->reflectionProvider ->getClass ($ className );
115- if (
116- $ classReflection ->hasNativeMethod ('__get ' )
117- || $ classReflection ->hasNativeMethod ('__set ' )
118- ) {
119- return [];
120- }
121- }
122- }
123-
124- if (count ($ classNames ) === 1 ) {
125- $ propertyClassReflection = $ this ->reflectionProvider ->getClass ($ classNames [0 ]);
126- $ parentClassReflection = $ propertyClassReflection ->getParentClass ();
127- while ($ parentClassReflection !== null ) {
128- if ($ parentClassReflection ->hasProperty ($ name )) {
129- if ($ scope ->canAccessProperty ($ parentClassReflection ->getProperty ($ name , $ scope ))) {
130- return [];
131- }
132- return [
133- RuleErrorBuilder::message (sprintf (
134- 'Access to private property $%s of parent class %s. ' ,
135- $ name ,
136- $ parentClassReflection ->getDisplayName (),
137- ))->identifier ('property.private ' )->build (),
138- ];
139- }
140-
141- $ parentClassReflection = $ parentClassReflection ->getParentClass ();
142- }
143- }
144-
145- $ ruleErrorBuilder = RuleErrorBuilder::message (sprintf (
146- 'Access to an undefined property %s::$%s. ' ,
147- $ typeForDescribe ->describe (VerbosityLevel::typeOnly ()),
148- $ name ,
149- ))->identifier ('property.notFound ' );
150- if ($ typeResult ->getTip () !== null ) {
151- $ ruleErrorBuilder ->tip ($ typeResult ->getTip ());
152- } else {
153- $ ruleErrorBuilder ->tip ('Learn more: <fg=cyan>https://phpstan.org/blog/solving-phpstan-access-to-undefined-property</> ' );
154- }
155-
156- return [
157- $ ruleErrorBuilder ->build (),
158- ];
159- }
160-
161- $ propertyReflection = $ type ->getProperty ($ name , $ scope );
162- if (!$ scope ->canAccessProperty ($ propertyReflection )) {
163- return [
164- RuleErrorBuilder::message (sprintf (
165- 'Access to %s property %s::$%s. ' ,
166- $ propertyReflection ->isPrivate () ? 'private ' : 'protected ' ,
167- $ type ->describe (VerbosityLevel::typeOnly ()),
168- $ name ,
169- ))->identifier (sprintf ('property.%s ' , $ propertyReflection ->isPrivate () ? 'private ' : 'protected ' ))->build (),
170- ];
171- }
172-
173- return [];
174- }
175-
176- private function canAccessUndefinedProperties (Scope $ scope , Node \Expr $ node ): bool
177- {
178- return $ scope ->isUndefinedExpressionAllowed ($ node ) && !$ this ->checkDynamicProperties ;
27+ return $ this ->check ->check ($ node , $ scope );
17928 }
18029
18130}
0 commit comments