Skip to content

Commit 609c91f

Browse files
authored
Merge pull request #47 from shochdoerfer/feature/objectmanager_unittests
Add type for ObjectManager helper for tests
2 parents cd2da68 + ce9061b commit 609c91f

10 files changed

+508
-8
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,17 @@ shipped by Magento. Once those problems are fixed in Magento, those mocks can be
4343
### TestFramework autoloader
4444
The extension provides an autoloader for `Magento\TestFramework` classes to let you run PHPStan also against your test classes.
4545

46+
#### TestFramework ObjectManager type hints
47+
A type extension was added so that `Magento\Framework\TestFramework\Unit\Helper\ObjectManager` calls return the correct return type.
48+
Additionally, a PHPStan rule was added to check that only `Magento\Framework\Data\Collection` sub classes can be passed to
49+
`Magento\Framework\TestFramework\Unit\Helper\ObjectManager::getCollectionMock()`.
50+
4651
### ObjectManager type hints
47-
A type extension was added so that `ObjectManager` calls return the correct return type.
52+
A type extension was added so that `Magento\Framework\App\ObjectManager` calls return the correct return type.
4853

4954
### Support for magic method calls
50-
For some classes like the `DataObject` or the `SessionManager` logic was added to be able to support magic method calls.
55+
For some classes like the `Magento\Framework\DataObject` or `Magento\Framework\Session\SessionManager` logic was added
56+
to be able to support magic method calls.
5157

5258
### PHPStan rules
5359

