Skip to content

Commit dbcdf2c

Browse files
authored
Merge pull request #736 from ostrolucky/interceptor-references
Support by-reference params in interceptors
2 parents af6ddd1 + 50f315b commit dbcdf2c

File tree

7 files changed

+281
-2
lines changed

7 files changed

+281
-2
lines changed

src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Util/InterceptorGenerator.php

+5-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,11 @@ public static function createInterceptedMethodBody(
6767
'{{$suffixInterceptorsName}}' => $suffixInterceptors->getName(),
6868
'{{$suffixEarlyReturnExpression}}' => ProxiedMethodReturnExpression::generate('$suffixReturnValue', $originalMethod),
6969
'{{$returnExpression}}' => ProxiedMethodReturnExpression::generate('$returnValue', $originalMethod),
70-
'{{$paramsString}}' => 'array(' . implode(', ', array_map(static fn (ParameterGenerator $parameter): string => var_export($parameter->getName(), true) . ' => $' . $parameter->getName(), $method->getParameters())) . ')',
70+
'{{$paramsString}}' => 'array(' . implode(', ', array_map(
71+
static fn (ParameterGenerator $parameter): string => var_export($parameter->getName(), true) . ' => ' . ($parameter->getPassedByReference() ? '&$' : '$') . $parameter->getName(),
72+
$method->getParameters()
73+
))
74+
. ')',
7175
];
7276

