Skip to content

Commit ae19b1b

Browse files
committed
Handle subject return type
1 parent e6a4cb9 commit ae19b1b

File tree

9 files changed

+125
-17
lines changed

9 files changed

+125
-17
lines changed

extension.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ services:
44
tags: [phpstan.broker.methodsClassReflectionExtension]
55

66
extensions:
7-
phpspec: Proget\PHPStan\PhpSpec\DependencyInjection\PhpSpecDynamicMethodReturnTypeExtensionCreator
7+
phpspec: Proget\PHPStan\PhpSpec\DependencyInjection\CollaboratorExtension
88

99
parameters:
1010
specDirs:

spec/Proget/Tests/PHPStan/PhpSpec/FooSpec.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Proget\Tests\PHPStan\PhpSpec\Baz;
88
use Proget\Tests\PHPStan\PhpSpec\Foo;
99
use PhpSpec\ObjectBehavior;
10+
use Proget\Tests\PHPStan\PhpSpec\ValueObject;
1011

1112
class FooSpec extends ObjectBehavior
1213
{
@@ -26,4 +27,13 @@ public function it_should_make_baz(Baz $baz): void
2627

2728
$this->makeBaz()->shouldBe(123);
2829
}
30+
31+
public function it_should_make_value_object_with_provided_string(): void
32+
{
33+
$vo = $this->makeValueObject('php-ml is awesome'); // little product placement XD
34+
35+
$vo->shouldBeAnInstanceOf(ValueObject::class);
36+
$vo->string()->shouldEqual('php-ml is awesome');
37+
$vo->copy()->copy()->string()->shouldEqual('php-ml is awesome');
38+
}
2939
}

src/DependencyInjection/PhpSpecDynamicMethodReturnTypeExtensionCreator.php renamed to src/DependencyInjection/CollaboratorExtension.php

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,34 @@
88
use Nette\DI\ServiceDefinition;
99
use Proget\PHPStan\PhpSpec\Extractor\CollaboratorExtractor;
1010
use Proget\PHPStan\PhpSpec\Locator\SpecClassLocator;
11-
use Proget\PHPStan\PhpSpec\Reflection\CollaboratorDynamicMethodReturnTypeExtension;
11+
use Proget\PHPStan\PhpSpec\Type\CollaboratorDynamicMethodReturnTypeExtension;
1212