extension.neon

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ services:
2020
class: bitExpert\PHPStan\Magento\Type\ObjectManagerDynamicReturnTypeExtension
2121
tags:
2222
- phpstan.broker.dynamicMethodReturnTypeExtension
23+
-
24+
class: bitExpert\PHPStan\Magento\Type\TestFrameworkObjectManagerDynamicReturnTypeExtension
25+
tags:
26+
- phpstan.broker.dynamicMethodReturnTypeExtension
2327
-
2428
class: bitExpert\PHPStan\Magento\Reflection\Framework\Session\SessionManagerMagicMethodReflectionExtension
2529
tags:
@@ -28,8 +32,11 @@ services:
2832
class: bitExpert\PHPStan\Magento\Reflection\Framework\DataObjectMagicMethodReflectionExtension
2933
tags:
3034
- phpstan.broker.methodsClassReflectionExtension
35+
-
36+
class: bitExpert\PHPStan\Magento\Rules\GetCollectionMockMethodNeedsCollectionSubclassRule
37+
tags:
38+
- phpstan.rules.rule
3139
-
3240
class: bitExpert\PHPStan\Magento\Rules\AbstractModelRetrieveCollectionViaFactoryRule
3341
-
3442
class: bitExpert\PHPStan\Magento\Rules\AbstractModelUseServiceContractRule
35-
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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\Rules;
14+
15+
use bitExpert\PHPStan\Magento\Type\TestFrameworkObjectManagerDynamicReturnTypeExtension;
16+
use PhpParser\Node;
17+
use PhpParser\Node\Expr\MethodCall;
18+
use PHPStan\Analyser\Scope;
19+
use PHPStan\Rules\Rule;
20+
use PHPStan\ShouldNotHappenException;
21+
use PHPStan\Type\ErrorType;
22+
use PHPStan\Type\ObjectType;
23+
use PHPStan\Type\VerbosityLevel;
24+
25+
/**
26+
* \Magento\Framework\TestFramework\Unit\Helper\ObjectManager::getCollectionMock() needs first parameter to extend
27+
* \Magento\Framework\Data\Collection
28+
*
29+
* @implements Rule<MethodCall>
30+
*/
31+
class GetCollectionMockMethodNeedsCollectionSubclassRule implements Rule
32+
{
33+
/**
34+
* @return string
35+
*/
36+
public function getNodeType(): string
37+
{
38+
return MethodCall::class;
39+
}
40+
41+
/**
42+
* @param Node $node
43+
* @param Scope $scope
44+
* @return (string|\PHPStan\Rules\RuleError)[] errors
45+
* @throws ShouldNotHappenException
46+
*/
47+
public function processNode(Node $node, Scope $scope): array
48+
{
49+
if (!$node instanceof MethodCall) {
50+
throw new ShouldNotHappenException();
51+
}
52+
53+
if (!$node->name instanceof Node\Identifier) {
54+
return [];
55+
}
56+
57+
if ($node->name->name !== 'getCollectionMock') {
58+
return [];
59+
}
60+
61+
$dynReturnTypeExt = new TestFrameworkObjectManagerDynamicReturnTypeExtension();
62+
63+
$type = $scope->getType($node->var);
64+
$isAbstractModelType = (new ObjectType($dynReturnTypeExt->getClass()))->isSuperTypeOf($type);
65+
if (!$isAbstractModelType->yes()) {
66+
return [];
67+
}
68+
69+
// the return type check is done in TestFrameworkObjectManagerDynamicReturnTypeExtension. When an ErrorType
70+
// is returned, it's an indication that the type check failed. That's why we only need to check for the
71+
// ErrorType here
72+
$returnType = $scope->getType($node);
73+
if (!$returnType->equals(new ErrorType())) {
74+
return [];
75+
}
76+
77+
$argType = $scope->getType($node->args[0]->value);
78+
return [
79+
sprintf(
80+
'%s does not extend \Magento\Framework\Data\Collection as required!',
81+
$argType->getValue()
82+
)
83+
];
84+
}
85+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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\Type;
14+
15+
use PhpParser\Node\Expr\MethodCall;
16+
use PhpParser\Node\Identifier;
17+
use PHPStan\Analyser\Scope;
18+
use PHPStan\Reflection\MethodReflection;
19+
use PHPStan\ShouldNotHappenException;
20+
use PHPStan\Type\ArrayType;
21+
use PHPStan\Type\Constant\ConstantStringType;
22+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
23+
use PHPStan\Type\ErrorType;
24+
use PHPStan\Type\MixedType;
25+
use PHPStan\Type\ObjectType;
26+
use PHPStan\Type\StringType;
27+
use PHPStan\Type\Type;
28+
use PHPStan\Type\TypeCombinator;
29+
30+
class TestFrameworkObjectManagerDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
31+
{
32+
/**
33+
* @return string
34+
*/
35+
public function getClass(): string
36+
{
37+
return 'Magento\Framework\TestFramework\Unit\Helper\ObjectManager';
38+
}
39+
40+
/**
41+
* @param MethodReflection $methodReflection
42+
* @return bool
43+
*/
44+
public function isMethodSupported(MethodReflection $methodReflection): bool
45+
{
46+
return in_array(
47+
$methodReflection->getName(),
48+
['getObject', 'getConstructArguments', 'getCollectionMock'],
49+
true
50+
);
51+
}
52+
53+
/**
54+
* @param MethodReflection $methodReflection
55+
* @param MethodCall $methodCall
56+
* @param Scope $scope
57+
* @return Type
58+
*/
59+
public function getTypeFromMethodCall(
60+
MethodReflection $methodReflection,
61+
MethodCall $methodCall,
62+
Scope $scope
63+
): Type {
64+
$methodName = ($methodCall->name instanceof Identifier) ? $methodCall->name->toString() : $methodCall->name;
65+
switch ($methodName) {
66+
case 'getObject':
67+
return $this->getTypeForGetObjectMethodCall($methodReflection, $methodCall, $scope);
68+
case 'getConstructArguments':
69+
return $this->getTypeForGetConstructArgumentsMethodCall($methodReflection, $methodCall, $scope);
70+
case 'getCollectionMock':
71+
return $this->getTypeForGetCollectionMockMethodCall($methodReflection, $methodCall, $scope);
72+
default:
73+
return new ErrorType();
74+
}
75+
}
76+
77+
/**
78+
* @param MethodReflection $methodReflection
79+
* @param MethodCall $methodCall
80+
* @param Scope $scope
81+
* @return Type
82+
*/
83+
private function getTypeForGetObjectMethodCall(
84+
MethodReflection $methodReflection,
85+
MethodCall $methodCall,
86+
Scope $scope
87+
): Type {
88+
$mixedType = new MixedType();
89+
if (count($methodCall->args) === 0) {
90+
return $mixedType;
91+
}
92+
93+
$argType = $scope->getType($methodCall->args[0]->value);
94+
if (!$argType instanceof ConstantStringType) {
95+
return $mixedType;
96+
}
97+
return TypeCombinator::addNull(new ObjectType($argType->getValue()));
98+
}
99+
100+
/**
101+
* @param MethodReflection $methodReflection
102+
* @param MethodCall $methodCall
103+
* @param Scope $scope
104+
* @return Type
105+
*/
106+
private function getTypeForGetConstructArgumentsMethodCall(
107+
MethodReflection $methodReflection,
108+
MethodCall $methodCall,
109+
Scope $scope
110+
): Type {
111+
return TypeCombinator::addNull(new ArrayType(new StringType(), new MixedType()));
112+
}
113+
114+
/**
115+
* @param MethodReflection $methodReflection
116+
* @param MethodCall $methodCall
117+
* @param Scope $scope
118+
* @return Type
119+
*/
120+
private function getTypeForGetCollectionMockMethodCall(
121+
MethodReflection $methodReflection,
122+
MethodCall $methodCall,
123+
Scope $scope
124+
): Type {
125+
$className = $scope->getType($methodCall->args[0]->value)->getValue();
126+
if (!is_subclass_of($className, 'Magento\Framework\Data\Collection')) {
127+
return new \PHPStan\Type\ErrorType();
128+
}
129+
130+
$type = TypeCombinator::intersect(
131+
new ObjectType($className),
132+
new ObjectType('PHPUnit\Framework\MockObject\MockObject')
133+
);
134+
135+
return $type;
136+
}
137+
}

tests/bitExpert/PHPStan/Magento/Rules/AbstractModelRetrieveCollectionViaFactoryRuleUnitTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public function checkCaughtException(): void
3535
[
3636
'Collections should be used directly via factory, not via ' .
3737
SampleModel::class . '::getCollection() method',
38-
04
38+
4
3939
]
4040
]);
4141
}

