Skip to content

Commit 352d7fb

Browse files
Add "fluentSafe" option to LazyLoadingValueHolderGenerator
1 parent fefdbda commit 352d7fb

File tree

4 files changed

+68
-11
lines changed

4 files changed

+68
-11
lines changed

src/ProxyManager/Factory/AbstractBaseFactory.php

+8-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
use function assert;
1818
use function class_exists;
1919
use function is_a;
20+
use function serialize;
21+
use function sha1;
2022

2123
/**
2224
* Base factory common logic
@@ -29,7 +31,7 @@ abstract class AbstractBaseFactory
2931
* Cached checked class names
3032
*
3133
* @var array<string, string>
32-
* @psalm-var array<class-string, class-string>
34+
* @psalm-var array<string, class-string>
3335
*/
3436
private $checkedClasses = [];
3537

@@ -55,8 +57,10 @@ public function __construct(?Configuration $configuration = null)
5557
*/
5658
protected function generateProxy(string $className, array $proxyOptions = []): string
5759
{
58-
if (array_key_exists($className, $this->checkedClasses)) {
59-
$generatedClassName = $this->checkedClasses[$className];
60+
$cacheKey = $proxyOptions ? sha1(serialize([$className, $proxyOptions])) : $className;
61+
62+
if (array_key_exists($cacheKey, $this->checkedClasses)) {
63+
$generatedClassName = $this->checkedClasses[$cacheKey];
6064

6165
assert(is_a($generatedClassName, $className, true));
6266

@@ -88,7 +92,7 @@ protected function generateProxy(string $className, array $proxyOptions = []): s
8892
->getSignatureChecker()
8993
->checkSignature(new ReflectionClass($proxyClassName), $proxyParameters);
9094

91-
return $this->checkedClasses[$className] = $proxyClassName;
95+
return $this->checkedClasses[$cacheKey] = $proxyClassName;
9296
}
9397

9498
abstract protected function getGenerator(): ProxyGeneratorInterface;

src/ProxyManager/Factory/LazyLoadingValueHolderFactory.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function __construct(?Configuration $configuration = null)
3838
* array<string, mixed>=,
3939
* ?Closure=
4040
* ) : bool $initializer
41-
* @psalm-param array{skipDestructor?: bool} $proxyOptions
41+
* @psalm-param array{skipDestructor?: bool, fluentSafe?: bool} $proxyOptions
4242
*
4343
* @psalm-return RealObjectType&ValueHolderInterface<RealObjectType>&VirtualProxyInterface
4444
*

src/ProxyManager/ProxyGenerator/LazyLoadingValueHolderGenerator.php

+27-6
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
use function array_merge;
4141
use function func_get_arg;
4242
use function func_num_args;
43+
use function str_replace;
44+
use function substr;
4345

4446
/**
4547
* Generator for proxies implementing {@see \ProxyManager\Proxy\VirtualProxyInterface}
@@ -56,11 +58,11 @@ class LazyLoadingValueHolderGenerator implements ProxyGeneratorInterface
5658
* @throws InvalidProxiedClassException
5759
* @throws InvalidArgumentException
5860
*
59-
* @psalm-param array{skipDestructor?: bool} $proxyOptions
61+
* @psalm-param array{skipDestructor?: bool, fluentSafe?: bool} $proxyOptions
6062
*/
6163
public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator/*, array $proxyOptions = []*/)
6264
{
63-
/** @psalm-var array{skipDestructor?: bool} $proxyOptions */
65+
/** @psalm-var array{skipDestructor?: bool, fluentSafe?: bool} $proxyOptions */
6466
$proxyOptions = func_num_args() >= 3 ? func_get_arg(2) : [];
6567

6668
CanProxyAssertion::assertClassCanBeProxied($originalClass);
@@ -92,7 +94,7 @@ static function (MethodGenerator $generatedMethod) use ($originalClass, $classGe
9294
},
9395
array_merge(
9496
array_map(
95-
$this->buildLazyLoadingMethodInterceptor($initializer, $valueHolder),
97+
$this->buildLazyLoadingMethodInterceptor($initializer, $valueHolder, $proxyOptions['fluentSafe'] ?? false),
9698
ProxiedMethodsFilter::getProxiedMethods($originalClass, $excludedMethods)
9799
),
98100
[
@@ -118,14 +120,33 @@ static function (MethodGenerator $generatedMethod) use ($originalClass, $classGe
118120

119121
private function buildLazyLoadingMethodInterceptor(
120122
InitializerProperty $initializer,
121-
ValueHolderProperty $valueHolder
123+
ValueHolderProperty $valueHolder,
124+
bool $fluentSafe
122125
): callable {
123-
return static function (ReflectionMethod $method) use ($initializer, $valueHolder): LazyLoadingMethodInterceptor {
124-
return LazyLoadingMethodInterceptor::generateMethod(
126+
return static function (ReflectionMethod $method) use ($initializer, $valueHolder, $fluentSafe): LazyLoadingMethodInterceptor {
127+
$byRef = $method->returnsReference() ? '& ' : '';
128+
$method = LazyLoadingMethodInterceptor::generateMethod(
125129
new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()),
126130
$initializer,
127131
$valueHolder
128132
);
133+
134+
if ($fluentSafe) {
135+
$valueHolderName = '$this->' . $valueHolder->getName();
136+
$body = $method->getBody();
137+
$newBody = str_replace('return ' . $valueHolderName, 'if (' . $valueHolderName . ' === $returnValue = ' . $byRef . $valueHolderName, $body);
138+
139+
if ($newBody !== $body) {
140+
$method->setBody(
141+
substr($newBody, 0, -1) . ') {' . "\n"
142+
. ' return $this;' . "\n"
143+
. '}' . "\n\n"
144+
. 'return $returnValue;'
145+
);
146+
}
147+
}
148+
149+
return $method;
129150
};
130151
}
131152
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
Verifies that generated lazy loading value holders can be fluent-safe
3+
--FILE--
4+
<?php
5+
6+
require_once __DIR__ . '/init.php';
7+
8+
class FluentClass
9+
{
10+
public function foo()
11+
{
12+
return $this;
13+
}
14+
}
15+
16+
$fluentObject = new FluentClass();
17+
18+
$factory = new \ProxyManager\Factory\LazyLoadingValueHolderFactory($configuration);
19+
20+
$init = function (& $wrapped, $proxy, $method, $parameters, & $initializer) use ($fluentObject) {
21+
$wrapped = $fluentObject;
22+
$initializer = null;
23+
};
24+
25+
$proxy = $factory->createProxy(FluentClass::class, $init, ['fluentSafe' => true]);
26+
echo $proxy->foo() === $proxy;
27+
28+
$proxy = $factory->createProxy(FluentClass::class, $init);
29+
echo $proxy->foo() === $fluentObject;
30+
?>
31+
--EXPECT--
32+
11

0 commit comments

Comments
 (0)