Skip to content

Commit d4d36e8

Browse files
authored
Introduce lazy object and proxy object support helpers (#57831)
* Add lazy and proxy helpers * Use factories in tests to better reflect usage * Improve test names * Pass eager properties through to proxy method * Remove version compare * Extract return type reflector to trait * Add return type closure detection
1 parent 0cc4f58 commit d4d36e8

File tree

5 files changed

+818
-29
lines changed

5 files changed

+818
-29
lines changed

src/Illuminate/Container/Container.php

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Illuminate\Contracts\Container\ContextualAttribute;
1515
use Illuminate\Contracts\Container\SelfBuilding;
1616
use Illuminate\Support\Collection;
17+
use Illuminate\Support\Traits\ReflectsClosures;
1718
use LogicException;
1819
use ReflectionAttribute;
1920
use ReflectionClass;
@@ -26,6 +27,8 @@
2627

2728
class Container implements ArrayAccess, ContainerContract
2829
{
30+
use ReflectsClosures;
31+
2932
/**
3033
* The current globally available container (if any).
3134
*
@@ -568,35 +571,6 @@ protected function bindBasedOnClosureReturnTypes($abstract, $concrete = null, $s
568571
}
569572
}
570573

571-
/**
572-
* Get the class names / types of the return type of the given Closure.
573-
*
574-
* @param \Closure $closure
575-
* @return list<class-string>
576-
*
577-
* @throws \ReflectionException
578-
*/
579-
protected function closureReturnTypes(Closure $closure)
580-
{
581-
$reflection = new ReflectionFunction($closure);
582-
583-
if ($reflection->getReturnType() === null ||
584-
$reflection->getReturnType() instanceof ReflectionIntersectionType) {
585-
return [];
586-
}
587-
588-
$types = $reflection->getReturnType() instanceof ReflectionUnionType
589-
? $reflection->getReturnType()->getTypes()
590-
: [$reflection->getReturnType()];
591-
592-
return (new Collection($types))
593-
->reject(fn ($type) => $type->isBuiltin())
594-
->reject(fn ($type) => in_array($type->getName(), ['static', 'self']))
595-
->map(fn ($type) => $type->getName())
596-
->values()
597-
->all();
598-
}
599-
600574
/**
601575
* "Extend" an abstract type in the container.
602576
*

src/Illuminate/Support/Traits/ReflectsClosures.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use Illuminate\Support\Collection;
77
use Illuminate\Support\Reflector;
88
use ReflectionFunction;
9+
use ReflectionIntersectionType;
10+
use ReflectionUnionType;
911
use RuntimeException;
1012

1113
trait ReflectsClosures
@@ -92,4 +94,33 @@ protected function closureParameterTypes(Closure $closure)
9294
})
9395
->all();
9496
}
97+
98+
/**
99+
* Get the class names / types of the return type of the given Closure.
100+
*
101+
* @param \Closure $closure
102+
* @return list<class-string>
103+
*
104+
* @throws \ReflectionException
105+
*/
106+
protected function closureReturnTypes(Closure $closure)
107+
{
108+
$reflection = new ReflectionFunction($closure);
109+
110+
if ($reflection->getReturnType() === null ||
111+
$reflection->getReturnType() instanceof ReflectionIntersectionType) {
112+
return [];
113+
}
114+
115+
$types = $reflection->getReturnType() instanceof ReflectionUnionType
116+
? $reflection->getReturnType()->getTypes()
117+
: [$reflection->getReturnType()];
118+
119+
return (new Collection($types))
120+
->reject(fn ($type) => $type->isBuiltin())
121+
->reject(fn ($type) => in_array($type->getName(), ['static', 'self']))
122+
->map(fn ($type) => $type->getName())
123+
->values()
124+
->all();
125+
}
95126
}

src/Illuminate/Support/helpers.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Illuminate\Support\Sleep;
1414
use Illuminate\Support\Str;
1515
use Illuminate\Support\Stringable as SupportStringable;
16+
use Illuminate\Support\Traits\ReflectsClosures;
1617

1718
if (! function_exists('append_config')) {
1819
/**
@@ -196,6 +197,54 @@ function literal(...$arguments)
196197
}
197198
}
198199

200+
if (! function_exists('lazy')) {
201+
/**
202+
* Create a lazy instance.
203+
*
204+
* @template TValue of object
205+
*
206+
* @param class-string<TValue>|(\Closure(TValue): mixed) $class
207+
* @param (\Closure(TValue): mixed)|int $callback
208+
* @param int $options
209+
* @param array<string, mixed> $eager
210+
* @return TValue
211+
*/
212+
function lazy($class, $callback = 0, $options = 0, $eager = [])
213+
{
214+
static $closureReflector;
215+
216+
$closureReflector ??= new class
217+
{
218+
use ReflectsClosures;
219+
220+
public function typeFromParameter($callback)
221+
{
222+
return $this->firstClosureParameterType($callback);
223+
}
224+
};
225+
226+
[$class, $callback, $options] = is_string($class)
227+
? [$class, $callback, $options]
228+
: [$closureReflector->typeFromParameter($class), $class, $callback ?: $options];
229+
230+
$reflectionClass = new ReflectionClass($class);
231+
232+
$instance = $reflectionClass->newLazyGhost(function ($instance) use ($callback) {
233+
$result = $callback($instance);
234+
235+
if (is_array($result)) {
236+
$instance->__construct(...$result);
237+
}
238+
}, $options);
239+
240+
foreach ($eager as $property => $value) {
241+
$reflectionClass->getProperty($property)->setRawValueWithoutLazyInitialization($instance, $value);
242+
}
243+
244+
return $instance;
245+
}
246+
}
247+
199248
if (! function_exists('object_get')) {
200249
/**
201250
* Get an item from an object using "dot" notation.
@@ -295,6 +344,52 @@ function preg_replace_array($pattern, array $replacements, $subject): string
295344
}
296345
}
297346

347+
if (! function_exists('proxy')) {
348+
/**
349+
* Create a lazy proxy instance.
350+
*
351+
* @template TValue of object
352+
*
353+
* @param class-string<TValue>|(\Closure(TValue): TValue) $class
354+
* @param (\Closure(TValue): TValue)|int $callback
355+
* @param int $options
356+
* @param array<string, mixed> $eager
357+
* @return TValue
358+
*/
359+
function proxy($class, $callback = 0, $options = 0, $eager = [])
360+
{
361+
static $closureReflector;
362+
363+
$closureReflector = new class
364+
{
365+
use ReflectsClosures;
366+
367+
public function get($callback)
368+
{
369+
return $this->closureReturnTypes($callback)[0] ?? $this->firstClosureParameterType($callback);
370+
}
371+
};
372+
373+
[$class, $callback, $options] = is_string($class)
374+
? [$class, $callback, $options]
375+
: [$closureReflector->get($class), $class, $callback ?: $options];
376+
377+
$reflectionClass = new ReflectionClass($class);
378+
379+
$proxy = $reflectionClass->newLazyProxy(function () use ($callback, $eager, &$proxy) {
380+
$instance = $callback($proxy, $eager);
381+
382+
return $instance;
383+
}, $options);
384+
385+
foreach ($eager as $property => $value) {
386+
$reflectionClass->getProperty($property)->setRawValueWithoutLazyInitialization($proxy, $value);
387+
}
388+
389+
return $proxy;
390+
}
391+
}
392+
298393
if (! function_exists('retry')) {
299394
/**
300395
* Retry an operation a given number of times.

0 commit comments

Comments
 (0)