13-
final class PhpSpecDynamicMethodReturnTypeExtensionCreator extends CompilerExtension
13+
final class CollaboratorExtension extends CompilerExtension
1414
{
1515
public function beforeCompile()
1616
{
1717
$builder = $this->getContainerBuilder();
1818
$workingDir = $this->getContainerBuilder()->parameters['currentWorkingDirectory'];
19+
1920
$specClasses = (new SpecClassLocator())->locate(array_map(function (string $dir) use ($workingDir) {
2021
return $workingDir.DIRECTORY_SEPARATOR.ltrim($dir, DIRECTORY_SEPARATOR);
2122
}, $this->getContainerBuilder()->parameters['specDirs']));
2223

23-
foreach ((new CollaboratorExtractor())->extract($specClasses) as $class) {
24-
$definition = new ServiceDefinition();
25-
$definition->addTag('phpstan.broker.dynamicMethodReturnTypeExtension');
26-
$definition->setType(CollaboratorDynamicMethodReturnTypeExtension::class);
27-
$definition->setArguments([$class]);
28-
24+
foreach ((new CollaboratorExtractor())->extract($specClasses) as $collaboratorClass) {
2925
$builder->addDefinition(
30-
'collaborator.'.strtolower((string) preg_replace('/[^a-zA-Z0-9]+/', '', $class)),
31-
$definition
26+
'collaborator.'.strtolower((string) preg_replace('/[^a-zA-Z0-9]+/', '', $collaboratorClass)),
27+
$this->createCollaboratorDefinition($collaboratorClass)
3228
);
3329
}
3430
}
31+
32+
private function createCollaboratorDefinition(string $collaboratorClass): ServiceDefinition
33+
{
34+
$definition = new ServiceDefinition();
35+
$definition->addTag('phpstan.broker.dynamicMethodReturnTypeExtension');
36+
$definition->setType(CollaboratorDynamicMethodReturnTypeExtension::class);
37+
$definition->setArguments([$collaboratorClass]);
38+
39+
return $definition;
40+
}
3541
}

src/Locator/SpecClassLocator.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99

1010
final class SpecClassLocator
1111
{
12+
/**
13+
* @param string[] $dirs
14+
*
15+
* @return string[]
16+
*/
1217
public function locate(array $dirs): array
1318
{
1419
$finder = (new Finder())->in($dirs)->name('*.php');

src/Reflection/ObjectBehaviorMethodsClassReflectionExtension.php

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@
99
use PhpSpec\Locator\ResourceLocator;
1010
use PhpSpec\ObjectBehavior;
1111
use PhpSpec\Util\Filesystem;
12-
use PhpSpec\Wrapper\Subject;
1312
use PHPStan\Analyser\OutOfClassScope;
1413
use PHPStan\Broker\Broker;
1514
use PHPStan\Reflection\BrokerAwareExtension;
1615
use PHPStan\Reflection\ClassReflection;
1716
use PHPStan\Reflection\MethodReflection;
1817
use PHPStan\Reflection\MethodsClassReflectionExtension;
19-
use PHPStan\Type\ObjectType;
2018
use Proget\PHPStan\PhpSpec\Exception\SpecSourceClassNotFound;
19+
use Proget\PHPStan\PhpSpec\Type\SubjectType;
2120

2221
final class ObjectBehaviorMethodsClassReflectionExtension implements MethodsClassReflectionExtension, BrokerAwareExtension
2322
{
@@ -63,7 +62,13 @@ public function getMethod(ClassReflection $classReflection, string $methodName):
6362
if (count($resources) === 0) {
6463
throw new SpecSourceClassNotFound(sprintf('Source class from %s not found', $classReflection->getName()));
6564
}
66-
$srcClassReflection = $this->broker->getClass($resources[0]->getSrcClassname());
65+
66+
$className = $resources[0]->getSrcClassname();
67+
if (!class_exists($className)) {
68+
throw new SpecSourceClassNotFound(sprintf('Spec source class %s not found', $className));
69+
}
70+
71+
$srcClassReflection = $this->broker->getClass($className);
6772

6873
$method = $srcClassReflection->getNativeMethod($methodName);
6974
$this->replaceReturnType($method);
@@ -76,11 +81,11 @@ private function replaceReturnType(MethodReflection $method): void
7681
$methodReflection = new \ReflectionClass($method);
7782
$returnType = $methodReflection->getProperty('nativeReturnType');
7883
$returnType->setAccessible(true);
79-
$returnType->setValue($method, new ObjectType(Subject::class));
84+
$returnType->setValue($method, new SubjectType($returnType->getValue($method)));
8085

8186
$nativeReturnType = $methodReflection->getProperty('returnType');
8287
$nativeReturnType->setAccessible(true);
83-
$nativeReturnType->setValue($method, new ObjectType(Subject::class));
88+
$nativeReturnType->setValue($method, new SubjectType($nativeReturnType->getValue($method)));
8489

8590
$variants = $methodReflection->getProperty('variants');
8691
$variants->setAccessible(true);

src/Reflection/CollaboratorDynamicMethodReturnTypeExtension.php renamed to src/Type/CollaboratorDynamicMethodReturnTypeExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace Proget\PHPStan\PhpSpec\Reflection;
5+
namespace Proget\PHPStan\PhpSpec\Type;
66

77
use PhpParser\Node\Expr\MethodCall;
88
use PHPStan\Analyser\Scope;

src/Type/SubjectType.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Proget\PHPStan\PhpSpec\Type;
6+
7+
use PhpSpec\Wrapper\Subject;
8+
use PHPStan\Reflection\ClassMemberAccessAnswerer;
9+
use PHPStan\Reflection\MethodReflection;
10+
use PHPStan\Type\ObjectType;
11+
use PHPStan\Type\Type;
12+
use PHPStan\Type\VerbosityLevel;
13+
14+
final class SubjectType extends ObjectType
15+
{
16+
/**
17+
* @var Type
18+
*/
19+
private $wrappedType;
20+
21+
public function __construct(Type $wrappedType)
22+
{
23+
parent::__construct(Subject::class);
24+
$this->wrappedType = $wrappedType;
25+
}
26+
27+
public function hasMethod(string $methodName): bool
28+
{
29+
if ($this->wrappedType->hasMethod($methodName)) {
30+
return true;
31+
}
32+
33+
return parent::hasMethod($methodName);
34+
}
35+
36+
public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection
37+
{
38+
if ($this->wrappedType->hasMethod($methodName)) {
39+
return $this->wrappedType->getMethod($methodName, $scope);
40+
}
41+
42+
return parent::getMethod($methodName, $scope);
43+
}
44+
45+
public function describe(VerbosityLevel $level): string
46+
{
47+
return sprintf('%s<%s>', parent::describe($level), $this->wrappedType->describe($level));
48+
}
49+
}

tests/Foo.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,9 @@ public function makeBaz(): int
2525
{
2626
return $this->baz->make();
2727
}
28+
29+
public function makeValueObject(string $string): ValueObject
30+
{
31+
return new ValueObject($string);
32+
}
2833
}

tests/ValueObject.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Proget\Tests\PHPStan\PhpSpec;
6+
7+
final class ValueObject
8+
{
9+
/**
10+
* @var string
11+
*/
12+
private $string;
13+
14+
public function __construct(string $string)
15+
{
16+
$this->string = $string;
17+
}
18+
19+
public function string(): string
20+
{
21+
return $this->string;
22+
}
23+
24+
public function copy(): self
25+
{
26+
return new self($this->string);
27+
}
28+
}

0 commit comments

Comments
 (0)