tests/bitExpert/PHPStan/Magento/Rules/AbstractModelUseServiceContractRuleUnitTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ public function checkCaughtExceptions(): void
3434
$this->analyse([__DIR__ . '/Helper/service_contract.php'], [
3535
[
3636
'Use service contracts to persist entities in favour of ' . SampleModel::class . '::save() method',
37-
04,
37+
4,
3838
],
3939
[
4040
'Use service contracts to persist entities in favour of ' . SampleModel::class . '::delete() method',
41-
05,
41+
5,
4242
],
4343
[
4444
'Use service contracts to persist entities in favour of ' . SampleModel::class . '::load() method',
45-
06,
45+
6,
4646
],
4747
]);
4848
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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\Rules;
14+
15+
use bitExpert\PHPStan\Magento\Rules\Helper\SampleModel;
16+
use bitExpert\PHPStan\Magento\Type\TestFrameworkObjectManagerDynamicReturnTypeExtension;
17+
use PHPStan\Rules\Rule;
18+
use PHPStan\Testing\RuleTestCase;
19+
20+
/**
21+
* @extends \PHPStan\Testing\RuleTestCase<GetCollectionMockMethodNeedsCollectionSubclassRule>
22+
*/
23+
class GetCollectionMockMethodNeedsCollectionSubclassRuleUnitTest extends RuleTestCase
24+
{
25+
protected function getRule(): Rule
26+
{
27+
return new GetCollectionMockMethodNeedsCollectionSubclassRule();
28+
}
29+
30+
/**
31+
* @return \PHPStan\Type\DynamicMethodReturnTypeExtension[]
32+
*/
33+
public function getDynamicMethodReturnTypeExtensions() : array
34+
{
35+
return [
36+
new TestFrameworkObjectManagerDynamicReturnTypeExtension(),
37+
];
38+
}
39+
40+
/**
41+
* @test
42+
*/
43+
public function checkCaughtExceptions(): void
44+
{
45+
$this->analyse([__DIR__ . '/Helper/objectmanager_collectionmock.php'], [
46+
[
47+
'DateTime does not extend \Magento\Framework\Data\Collection as required!',
48+
7,
49+
],
50+
]);
51+
}
52+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
$objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager(
4+
new class extends PHPUnit\Framework\TestCase {
5+
}
6+
);
7+
$mock = $objectManager->getCollectionMock(\DateTime::class, []);

tests/bitExpert/PHPStan/Magento/Type/ObjectManagerDynamicReturnTypeExtensionUnitTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
namespace bitExpert\PHPStan\Magento\Type;
1414

15-
use bitExpert\PHPStan\Magento\Type\ObjectManagerDynamicReturnTypeExtension;
1615
use PhpParser\Node\Expr\MethodCall;
1716
use PHPStan\Analyser\Scope;
1817
use PHPStan\Reflection\MethodReflection;
@@ -43,6 +42,7 @@ public function isMethodSupportedDataprovider(): array
4342
['bar', false],
4443
];
4544
}
45+
4646
/**
4747
* @test
4848
* @dataProvider isMethodSupportedDataprovider

0 commit comments

Comments
 (0)