Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix mocking classes with new initializers in method and attribute params on PHP 8.1 #1301

Merged
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
},
"autoload-dev": {
"psr-4": {
"Fixture\\": "tests/Fixture/",
"test\\": "tests/"
},
"files": [
Expand Down
24 changes: 12 additions & 12 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1575,6 +1575,7 @@
<code><![CDATA[$method->getParameters()]]></code>
<code><![CDATA[$overrides[$class_name][$method->getName()]]]></code>
<code><![CDATA[$overrides[strtolower($class->getName())][$method->getName()]]]></code>
<code><![CDATA[$param->__toString()]]></code>
</MixedArgument>
<MixedArgumentTypeCoercion>
<code>$param</code>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,20 @@ protected function renderParams(Method $method, $config)

if (is_object($defaultValue)) {
$prefix = get_class($defaultValue);
if ($isPhp81 && enum_exists($prefix)) {
$prefix = var_export($defaultValue, true);
if ($isPhp81) {
if (enum_exists($prefix)) {
$prefix = var_export($defaultValue, true);
} elseif (
!$param->isDefaultValueConstant() &&
// "Parameter #1 [ <optional> F\Q\CN $a = new \F\Q\CN(param1, param2: 2) ]
preg_match(
'#<optional>\s.*?\s=\snew\s(.*?)\s]$#',
$param->__toString(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not intended to ever call __toString() directly. This should be a cast instead ((string)$param).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Bilge I agree,

However the reason for using __toString() instead was because $param is actually an object that is proxying these calls via __call method to the ReflectionParameter which was giving an error because the __toString expects a string return type.

public function __call($method, array $args)

$matches
) === 1
) {
$prefix = 'new ' . $matches[1];
}
}
} else {
$prefix = var_export($defaultValue, true);
Expand Down Expand Up @@ -158,7 +170,7 @@ private function renderMethodBody($method, $config)

$body .= "\$ret = {$invoke}(__FUNCTION__, \$argv);\n";

if (! in_array($method->getReturnType(), ['never','void'], true)) {
if (! in_array($method->getReturnType(), ['never', 'void'], true)) {
$body .= "return \$ret;\n";
}

Expand Down
18 changes: 18 additions & 0 deletions tests/Fixture/PHP81/A.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Fixture\PHP81;

class A
{
public function __construct(
private int $x = 1
) {
}

public function test(): int
{
return $this->x;
}
}
18 changes: 18 additions & 0 deletions tests/Fixture/PHP81/B.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Fixture\PHP81;

class B
{
public function __construct(
private int $x
) {
}

public function test(): int
{
return $this->x;
}
}
19 changes: 19 additions & 0 deletions tests/Fixture/PHP81/C.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Fixture\PHP81;

class C
{
public function __construct(
private int $x = 1,
private B $b = new B(1)
) {
}

public function test(): int
{
return $this->x + $this->b->test();
}
}
13 changes: 13 additions & 0 deletions tests/Fixture/PHP81/HandlerClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Fixture\PHP81;

class HandlerClass
{
public function doStuff(MockClass $mockClass): string
{
return $mockClass->test('test');
}
}
29 changes: 29 additions & 0 deletions tests/Fixture/PHP81/MockClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Fixture\PHP81;

use DateTime;

class MockClass
{
public function __construct(
public A $a = new A(),
protected B $b = new B(1),
private C $c = new C(b: new B(1), x: 2),
public DateTime $dateTime = new DateTime(),
) {
}

public function test(
string $msg,
#[SomeAttribute(param: new A())]
A $a = new A(),
B $b = new B(1),
C $c = new C(x: 2),
DateTime $dateTime = new DateTime(),
): string {
return $msg . ' - ' . $a->test() . ' - ' . $b->test() . ' - ' . $c->test() . ' - ' . $dateTime->format('Y-m-d');
}
}
14 changes: 14 additions & 0 deletions tests/Fixture/PHP81/SomeAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Fixture\PHP81;

#[\Attribute(\Attribute::TARGET_PARAMETER)]
class SomeAttribute
{
public function __construct(
public object $param = new stdClass()
) {
}
}
26 changes: 26 additions & 0 deletions tests/Unit/PHP81/Php81LanguageFeaturesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace test\Unit\PHP81;

use Fixture\PHP81\HandlerClass;
use Fixture\PHP81\MockClass;
use Mockery\Adapter\Phpunit\MockeryTestCase;

/**
* @requires PHP 8.1.0-dev
*/
class Php81LanguageFeaturesTest extends MockeryTestCase
{
public function testNewInitializerExpression()
{
$class = mock(MockClass::class)
->expects('test')
->with('test')
->andReturn('it works')
->getMock();

self::assertSame('it works', (new HandlerClass())->doStuff($class));
}
}