Skip to content

Commit 0606e39

Browse files
ENGCOM-7566: PHPStan add support of magic methods of Data Object. #27905
- Merge Pull Request #27905 from swnsma/magento2-1:PHPStan-support-of-Data-Object-magic-methods - Merged commits: 1. 19b636e 2. 8849d1e 3. dea6c91 4. d825912 5. 313fc6f 6. 6fef300 7. 6c2d86f 8. f4b6d5e 9. c7a02e0 10. 23483fb 11. c973636 12. 9fed236 13. 9c1ba5a 14. 28dbb69 15. 5f0c767 16. 60d6b02 17. 64e5016 18. 61fe852 19. cb50ff6 20. 347c54f 21. a243302 22. 693cc0e 23. 46c8981 24. 4ff058c
2 parents d598c12 + 4ff058c commit 0606e39

File tree

10 files changed

+667
-2
lines changed

10 files changed

+667
-2
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\PhpStan\Reflection\Php;
9+
10+
use Magento\Framework\DataObject;
11+
use Magento\Framework\Session\SessionManager;
12+
use PHPStan\DependencyInjection\Container;
13+
use PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension;
14+
use PHPStan\Reflection\ClassReflection;
15+
use PHPStan\Reflection\MethodReflection;
16+
use PHPStan\Reflection\MethodsClassReflectionExtension;
17+
18+
/**
19+
* Extension to add support of magic methods (get/set/uns/has) based on @see DataObject
20+
*/
21+
class DataObjectClassReflectionExtension implements MethodsClassReflectionExtension
22+
{
23+
private const MAGIC_METHODS_PREFIXES = [
24+
'get',
25+
'set',
26+
'uns',
27+
'has'
28+
];
29+
30+
private const PREFIX_LENGTH = 3;
31+
32+
/**
33+
* @var Container
34+
*/
35+
private $container;
36+
37+
/**
38+
* @var AnnotationsMethodsClassReflectionExtension
39+
*/
40+
private $annotationsMethodsClassReflectionExtension;
41+
42+
/**
43+
* @param Container $container
44+
*/
45+
public function __construct(Container $container)
46+
{
47+
$this->container = $container;
48+
$this->annotationsMethodsClassReflectionExtension = $this->container
49+
->getByType(AnnotationsMethodsClassReflectionExtension::class);
50+
}
51+
52+
/**
53+
* Check if class has relations with DataObject and requested method can be considered as a magic method.
54+
*
55+
* @param ClassReflection $classReflection
56+
* @param string $methodName
57+
*
58+
* @return bool
59+
*/
60+
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
61+
{
62+
// Workaround due to annotation extension always loads last.
63+
if ($this->annotationsMethodsClassReflectionExtension->hasMethod($classReflection, $methodName)) {
64+
// In case when annotation already available for the method, we will not use 'magic methods' approach.
65+
return false;
66+
}
67+
if ($classReflection->isSubclassOf(DataObject::class) || $classReflection->getName() == DataObject::class) {
68+
return in_array($this->getPrefix($methodName), self::MAGIC_METHODS_PREFIXES);
69+
}
70+
/** SessionManager redirects all calls to `__call` to container which extends DataObject */
71+
if ($classReflection->isSubclassOf(SessionManager::class)
72+
|| $classReflection->getName() === SessionManager::class
73+
) {
74+
/** @see \Magento\Framework\Session\SessionManager::__call */
75+
return in_array($this->getPrefix($methodName), self::MAGIC_METHODS_PREFIXES);
76+
}
77+
78+
return false;
79+
}
80+
81+
/**
82+
* Get prefix from method name.
83+
*
84+
* @param string $methodName
85+
*
86+
* @return string
87+
*/
88+
private function getPrefix(string $methodName): string
89+
{
90+
return (string)substr($methodName, 0, self::PREFIX_LENGTH);
91+
}
92+
93+
/**
94+
* Get method reflection instance.
95+
*
96+
* @param ClassReflection $classReflection
97+
* @param string $methodName
98+
*
99+
* @return MethodReflection
100+
*/
101+
public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
102+
{
103+
return new DataObjectMethodReflection($classReflection, $methodName);
104+
}
105+
}
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\PhpStan\Reflection\Php;
9+
10+
use PHPStan\Reflection\ClassMemberReflection;
11+
use PHPStan\Reflection\ClassReflection;
12+
use PHPStan\Reflection\FunctionVariant;
13+
use PHPStan\Reflection\MethodReflection;
14+
use PHPStan\Reflection\ParameterReflection;
15+
use PHPStan\Reflection\ParametersAcceptor;
16+
use PHPStan\Reflection\Php\DummyParameter;
17+
use PHPStan\TrinaryLogic;
18+
use PHPStan\Type\BooleanType;
19+
use PHPStan\Type\Generic\TemplateTypeMap;
20+
use PHPStan\Type\IntegerType;
21+
use PHPStan\Type\MixedType;
22+
use PHPStan\Type\ObjectType;
23+
use PHPStan\Type\StringType;
24+
use PHPStan\Type\Type;
25+
use PHPStan\Type\UnionType;
26+
use PHPStan\Type\VoidType;
27+
28+
/**
29+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
30+
*/
31+
class DataObjectMethodReflection implements MethodReflection
32+
{
33+
private const PREFIX_LENGTH = 3;
34+
35+
/**
36+
* @var ClassReflection
37+
*/
38+
private $classReflection;
39+
40+
/**
41+
* @var string
42+
*/
43+
private $methodName;
44+
45+
/**
46+
* @param ClassReflection $classReflection
47+
* @param string $methodName
48+
*/
49+
public function __construct(ClassReflection $classReflection, string $methodName)
50+
{
51+
$this->classReflection = $classReflection;
52+
$this->methodName = $methodName;
53+
}
54+
55+
/**
56+
* Get methods class reflection.
57+
*
58+
* @return ClassReflection
59+
*/
60+
public function getDeclaringClass(): ClassReflection
61+
{
62+
return $this->classReflection;
63+
}
64+
65+
/**
66+
* Get if method is static.
67+
*
68+
* @return bool
69+
*/
70+
public function isStatic(): bool
71+
{
72+
return false;
73+
}
74+
75+
/**
76+
* Get if method is private.
77+
*
78+
* @return bool
79+
*/
80+
public function isPrivate(): bool
81+
{
82+
return false;
83+
}
84+
85+
/**
86+
* Get if method is public.
87+
*
88+
* @return bool
89+
*/
90+
public function isPublic(): bool
91+
{
92+
return true;
93+
}
94+
95+
/**
96+
* Get Method PHP Doc comment message.
97+
*
98+
* @return string|null
99+
*/
100+
public function getDocComment(): ?string
101+
{
102+
return null;
103+
}
104+
105+
/**
106+
* Get Method Name.
107+
*
108+
* @return string
109+
*/
110+
public function getName(): string
111+
{
112+
return $this->methodName;
113+
}
114+
115+
/**
116+
* Get Prototype.
117+
*
118+
* @return ClassMemberReflection
119+
*/
120+
public function getPrototype(): ClassMemberReflection
121+
{
122+
return $this;
123+
}
124+
125+
/**
126+
* Get Magic Methods variant based on type (get/set/has/uns).
127+
*
128+
* @return ParametersAcceptor[]
129+
*/
130+
public function getVariants(): array
131+
{
132+
return [
133+
new FunctionVariant(
134+
TemplateTypeMap::createEmpty(),
135+
TemplateTypeMap::createEmpty(),
136+
$this->getMethodParameters(),
137+
false,
138+
$this->getReturnType()
139+
)
140+
];
141+
}
142+
143+
/**
144+
* Get prefix from method name.
145+
*
146+
* @return string
147+
*/
148+
private function getMethodNamePrefix(): string
149+
{
150+
return (string)substr($this->methodName, 0, self::PREFIX_LENGTH);
151+
}
152+
153+
/**
154+
* Get Magic Methods parameters.
155+
*
156+
* @return ParameterReflection[]
157+
*/
158+
private function getMethodParameters(): array
159+
{
160+
$params = [];
161+
switch ($this->getMethodNamePrefix()) {
162+
case 'set':
163+
$params[] = new DummyParameter(
164+
'value',
165+
new MixedType(),
166+
false,
167+
null,
168+
false,
169+
null
170+
);
171+
break;
172+
case 'get':
173+
$params[] = new DummyParameter(
174+
'index',
175+
new UnionType([new StringType(), new IntegerType()]),
176+
true,
177+
null,
178+
false,
179+
null
180+
);
181+
break;
182+
}
183+
184+
return $params;
185+
}
186+
187+
/**
188+
* Get Magic Methods return type.
189+
*
190+
* @return Type
191+
*/
192+
private function getReturnType(): Type
193+
{
194+
switch ($this->getMethodNamePrefix()) {
195+
case 'set':
196+
case 'uns':
197+
$returnType = new ObjectType($this->classReflection->getName());
198+
break;
199+
case 'has':
200+
$returnType = new BooleanType();
201+
break;
202+
default:
203+
$returnType = new MixedType();
204+
break;
205+
}
206+
207+
return $returnType;
208+
}
209+
210+
/**
211+
* Get if method is deprecated.
212+
*
213+
* @return TrinaryLogic
214+
*/
215+
public function isDeprecated(): TrinaryLogic
216+
{
217+
return TrinaryLogic::createNo();
218+
}
219+
220+
/**
221+
* Get deprecated description.
222+
*
223+
* @return string|null
224+
*/
225+
public function getDeprecatedDescription(): ?string
226+
{
227+
return null;
228+
}
229+
230+
/**
231+
* Get if method is final.
232+
*
233+
* @return TrinaryLogic
234+
*/
235+
public function isFinal(): TrinaryLogic
236+
{
237+
return TrinaryLogic::createNo();
238+
}
239+
240+
/**
241+
* Get if method is internal.
242+
*
243+
* @return TrinaryLogic
244+
*/
245+
public function isInternal(): TrinaryLogic
246+
{
247+
return TrinaryLogic::createNo();
248+
}
249+
250+
/**
251+
* Get method throw type.
252+
*
253+
* @return Type|null
254+
*/
255+
public function getThrowType(): ?Type
256+
{
257+
return new VoidType();
258+
}
259+
260+
/**
261+
* Get if method has side effect.
262+
*
263+
* @return TrinaryLogic
264+
*/
265+
public function hasSideEffects(): TrinaryLogic
266+
{
267+
return TrinaryLogic::createYes();
268+
}
269+
}

dev/tests/static/framework/Magento/TestFramework/CodingStandard/Tool/PhpStan.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class PhpStan implements ToolInterface
1919
*
2020
* @see https://github.com/phpstan/phpstan#rule-levels
2121
*/
22-
private const RULE_LEVEL = 0;
22+
private const RULE_LEVEL = 1;
2323

2424
/**
2525
* Memory limit required by PHPStan for full Magento project scan.

0 commit comments

Comments
 (0)