Skip to content

Commit eaec4a3

Browse files
committed
Make DataObject extension stricter
Instead of accepting and returning mixed types, the extension now accepts and returns types based on the magic method documentation in the DataObject class.
1 parent a431803 commit eaec4a3

File tree

3 files changed

+235
-30
lines changed

3 files changed

+235
-30
lines changed

src/bitExpert/PHPStan/Magento/Reflection/AbstractMagicMethodReflectionExtension.php

Lines changed: 128 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,15 @@
1717
use PHPStan\Reflection\MethodReflection;
1818
use PHPStan\Reflection\MethodsClassReflectionExtension;
1919
use PHPStan\Reflection\Php\DummyParameter;
20-
use PHPStan\Reflection\TrivialParametersAcceptor;
20+
use PHPStan\Type\ArrayType;
21+
use PHPStan\Type\BooleanType;
2122
use PHPStan\Type\Generic\TemplateTypeMap;
23+
use PHPStan\Type\IntegerType;
2224
use PHPStan\Type\MixedType;
25+
use PHPStan\Type\NullType;
26+
use PHPStan\Type\ObjectType;
27+
use PHPStan\Type\StringType;
28+
use PHPStan\Type\UnionType;
2329

2430
abstract class AbstractMagicMethodReflectionExtension implements MethodsClassReflectionExtension
2531
{
@@ -30,47 +36,143 @@ abstract class AbstractMagicMethodReflectionExtension implements MethodsClassRef
3036
*/
3137
public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
3238
{
33-
if (strpos($methodName, 'get') === 0) {
34-
return $this->returnGetMagicMethodReflection($classReflection, $methodName);
39+
$methodPrefix = substr($methodName, 0, 3);
40+
switch ($methodPrefix) {
41+
case 'get':
42+
return $this->returnGetMagicMethod($classReflection, $methodName);
43+
case 'set':
44+
return $this->returnSetMagicMethod($classReflection, $methodName);
45+
case 'uns':
46+
return $this->returnUnsetMagicMethod($classReflection, $methodName);
47+
case 'has':
48+
return $this->returnHasMagicMethod($classReflection, $methodName);
49+
default:
50+
break;
3551
}
36-
37-
return $this->returnMagicMethodReflection($classReflection, $methodName);
3852
}
3953

4054
/**
41-
* Helper method to create magic method for get calls. The method call does not accept any parameter and will return
42-
* mixed type.
55+
* Helper method to create magic method reflection for get() calls.
4356
*
4457
* @param ClassReflection $classReflection
4558
* @param string $methodName
4659
* @return MethodReflection
4760
*/
48-
protected function returnGetMagicMethodReflection(
49-
ClassReflection $classReflection,
50-
string $methodName
51-
): MethodReflection {
52-
$variants = new TrivialParametersAcceptor();
61+
private function returnGetMagicMethod(ClassReflection $classReflection, string $methodName): MethodReflection
62+
{
63+
$params = [
64+
new DummyParameter(
65+
'key',
66+
new StringType(),
67+
true,
68+
null,
69+
false,
70+
null
71+
),
72+
new DummyParameter(
73+
'index',
74+
new UnionType([new StringType(), new IntegerType(), new NullType()]),
75+
true,
76+
null,
77+
false,
78+
null
79+
)
80+
];
81+
82+
$returnType = new MixedType();
83+
84+
$variants = new FunctionVariant(
85+
TemplateTypeMap::createEmpty(),
86+
null,
87+
$params,
88+
false,
89+
$returnType
90+
);
91+
5392
return new MagicMethodReflection($methodName, $classReflection, [$variants]);
5493
}
5594

56-
/**
57-
* Helper method to create magic reflection method for set, unset and has calls. Those method calls accept one
58-
* mixed parameter and return mixed type.
59-
*
60-
* @param ClassReflection $classReflection
61-
* @param string $methodName
62-
* @return MethodReflection
63-
*/
64-
protected function returnMagicMethodReflection(
65-
ClassReflection $classReflection,
66-
string $methodName
67-
): MethodReflection {
95+
private function returnSetMagicMethod(ClassReflection $classReflection, string $methodName): MethodReflection
96+
{
97+
$params = [
98+
new DummyParameter(
99+
'key',
100+
new UnionType([new StringType(), new ArrayType(new MixedType(), new MixedType())]),
101+
true,
102+
null,
103+
false,
104+
null
105+
),
106+
new DummyParameter(
107+
'value',
108+
new MixedType(),
109+
true,
110+
null,
111+
false,
112+
null
113+
)
114+
];
115+
116+
$returnType = new ObjectType($classReflection->getName());
117+
118+
$variants = new FunctionVariant(
119+
TemplateTypeMap::createEmpty(),
120+
null,
121+
$params,
122+
false,
123+
$returnType
124+
);
125+
126+
return new MagicMethodReflection($methodName, $classReflection, [$variants]);
127+
}
128+
129+
private function returnUnsetMagicMethod(ClassReflection $classReflection, string $methodName): MethodReflection
130+
{
131+
$params = [
132+
new DummyParameter(
133+
'key',
134+
new UnionType([new NullType(), new StringType(), new ArrayType(new MixedType(), new MixedType())]),
135+
true,
136+
null,
137+
false,
138+
null
139+
)
140+
];
141+
142+
$returnType = new ObjectType($classReflection->getName());
143+
144+
$variants = new FunctionVariant(
145+
TemplateTypeMap::createEmpty(),
146+
null,
147+
$params,
148+
false,
149+
$returnType
150+
);
151+
152+
return new MagicMethodReflection($methodName, $classReflection, [$variants]);
153+
}
154+
155+
private function returnHasMagicMethod(ClassReflection $classReflection, string $methodName): MethodReflection
156+
{
157+
$params = [
158+
new DummyParameter(
159+
'key',
160+
new StringType(),
161+
true,
162+
null,
163+
false,
164+
null
165+
)
166+
];
167+
168+
$returnType = new BooleanType();
169+
68170
$variants = new FunctionVariant(
69171
TemplateTypeMap::createEmpty(),
70172
null,
71-
[ new DummyParameter('name', new MixedType(), false, null, false, null),],
173+
$params,
72174
false,
73-
new MixedType()
175+
$returnType
74176
);
75177

76178
return new MagicMethodReflection($methodName, $classReflection, [$variants]);

src/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectMagicMethodReflectionExtension.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,9 @@ class DataObjectMagicMethodReflectionExtension extends AbstractMagicMethodReflec
2626
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
2727
{
2828
$parentClasses = $classReflection->getParentClassesNames();
29+
$parentClasses[] = $classReflection->getName();
2930

30-
$isDataObject = $classReflection->getName() === DataObject::class ||
31-
in_array(DataObject::class, $parentClasses, true);
32-
33-
return $isDataObject &&
31+
return in_array(DataObject::class, $parentClasses, true) &&
3432
in_array(substr($methodName, 0, 3), ['get', 'set', 'uns', 'has']);
3533
}
3634
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the phpstan-magento package.
5+
*
6+
* (c) bitExpert AG
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
declare(strict_types=1);
12+
13+
namespace bitExpert\PHPStan\Magento\Reflection\Framework;
14+
15+
use PHPStan\Reflection\ClassReflection;
16+
use PHPStan\Type\BooleanType;
17+
use PHPStan\Type\MixedType;
18+
use PHPStan\Type\ObjectType;
19+
use PHPStan\Type\StringType;
20+
use PHPStan\Type\UnionType;
21+
use PHPUnit\Framework\TestCase;
22+
23+
class DataObjectMagicMethodReflectionExtensionUnitTest extends TestCase
24+
{
25+
/**
26+
* @var DataObjectMagicMethodReflectionExtension
27+
*/
28+
private $extension;
29+
/**
30+
* @var ClassReflection|\PHPUnit\Framework\MockObject\MockObject
31+
*/
32+
private $classReflection;
33+
34+
protected function setUp(): void
35+
{
36+
$this->extension = new DataObjectMagicMethodReflectionExtension();
37+
$this->classReflection = $this->createMock(ClassReflection::class);
38+
}
39+
40+
/**
41+
* @test
42+
*/
43+
public function returnMagicMethodReflectionForGetMethod(): void
44+
{
45+
$methodReflection = $this->extension->getMethod($this->classReflection, 'getTest');
46+
47+
$variants = $methodReflection->getVariants();
48+
$params = $variants[0]->getParameters();
49+
50+
$this->assertCount(1, $variants);
51+
$this->assertInstanceOf(MixedType::class, $variants[0]->getReturnType());
52+
$this->assertCount(2, $params);
53+
$this->assertInstanceOf(StringType::class, $params[0]->getType());
54+
$this->assertInstanceOf(UnionType::class, $params[1]->getType());
55+
}
56+
57+
/**
58+
* @test
59+
*/
60+
public function returnMagicMethodReflectionForSetMethod(): void
61+
{
62+
$methodReflection = $this->extension->getMethod($this->classReflection, 'setTest');
63+
64+
$variants = $methodReflection->getVariants();
65+
$params = $variants[0]->getParameters();
66+
67+
$this->assertCount(1, $variants);
68+
$this->assertInstanceOf(ObjectType::class, $variants[0]->getReturnType());
69+
$this->assertCount(2, $params);
70+
$this->assertInstanceOf(UnionType::class, $params[0]->getType());
71+
$this->assertInstanceOf(MixedType::class, $params[1]->getType());
72+
}
73+
74+
/**
75+
* @test
76+
*/
77+
public function returnMagicMethodReflectionForUnsetMethod(): void
78+
{
79+
$methodReflection = $this->extension->getMethod($this->classReflection, 'unsetTest');
80+
81+
$variants = $methodReflection->getVariants();
82+
$params = $variants[0]->getParameters();
83+
84+
$this->assertCount(1, $variants);
85+
$this->assertInstanceOf(ObjectType::class, $variants[0]->getReturnType());
86+
$this->assertCount(1, $params);
87+
$this->assertInstanceOf(UnionType::class, $params[0]->getType());
88+
}
89+
90+
/**
91+
* @test
92+
*/
93+
public function returnMagicMethodReflectionForHasMethod(): void
94+
{
95+
$methodReflection = $this->extension->getMethod($this->classReflection, 'hasTest');
96+
97+
$variants = $methodReflection->getVariants();
98+
$params = $variants[0]->getParameters();
99+
100+
$this->assertCount(1, $variants);
101+
$this->assertInstanceOf(BooleanType::class, $variants[0]->getReturnType());
102+
$this->assertCount(1, $params);
103+
$this->assertInstanceOf(StringType::class, $params[0]->getType());
104+
}
105+
}

0 commit comments

Comments
 (0)