Skip to content

Commit

Permalink
Merge pull request #92 from localheinz/feature/will-implement
Browse files Browse the repository at this point in the history
Enhancement: Add support for willImplement()
  • Loading branch information
localheinz authored Dec 27, 2019
2 parents 53ce192 + 7da6080 commit defdf4d
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 11 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

For a full diff see [`0.5.1...master`][0.5.1...master].

### Added

* Support for `willImplement()` ([#92]), by [@localheinz]

## [`0.5.1`][0.5.1]

For a full diff see [`0.5.0...0.5.1`][0.5.0...0.5.1].
Expand Down Expand Up @@ -81,6 +85,7 @@ For a full diff see [`afd6fd9...0.1`][afd6fd9...0.1].

[#67]: https://github.com/Jan0707/phpstan-prophecy/pull/67
[#79]: https://github.com/Jan0707/phpstan-prophecy/pull/79
[#92]: https://github.com/Jan0707/phpstan-prophecy/pull/92

[@localheinz]: https://github.com/localheinz
[@PedroTroller]: https://github.com/PedroTroller
11 changes: 7 additions & 4 deletions src/Extension/ObjectProphecyRevealDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
return $parametersAcceptor->getReturnType();
}

return TypeCombinator::intersect(
new ObjectType($calledOnType->getProphesizedClass()),
$parametersAcceptor->getReturnType()
);
$types = \array_map(static function (string $class): ObjectType {
return new ObjectType($class);
}, $calledOnType->getProphesizedClasses());

$types[] = $parametersAcceptor->getReturnType();

return TypeCombinator::intersect(...$types);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace JanGregor\Prophecy\Extension;

use JanGregor\Prophecy\Type\ObjectProphecyType;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;

class ObjectProphecyWillImplementDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
public function getClass(): string
{
return 'Prophecy\Prophecy\ObjectProphecy';
}

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

public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
$parametersAcceptor = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants());

$calledOnType = $scope->getType($methodCall->var);

$prophecyType = $parametersAcceptor->getReturnType();

if (!$calledOnType instanceof ObjectProphecyType) {
return $prophecyType;
}

if (0 === \count($methodCall->args)) {
return $prophecyType;
}

$argType = $scope->getType($methodCall->args[0]->value);

if (!($argType instanceof ConstantStringType)) {
return $prophecyType;
}

$class = $argType->getValue();

if (!($prophecyType instanceof TypeWithClassName)) {
throw new ShouldNotHappenException();
}

if ('static' === $class) {
return $prophecyType;
}

if ('self' === $class && null !== $scope->getClassReflection()) {
$class = $scope->getClassReflection()->getName();
}

$calledOnType->addProphesizedClass($class);

return $calledOnType;
}
}
31 changes: 24 additions & 7 deletions src/Type/ObjectProphecyType.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,46 @@
class ObjectProphecyType extends ObjectType
{
/**
* @var string
* @var string[]
*/
protected $prophesizedClass;
protected $prophesizedClasses;

public function __construct(string $prophesizedClass)
{
$this->prophesizedClass = $prophesizedClass;
$this->prophesizedClasses = [
$prophesizedClass,
];

parent::__construct('Prophecy\Prophecy\ObjectProphecy');
}

public static function __set_state(array $properties): Type
{
return new self($properties['prophesizedClass']);
return new self($properties['prophesizedClasses']);
}

public function describe(VerbosityLevel $level): string
{
return \sprintf('%s<%s>', parent::describe($level), $this->prophesizedClass);
return \sprintf(
'%s<%s>',
parent::describe($level),
\implode(
'&',
$this->prophesizedClasses
)
);
}

public function getProphesizedClass(): string
public function addProphesizedClass(string $prophesizedClass): void
{
return $this->prophesizedClass;
$this->prophesizedClasses[] = $prophesizedClass;
}

/**
* @return string[]
*/
public function getProphesizedClasses(): array
{
return $this->prophesizedClasses;
}
}
5 changes: 5 additions & 0 deletions src/extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ services:
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

-
class: JanGregor\Prophecy\Extension\ObjectProphecyWillImplementDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

-
class: JanGregor\Prophecy\Reflection\ProphecyMethodsClassReflectionExtension
tags:
Expand Down
10 changes: 10 additions & 0 deletions tests/Model/Bar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace JanGregor\Prophecy\Test\Model;

interface Bar
{
public function bar(): string;
}
5 changes: 5 additions & 0 deletions tests/Model/BaseModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,9 @@ public function doubleTheNumber(int $number)
{
return 2 * $number;
}

public function bar(Bar $bar): string
{
return $bar->bar();
}
}
10 changes: 10 additions & 0 deletions tests/Model/Foo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace JanGregor\Prophecy\Test\Model;

interface Foo
{
public function foo(): string;
}
18 changes: 18 additions & 0 deletions tests/Test/BaseModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace JanGregor\Prophecy\Test\Test;

use JanGregor\Prophecy\Test\Model\Bar;
use JanGregor\Prophecy\Test\Model\BaseModel;
use JanGregor\Prophecy\Test\Model\Foo;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy;
Expand Down Expand Up @@ -59,4 +61,20 @@ public function testProphesizedAttributesShouldAlsoWork(): void
self::assertEquals('bar', $subject->getFoo());
self::assertEquals(5, $subject->doubleTheNumber(2));
}

public function testWillImplementWorks(): void
{
$fooThatAlsoBars = $this->prophesize(Foo::class);

$fooThatAlsoBars->willImplement(Bar::class);

$fooThatAlsoBars
->bar()
->shouldBeCalled()
->willReturn('Oh');

$subject = new BaseModel();

self::assertSame('Oh', $subject->bar($fooThatAlsoBars->reveal()));
}
}

0 comments on commit defdf4d

Please sign in to comment.