Skip to content

Commit a316610

Browse files
Return an intersection type when nameMock is used (#19)
1 parent 3ab03bd commit a316610

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed

extension.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ services:
5252
tags:
5353
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
5454

55+
-
56+
class: PHPStan\Mockery\Type\MockDynamicNamedMockReturnTypeExtension
57+
tags:
58+
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
59+
5560
-
5661
class: PHPStan\Mockery\PhpDoc\TypeNodeResolverExtension
5762
tags:
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Mockery\Type;
4+
5+
use PhpParser\Node\Expr\StaticCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\MethodReflection;
8+
use PHPStan\Type\Constant\ConstantStringType;
9+
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
10+
use PHPStan\Type\ObjectType;
11+
use PHPStan\Type\Type;
12+
use PHPStan\Type\TypeCombinator;
13+
14+
class MockDynamicNamedMockReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
15+
{
16+
17+
public function getClass(): string
18+
{
19+
return 'Mockery';
20+
}
21+
22+
public function isStaticMethodSupported(MethodReflection $methodReflection): bool
23+
{
24+
return $methodReflection->getName() === 'namedMock';
25+
}
26+
27+
public function getTypeFromStaticMethodCall(
28+
MethodReflection $methodReflection,
29+
StaticCall $methodCall,
30+
Scope $scope
31+
): Type
32+
{
33+
$defaultReturnType = new ObjectType('Mockery\\MockInterface');
34+
35+
$args = $methodCall->args;
36+
if (count($args) > 1) {
37+
array_shift($args);
38+
}
39+
40+
$types = [$defaultReturnType];
41+
foreach ($args as $arg) {
42+
$classType = $scope->getType($arg->value);
43+
if (!$classType instanceof ConstantStringType) {
44+
continue;
45+
}
46+
47+
$value = $classType->getValue();
48+
if (substr($value, 0, 6) === 'alias:') {
49+
$value = substr($value, 6);
50+
}
51+
if (substr($value, 0, 9) === 'overload:') {
52+
$value = substr($value, 9);
53+
}
54+
if (substr($value, -1) === ']' && strpos($value, '[') !== false) {
55+
$value = substr($value, 0, strpos($value, '['));
56+
}
57+
58+
if (strpos($value, ',') !== false) {
59+
$interfaceNames = explode(',', str_replace(' ', '', $value));
60+
} else {
61+
$interfaceNames = [$value];
62+
}
63+
64+
foreach ($interfaceNames as $name) {
65+
$types[] = new ObjectType($name);
66+
}
67+
}
68+
69+
return TypeCombinator::intersect(...$types);
70+
}
71+
72+
}

tests/Mockery/MockeryTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ public function testMakePartial(): void
113113
self::assertSame('foo', $fooMock->doFoo());
114114
}
115115

116+
public function testNamedMock(): void
117+
{
118+
$fooMock = \Mockery::namedMock('FooBar', Foo::class);
119+
$this->requireFoo($fooMock);
120+
121+
$fooMock->allows()->doFoo()->andReturns('foo');
122+
self::assertSame('foo', $fooMock->doFoo());
123+
}
124+
116125
private function requireFoo(Foo $foo): void
117126
{
118127
}

0 commit comments

Comments
 (0)