Skip to content

Commit

Permalink
Merge pull request #736 from ostrolucky/interceptor-references
Browse files Browse the repository at this point in the history
Support by-reference params in interceptors
  • Loading branch information
Ocramius authored Feb 28, 2022
2 parents af6ddd1 + 50f315b commit dbcdf2c
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ public static function createInterceptedMethodBody(
'{{$suffixInterceptorsName}}' => $suffixInterceptors->getName(),
'{{$suffixEarlyReturnExpression}}' => ProxiedMethodReturnExpression::generate('$suffixReturnValue', $originalMethod),
'{{$returnExpression}}' => ProxiedMethodReturnExpression::generate('$returnValue', $originalMethod),
'{{$paramsString}}' => 'array(' . implode(', ', array_map(static fn (ParameterGenerator $parameter): string => var_export($parameter->getName(), true) . ' => $' . $parameter->getName(), $method->getParameters())) . ')',
'{{$paramsString}}' => 'array(' . implode(', ', array_map(
static fn (ParameterGenerator $parameter): string => var_export($parameter->getName(), true) . ' => ' . ($parameter->getPassedByReference() ? '&$' : '$') . $parameter->getName(),
$method->getParameters()
))
. ')',
];

return str_replace(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ public static function createInterceptedMethodBody(

foreach ($method->getParameters() as $parameter) {
$parameterName = $parameter->getName();
$params[] = var_export($parameterName, true) . ' => $' . $parameter->getName();
$symbol = $parameter->getPassedByReference() ? '&$' : '$';
$params[] = var_export($parameterName, true) . ' => ' . $symbol . $parameterName;
}

$paramsString = 'array(' . implode(', ', $params) . ')';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use ProxyManagerTestAsset\ClassWithPublicStringNullableTypedProperty;
use ProxyManagerTestAsset\ClassWithSelfHint;
use ProxyManagerTestAsset\EmptyClass;
use ProxyManagerTestAsset\ReferenceIncrementDecrementClass;
use ProxyManagerTestAsset\VoidCounter;
use ReflectionClass;
use stdClass;
Expand Down Expand Up @@ -562,6 +563,79 @@ public function testWillRefuseToGenerateReferencesToTypedPropertiesWithoutDefaul
$factory->createProxy($instance);
}

public function testByReferencePassedArgumentsAreGivenAsReferenceToInterceptorCallbacks(): void
{
$proxy = (new AccessInterceptorScopeLocalizerFactory())->createProxy(
new ReferenceIncrementDecrementClass(),
[
'incrementReference' => static function (
object $proxy,
ReferenceIncrementDecrementClass $instance,
string $method,
array $args,
bool &$returnEarly
): void {
self::assertSame(0, $args['reference']);

$returnEarly = true;
$args['reference'] = 5;
},
]
);

$number = 0;

$proxy->incrementReference($number);

self::assertSame(5, $number, 'Number was changed by interceptor');
}

public function testByReferenceArgumentsAreForwardedThroughInterceptorsAndSubject(): void
{
$proxy = (new AccessInterceptorScopeLocalizerFactory())->createProxy(
new ReferenceIncrementDecrementClass(),
[
'incrementReference' => static function (
object $proxy,
ReferenceIncrementDecrementClass $instance,
string $method,
array $args,
bool &$returnEarly
): void {
self::assertSame(0, $args['reference']);

$returnEarly = false;
$args['reference'] = 5;
},
],
[
'incrementReference' => static function (
object $proxy,
ReferenceIncrementDecrementClass $instance,
string $method,
array $args,
mixed $returnValue,
bool &$returnEarly
): void {
self::assertIsInt($args['reference']);

$returnEarly = false;
$args['reference'] *= 2;
},
]
);

$number = 0;

$proxy->incrementReference($number);

self::assertSame(
12,
$number,
'Number was changed by prefix interceptor, then incremented, then doubled by suffix interceptor'
);
}

/**
* @psalm-param ExpectedType $expected
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use ProxyManagerTestAsset\ClassWithSelfHint;
use ProxyManagerTestAsset\EmptyClass;
use ProxyManagerTestAsset\OtherObjectAccessClass;
use ProxyManagerTestAsset\ReferenceIncrementDecrementClass;
use ProxyManagerTestAsset\VoidCounter;
use ReflectionClass;
use ReflectionProperty;
Expand Down Expand Up @@ -696,6 +697,79 @@ public function testWillInterceptAndReturnEarlyOnVoidMethod(): void
self::assertSame($increment + $addMore + 1, $object->counter);
}

public function testByReferencePassedArgumentsAreGivenAsReferenceToInterceptorCallbacks(): void
{
$proxy = (new AccessInterceptorValueHolderFactory())->createProxy(
new ReferenceIncrementDecrementClass(),
[
'incrementReference' => static function (
object $proxy,
ReferenceIncrementDecrementClass $instance,
string $method,
array $args,
bool &$returnEarly
): void {
self::assertSame(0, $args['reference']);

$returnEarly = true;
$args['reference'] = 5;
},
]
);

$number = 0;

$proxy->incrementReference($number);

self::assertSame(5, $number, 'Number was changed by interceptor');
}

public function testByReferenceArgumentsAreForwardedThroughInterceptorsAndSubject(): void
{
$proxy = (new AccessInterceptorValueHolderFactory())->createProxy(
new ReferenceIncrementDecrementClass(),
[
'incrementReference' => static function (
object $proxy,
ReferenceIncrementDecrementClass $instance,
string $method,
array $args,
bool &$returnEarly
): void {
self::assertSame(0, $args['reference']);

$returnEarly = false;
$args['reference'] = 5;
},
],
[
'incrementReference' => static function (
object $proxy,
ReferenceIncrementDecrementClass $instance,
string $method,
array $args,
mixed $returnValue,
bool &$returnEarly
): void {
self::assertIsInt($args['reference']);

$returnEarly = false;
$args['reference'] *= 2;
},
]
);

$number = 0;

$proxy->incrementReference($number);

self::assertSame(
12,
$number,
'Number was changed by prefix interceptor, then incremented, then doubled by suffix interceptor'
);
}

private static function assertByRefVariableValueSame(mixed $expected, mixed & $actual): void
{
self::assertSame($expected, $actual);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,61 @@ public function testInterceptorGeneratorWithExistingNonVoidMethod(): void
}
}
return $returnValue;
PHP;
// @codingStandardsIgnoreEnd

self::assertSame(
$expected,
InterceptorGenerator::createInterceptedMethodBody(
'$returnValue = "foo";',
$method,
$prefixInterceptors,
$suffixInterceptors,
new ReflectionMethod(BaseClass::class, 'publicMethod')
)
);
}

public function testInterceptorGeneratorWithReferences(): void
{
$method = $this->createMock(MethodGenerator::class);
$bar = $this->createMock(ParameterGenerator::class);
$baz = $this->createMock(ParameterGenerator::class);
$prefixInterceptors = $this->createMock(PropertyGenerator::class);
$suffixInterceptors = $this->createMock(PropertyGenerator::class);

$bar->method('getName')->willReturn('bar');
$bar->method('getPassedByReference')->willReturn(false);
$baz->method('getName')->willReturn('baz');
$baz->method('getPassedByReference')->willReturn(true);
$method->method('getName')->willReturn('fooMethod');
$method->method('getParameters')->will(self::returnValue([$bar, $baz]));
$prefixInterceptors->method('getName')->willReturn('pre');
$suffixInterceptors->method('getName')->willReturn('post');

// @codingStandardsIgnoreStart
$expected = <<<'PHP'
if (isset($this->pre['fooMethod'])) {
$returnEarly = false;
$prefixReturnValue = $this->pre['fooMethod']->__invoke($this, $this, 'fooMethod', array('bar' => $bar, 'baz' => &$baz), $returnEarly);
if ($returnEarly) {
return $prefixReturnValue;
}
}
$returnValue = "foo";
if (isset($this->post['fooMethod'])) {
$returnEarly = false;
$suffixReturnValue = $this->post['fooMethod']->__invoke($this, $this, 'fooMethod', array('bar' => $bar, 'baz' => &$baz), $returnValue, $returnEarly);
if ($returnEarly) {
return $suffixReturnValue;
}
}
return $returnValue;
PHP;
// @codingStandardsIgnoreEnd
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,64 @@ public function testInterceptorGeneratorWithNonVoidOriginalMethod(): void
}
}
return $returnValue;
PHP;
// @codingStandardsIgnoreEnd

self::assertSame(
$expected,
InterceptorGenerator::createInterceptedMethodBody(
'$returnValue = "foo";',
$method,
$valueHolder,
$prefixInterceptors,
$suffixInterceptors,
new ReflectionMethod(BaseClass::class, 'publicMethod')
)
);
}

public function testInterceptorGeneratorWithReferences(): void
{
$method = $this->createMock(MethodGenerator::class);
$bar = $this->createMock(ParameterGenerator::class);
$baz = $this->createMock(ParameterGenerator::class);
$valueHolder = $this->createMock(PropertyGenerator::class);
$prefixInterceptors = $this->createMock(PropertyGenerator::class);
$suffixInterceptors = $this->createMock(PropertyGenerator::class);

$bar->method('getName')->willReturn('bar');
$bar->method('getPassedByReference')->willReturn(false);
$baz->method('getName')->willReturn('baz');
$baz->method('getPassedByReference')->willReturn(true);
$method->method('getName')->willReturn('fooMethod');
$method->method('getParameters')->will(self::returnValue([$bar, $baz]));
$valueHolder->method('getName')->willReturn('foo');
$prefixInterceptors->method('getName')->willReturn('pre');
$suffixInterceptors->method('getName')->willReturn('post');

// @codingStandardsIgnoreStart
$expected = <<<'PHP'
if (isset($this->pre['fooMethod'])) {
$returnEarly = false;
$prefixReturnValue = $this->pre['fooMethod']->__invoke($this, $this->foo, 'fooMethod', array('bar' => $bar, 'baz' => &$baz), $returnEarly);
if ($returnEarly) {
return $prefixReturnValue;
}
}
$returnValue = "foo";
if (isset($this->post['fooMethod'])) {
$returnEarly = false;
$suffixReturnValue = $this->post['fooMethod']->__invoke($this, $this->foo, 'fooMethod', array('bar' => $bar, 'baz' => &$baz), $returnValue, $returnEarly);
if ($returnEarly) {
return $suffixReturnValue;
}
}
return $returnValue;
PHP;
// @codingStandardsIgnoreEnd
Expand Down
13 changes: 13 additions & 0 deletions tests/ProxyManagerTestAsset/ReferenceIncrementDecrementClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace ProxyManagerTestAsset;

class ReferenceIncrementDecrementClass
{
public function incrementReference(int & $reference): void
{
$reference += 1;
}
}

0 comments on commit dbcdf2c

Please sign in to comment.