7377
return str_replace(

src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Util/InterceptorGenerator.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ public static function createInterceptedMethodBody(
6666

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

7273
$paramsString = 'array(' . implode(', ', $params) . ')';

tests/ProxyManagerTest/Functional/AccessInterceptorScopeLocalizerFunctionalTest.php

+74
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use ProxyManagerTestAsset\ClassWithPublicStringNullableTypedProperty;
2525
use ProxyManagerTestAsset\ClassWithSelfHint;
2626
use ProxyManagerTestAsset\EmptyClass;
27+
use ProxyManagerTestAsset\ReferenceIncrementDecrementClass;
2728
use ProxyManagerTestAsset\VoidCounter;
2829
use ReflectionClass;
2930
use stdClass;
@@ -562,6 +563,79 @@ public function testWillRefuseToGenerateReferencesToTypedPropertiesWithoutDefaul
562563
$factory->createProxy($instance);
563564
}
564565

566+
public function testByReferencePassedArgumentsAreGivenAsReferenceToInterceptorCallbacks(): void
567+
{
568+
$proxy = (new AccessInterceptorScopeLocalizerFactory())->createProxy(
569+
new ReferenceIncrementDecrementClass(),
570+
[
571+
'incrementReference' => static function (
572+
object $proxy,
573+
ReferenceIncrementDecrementClass $instance,
574+
string $method,
575+
array $args,
576+
bool &$returnEarly
577+
): void {
578+
self::assertSame(0, $args['reference']);
579+
580+
$returnEarly = true;
581+
$args['reference'] = 5;
582+
},
583+
]
584+
);
585+
586+
$number = 0;
587+
588+
$proxy->incrementReference($number);
589+
590+
self::assertSame(5, $number, 'Number was changed by interceptor');
591+
}
592+
593+
public function testByReferenceArgumentsAreForwardedThroughInterceptorsAndSubject(): void
594+
{
595+
$proxy = (new AccessInterceptorScopeLocalizerFactory())->createProxy(
596+
new ReferenceIncrementDecrementClass(),
597+
[
598+
'incrementReference' => static function (
599+
object $proxy,
600+
ReferenceIncrementDecrementClass $instance,
601+
string $method,
602+
array $args,
603+
bool &$returnEarly
604+
): void {
605+
self::assertSame(0, $args['reference']);
606+
607+
$returnEarly = false;
608+
$args['reference'] = 5;
609+
},
610+
],
611+
[
612+
'incrementReference' => static function (
613+
object $proxy,
614+
ReferenceIncrementDecrementClass $instance,
615+
string $method,
616+
array $args,
617+
mixed $returnValue,
618+
bool &$returnEarly
619+
): void {
620+
self::assertIsInt($args['reference']);
621+
622+
$returnEarly = false;
623+
$args['reference'] *= 2;
624+
},
625+
]
626+
);
627+
628+
$number = 0;
629+
630+
$proxy->incrementReference($number);
631+
632+
self::assertSame(
633+
12,
634+
$number,
635+
'Number was changed by prefix interceptor, then incremented, then doubled by suffix interceptor'
636+
);
637+
}
638+
565639
/**
566640
* @psalm-param ExpectedType $expected
567641
*

tests/ProxyManagerTest/Functional/AccessInterceptorValueHolderFunctionalTest.php

+74
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use ProxyManagerTestAsset\ClassWithSelfHint;
2626
use ProxyManagerTestAsset\EmptyClass;
2727
use ProxyManagerTestAsset\OtherObjectAccessClass;
28+
use ProxyManagerTestAsset\ReferenceIncrementDecrementClass;
2829
use ProxyManagerTestAsset\VoidCounter;
2930
use ReflectionClass;
3031
use ReflectionProperty;
@@ -696,6 +697,79 @@ public function testWillInterceptAndReturnEarlyOnVoidMethod(): void
696697
self::assertSame($increment + $addMore + 1, $object->counter);
697698
}
698699

700+
public function testByReferencePassedArgumentsAreGivenAsReferenceToInterceptorCallbacks(): void
701+
{
702+
$proxy = (new AccessInterceptorValueHolderFactory())->createProxy(
703+
new ReferenceIncrementDecrementClass(),
704+
[
705+
'incrementReference' => static function (
706+
object $proxy,
707+
ReferenceIncrementDecrementClass $instance,
708+
string $method,
709+
array $args,
710+
bool &$returnEarly
711+
): void {
712+
self::assertSame(0, $args['reference']);
713+
714+
$returnEarly = true;
715+
$args['reference'] = 5;
716+
},
717+
]
718+
);
719+
720+
$number = 0;
721+
722+
$proxy->incrementReference($number);
723+
724+
self::assertSame(5, $number, 'Number was changed by interceptor');
725+
}
726+
727+
public function testByReferenceArgumentsAreForwardedThroughInterceptorsAndSubject(): void
728+
{
729+
$proxy = (new AccessInterceptorValueHolderFactory())->createProxy(
730+
new ReferenceIncrementDecrementClass(),
731+
[
732+
'incrementReference' => static function (
733+
object $proxy,
734+
ReferenceIncrementDecrementClass $instance,
735+
string $method,
736+
array $args,
737+
bool &$returnEarly
738+
): void {
739+
self::assertSame(0, $args['reference']);
740+
741+
$returnEarly = false;
742+
$args['reference'] = 5;
743+
},
744+
],
745+
[
746+
'incrementReference' => static function (
747+
object $proxy,
748+
ReferenceIncrementDecrementClass $instance,
749+
string $method,
750+
array $args,
751+
mixed $returnValue,
752+
bool &$returnEarly
753+
): void {
754+
self::assertIsInt($args['reference']);
755+
756+
$returnEarly = false;
757+
$args['reference'] *= 2;
758+
},
759+
]
760+
);
761+
762+
$number = 0;
763+
764+
$proxy->incrementReference($number);
765+
766+
self::assertSame(
767+
12,
768+
$number,
769+
'Number was changed by prefix interceptor, then incremented, then doubled by suffix interceptor'
770+
);
771+
}
772+
699773
private static function assertByRefVariableValueSame(mixed $expected, mixed & $actual): void
700774
{
701775
self::assertSame($expected, $actual);

tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Util/InterceptorGeneratorTest.php

+55
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,61 @@ public function testInterceptorGeneratorWithExistingNonVoidMethod(): void
167167
}
168168
}
169169
170+
return $returnValue;
171+
PHP;
172+
// @codingStandardsIgnoreEnd
173+
174+
self::assertSame(
175+
$expected,
176+
InterceptorGenerator::createInterceptedMethodBody(
177+
'$returnValue = "foo";',
178+
$method,
179+
$prefixInterceptors,
180+
$suffixInterceptors,
181+
new ReflectionMethod(BaseClass::class, 'publicMethod')
182+
)
183+
);
184+
}
185+
186+
public function testInterceptorGeneratorWithReferences(): void
187+
{
188+
$method = $this->createMock(MethodGenerator::class);
189+
$bar = $this->createMock(ParameterGenerator::class);
190+
$baz = $this->createMock(ParameterGenerator::class);
191+
$prefixInterceptors = $this->createMock(PropertyGenerator::class);
192+
$suffixInterceptors = $this->createMock(PropertyGenerator::class);
193+
194+
$bar->method('getName')->willReturn('bar');
195+
$bar->method('getPassedByReference')->willReturn(false);
196+
$baz->method('getName')->willReturn('baz');
197+
$baz->method('getPassedByReference')->willReturn(true);
198+
$method->method('getName')->willReturn('fooMethod');
199+
$method->method('getParameters')->will(self::returnValue([$bar, $baz]));
200+
$prefixInterceptors->method('getName')->willReturn('pre');
201+
$suffixInterceptors->method('getName')->willReturn('post');
202+
203+
// @codingStandardsIgnoreStart
204+
$expected = <<<'PHP'
205+
if (isset($this->pre['fooMethod'])) {
206+
$returnEarly = false;
207+
$prefixReturnValue = $this->pre['fooMethod']->__invoke($this, $this, 'fooMethod', array('bar' => $bar, 'baz' => &$baz), $returnEarly);
208+
209+
if ($returnEarly) {
210+
return $prefixReturnValue;
211+
}
212+
}
213+
214+
$returnValue = "foo";
215+
216+
if (isset($this->post['fooMethod'])) {
217+
$returnEarly = false;
218+
$suffixReturnValue = $this->post['fooMethod']->__invoke($this, $this, 'fooMethod', array('bar' => $bar, 'baz' => &$baz), $returnValue, $returnEarly);
219+
220+
if ($returnEarly) {
221+
return $suffixReturnValue;
222+
}
223+
}
224+
170225
return $returnValue;
171226
PHP;
172227
// @codingStandardsIgnoreEnd

tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Util/InterceptorGeneratorTest.php

+58
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,64 @@ public function testInterceptorGeneratorWithNonVoidOriginalMethod(): void
175175
}
176176
}
177177
178+
return $returnValue;
179+
PHP;
180+
// @codingStandardsIgnoreEnd
181+
182+
self::assertSame(
183+
$expected,
184+
InterceptorGenerator::createInterceptedMethodBody(
185+
'$returnValue = "foo";',
186+
$method,
187+
$valueHolder,
188+
$prefixInterceptors,
189+
$suffixInterceptors,
190+
new ReflectionMethod(BaseClass::class, 'publicMethod')
191+
)
192+
);
193+
}
194+
195+
public function testInterceptorGeneratorWithReferences(): void
196+
{
197+
$method = $this->createMock(MethodGenerator::class);
198+
$bar = $this->createMock(ParameterGenerator::class);
199+
$baz = $this->createMock(ParameterGenerator::class);
200+
$valueHolder = $this->createMock(PropertyGenerator::class);
201+
$prefixInterceptors = $this->createMock(PropertyGenerator::class);
202+
$suffixInterceptors = $this->createMock(PropertyGenerator::class);
203+
204+
$bar->method('getName')->willReturn('bar');
205+
$bar->method('getPassedByReference')->willReturn(false);
206+
$baz->method('getName')->willReturn('baz');
207+
$baz->method('getPassedByReference')->willReturn(true);
208+
$method->method('getName')->willReturn('fooMethod');
209+
$method->method('getParameters')->will(self::returnValue([$bar, $baz]));
210+
$valueHolder->method('getName')->willReturn('foo');
211+
$prefixInterceptors->method('getName')->willReturn('pre');
212+
$suffixInterceptors->method('getName')->willReturn('post');
213+
214+
// @codingStandardsIgnoreStart
215+
$expected = <<<'PHP'
216+
if (isset($this->pre['fooMethod'])) {
217+
$returnEarly = false;
218+
$prefixReturnValue = $this->pre['fooMethod']->__invoke($this, $this->foo, 'fooMethod', array('bar' => $bar, 'baz' => &$baz), $returnEarly);
219+
220+
if ($returnEarly) {
221+
return $prefixReturnValue;
222+
}
223+
}
224+
225+
$returnValue = "foo";
226+
227+
if (isset($this->post['fooMethod'])) {
228+
$returnEarly = false;
229+
$suffixReturnValue = $this->post['fooMethod']->__invoke($this, $this->foo, 'fooMethod', array('bar' => $bar, 'baz' => &$baz), $returnValue, $returnEarly);
230+
231+
if ($returnEarly) {
232+
return $suffixReturnValue;
233+
}
234+
}
235+
178236
return $returnValue;
179237
PHP;
180238
// @codingStandardsIgnoreEnd
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ProxyManagerTestAsset;
6+
7+
class ReferenceIncrementDecrementClass
8+
{
9+
public function incrementReference(int & $reference): void
10+
{
11+
$reference += 1;
12+
}
13+
}

0 commit comments

Comments
 (0)