Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ services:
factory: PHPStan\Type\Symfony\EnvelopeReturnTypeExtension
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]

# BrowserKitAssertionTrait::getClient() return type
-
class: PHPStan\Type\Symfony\BrowserKitAssertionTraitReturnTypeExtension
tags: [phpstan.broker.expressionTypeResolverExtension]

# Messenger HandleTrait::handle() return type
-
class: PHPStan\Type\Symfony\MessengerHandleTraitReturnTypeExtension
Expand Down
72 changes: 72 additions & 0 deletions src/Type/Symfony/BrowserKitAssertionTraitReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Symfony;

use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use PHPStan\Analyser\Scope;
use PHPStan\Type\ExpressionTypeResolverExtension;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\UnionType;
use function count;

final class BrowserKitAssertionTraitReturnTypeExtension implements ExpressionTypeResolverExtension
{

private const TRAIT_NAME = 'Symfony\Bundle\FrameworkBundle\Test\BrowserKitAssertionsTrait';
private const TRAIT_METHOD_NAME = 'getclient';

public function getType(Expr $expr, Scope $scope): ?Type
{
if ($this->isSupported($expr, $scope)) {
$args = $expr->getArgs();
if (count($args) > 0) {
return TypeCombinator::intersect(
$scope->getType($args[0]->value),
new UnionType([
new ObjectType('Symfony\Component\BrowserKit\AbstractBrowser'),
new NullType(),
]),
);
}

return new ObjectType('Symfony\Component\BrowserKit\AbstractBrowser');
}

return null;
}

/**
* @phpstan-assert-if-true =MethodCall $expr
*/
private function isSupported(Expr $expr, Scope $scope): bool
{
if (!($expr instanceof MethodCall) || !($expr->name instanceof Identifier) || $expr->name->toLowerString() !== self::TRAIT_METHOD_NAME) {
return false;
}

if (!$scope->isInClass()) {
return false;
}

$methodReflection = $scope->getMethodReflection($scope->getType($expr->var), $expr->name->toString());
if ($methodReflection === null) {
return false;
}

$reflectionClass = $methodReflection->getDeclaringClass()->getNativeReflection();
if (!$reflectionClass->hasMethod(self::TRAIT_METHOD_NAME)) {
return false;
}

$traitMethodReflection = $reflectionClass->getMethod(self::TRAIT_METHOD_NAME);
$declaringClassReflection = $traitMethodReflection->getBetterReflection()->getDeclaringClass();

return $declaringClassReflection->getName() === self::TRAIT_NAME;
}

}
12 changes: 8 additions & 4 deletions src/Type/Symfony/MessengerHandleTraitReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,22 +66,26 @@ private function getMessageMap(): MessageMap
*/
private function isSupported(Expr $expr, Scope $scope): bool
{
if (!($expr instanceof MethodCall) || !($expr->name instanceof Identifier) || $expr->name->name !== self::TRAIT_METHOD_NAME) {
if (!($expr instanceof MethodCall) || !($expr->name instanceof Identifier) || $expr->name->toLowerString() !== self::TRAIT_METHOD_NAME) {
return false;
}

if (!$scope->isInClass()) {
return false;
}

$reflectionClass = $scope->getClassReflection()->getNativeReflection();
$methodReflection = $scope->getMethodReflection($scope->getType($expr->var), $expr->name->toString());
if ($methodReflection === null) {
return false;
}

$reflectionClass = $methodReflection->getDeclaringClass()->getNativeReflection();
if (!$reflectionClass->hasMethod(self::TRAIT_METHOD_NAME)) {
return false;
}

$methodReflection = $reflectionClass->getMethod(self::TRAIT_METHOD_NAME);
$declaringClassReflection = $methodReflection->getBetterReflection()->getDeclaringClass();
$traitMethodReflection = $reflectionClass->getMethod(self::TRAIT_METHOD_NAME);
$declaringClassReflection = $traitMethodReflection->getBetterReflection()->getDeclaringClass();

return $declaringClassReflection->getName() === self::TRAIT_NAME;
}
Expand Down
1 change: 1 addition & 0 deletions tests/Type/Symfony/ExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class ExtensionTest extends TypeInferenceTestCase
/** @return mixed[] */
public function dataFileAsserts(): iterable
{
yield from $this->gatherAssertTypes(__DIR__ . '/data/browserkit_assertion_trait.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/messenger_handle_trait.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/envelope_all.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/header_bag_get.php');
Expand Down
26 changes: 26 additions & 0 deletions tests/Type/Symfony/data/browserkit_assertion_trait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php declare(strict_types = 1);

namespace BrowserKitAssertionTrait;

use Symfony\Bundle\FrameworkBundle\Test\BrowserKitAssertionsTrait;
use Symfony\Component\BrowserKit\AbstractBrowser;
use function PHPStan\Testing\assertType;

class Foo {
use BrowserKitAssertionsTrait;

/**
* @param mixed $mixed
*/
public function test(AbstractBrowser $browser, ?AbstractBrowser $nullableBrowser, $mixed)
{
assertType('Symfony\Component\BrowserKit\AbstractBrowser', $this->getClient());
assertType('null', $this->getClient(null));
assertType('Symfony\Component\BrowserKit\AbstractBrowser', $this->getClient($browser));
assertType('Symfony\Component\BrowserKit\AbstractBrowser|null', $this->getClient($nullableBrowser));
assertType('Symfony\Component\BrowserKit\AbstractBrowser|null', $this->getClient($mixed));

assertType('Symfony\Component\BrowserKit\AbstractBrowser', $this->getclient());
assertType('mixed', $mixed->getClient());
}
}