Skip to content

Commit 19b636e

Browse files
author
oleksandrkravchuk
committed
PHPStan add support of magic methods of Data Object.
Add support of magic mathods of Session Manager (which uses Data Object as a container). Increase PHPStan level to 1. Add tests to test DataObjectReflectionExtension.
1 parent b307274 commit 19b636e

File tree

10 files changed

+618
-9
lines changed

10 files changed

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

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.

dev/tests/static/framework/tests/unit/testsuite/Magento/PhpStan/Formatters/FilteredErrorFormatterTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function testFormatErrors(
3838
string $expected
3939
): void {
4040
$formatter = new FilteredErrorFormatter(
41-
new FuzzyRelativePathHelper(self::DIRECTORY_PATH, '/', []),
41+
new FuzzyRelativePathHelper(self::DIRECTORY_PATH, [], '/'),
4242
false,
4343
false,
4444
false,
@@ -48,6 +48,7 @@ public function testFormatErrors(
4848
$analysisResult = new AnalysisResult(
4949
$fileErrors,
5050
[],
51+
[],
5152
false,
5253
false,
5354
null

0 commit comments

Comments
 (0)