Skip to content
This repository has been archived by the owner on Jun 26, 2018. It is now read-only.

Commit

Permalink
Merge 5f1a8cc into 964957a
Browse files Browse the repository at this point in the history
  • Loading branch information
malarzm committed Dec 12, 2017
2 parents 964957a + 5f1a8cc commit 1b63139
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 0 deletions.
3 changes: 3 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ services:
-
class: Lookyman\PHPStan\Symfony\Type\ContainerInterfaceDynamicReturnTypeExtension
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
-
class: Lookyman\PHPStan\Symfony\Type\ControllerDynamicReturnTypeExtension
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
-
class: Lookyman\PHPStan\Symfony\Rules\ContainerInterfacePrivateServiceRule
tags: [phpstan.rules.rule]
Expand Down
54 changes: 54 additions & 0 deletions src/Type/ControllerDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types = 1);

namespace Lookyman\PHPStan\Symfony\Type;

use Lookyman\PHPStan\Symfony\ServiceMap;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\MethodCall;

final class ControllerDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
/**
* @var ServiceMap
*/
private $serviceMap;

public function __construct(ServiceMap $symfonyServiceMap)
{
$this->serviceMap = $symfonyServiceMap;
}

public function getClass(): string
{
return 'Symfony\Bundle\FrameworkBundle\Controller\Controller';
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'get';
}

public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): Type {
if (isset($methodCall->args[0])
&& $methodCall->args[0] instanceof Arg
) {
$service = $this->serviceMap->getServiceFromNode($methodCall->args[0]->value);
if ($service !== \null && !$service['synthetic']) {
return new ObjectType($service['class'] ?? $service['id']);
}
}
return $methodReflection->getReturnType();
}

}
87 changes: 87 additions & 0 deletions tests/Type/ControllerDynamicReturnTypeExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

declare(strict_types = 1);

namespace Lookyman\PHPStan\Symfony\Type;

use Lookyman\PHPStan\Symfony\ServiceMap;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPUnit\Framework\TestCase;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Scalar\String_;

/**
* @covers \Lookyman\PHPStan\Symfony\Type\ControllerDynamicReturnTypeExtension
*/
final class ControllerDynamicReturnTypeExtensionTest extends TestCase
{

public function testImplementsDynamicMethodReturnTypeExtension()
{
self::assertInstanceOf(
DynamicMethodReturnTypeExtension::class,
new ControllerDynamicReturnTypeExtension(new ServiceMap(__DIR__ . '/../container.xml'))
);
}

public function testGetClass()
{
$extension = new ControllerDynamicReturnTypeExtension(new ServiceMap(__DIR__ . '/../container.xml'));
self::assertEquals('Symfony\Bundle\FrameworkBundle\Controller\Controller', $extension->getClass());
}

public function testIsMethodSupported()
{
$methodGet = $this->createMock(MethodReflection::class);
$methodGet->expects(self::once())->method('getName')->willReturn('get');

$methodFoo = $this->createMock(MethodReflection::class);
$methodFoo->expects(self::once())->method('getName')->willReturn('foo');

$extension = new ControllerDynamicReturnTypeExtension(new ServiceMap(__DIR__ . '/../container.xml'));
self::assertTrue($extension->isMethodSupported($methodGet));
self::assertFalse($extension->isMethodSupported($methodFoo));
}

/**
* @dataProvider getTypeFromMethodCallProvider
*/
public function testGetTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Type $expectedType)
{
$extension = new ControllerDynamicReturnTypeExtension(new ServiceMap(__DIR__ . '/../container.xml'));
$type = $extension->getTypeFromMethodCall(
$methodReflection,
$methodCall,
$this->createMock(Scope::class)
);
self::assertEquals($expectedType, $type);
}

public function getTypeFromMethodCallProvider(): array
{
$notFoundType = $this->createMock(Type::class);

$methodReflectionNotFound = $this->createMock(MethodReflection::class);
$methodReflectionNotFound->expects(self::once())->method('getReturnType')->willReturn($notFoundType);

return [
'found' => [
$this->createMock(MethodReflection::class),
new MethodCall($this->createMock(Expr::class), '', [new Arg(new String_('withClass'))]),
new ObjectType('Foo'),
],
'notFound' => [
$methodReflectionNotFound,
new MethodCall($this->createMock(Expr::class), ''),
$notFoundType,
],
];
}

}

0 comments on commit 1b63139

Please sign in to comment.