|
16 | 16 | use ReflectionUnionType; |
17 | 17 | use TypeError; |
18 | 18 | use UnexpectedValueException; |
| 19 | +use Windwalker\DI\Attributes\Lazy; |
19 | 20 | use Windwalker\DI\Attributes\Service; |
20 | 21 | use Windwalker\DI\Definition\DefinitionInterface; |
21 | 22 | use Windwalker\DI\Definition\ObjectBuilderDefinition; |
@@ -176,7 +177,7 @@ public function newInstanceByClassName( |
176 | 177 | * |
177 | 178 | * @param ReflectionFunctionAbstract $method Method for which to build the argument array. |
178 | 179 | * @param array $args The default args if class hint not provided. |
179 | | - * @param int $options |
| 180 | + * @param DIOptions|int $options |
180 | 181 | * |
181 | 182 | * @return array Array of arguments to pass to the method. |
182 | 183 | * |
@@ -242,12 +243,13 @@ protected function getMethodArgs( |
242 | 243 | $value = null; |
243 | 244 | $value = &$this->resolveParameterAttributes($value, $param); |
244 | 245 |
|
245 | | - if ($value === null) { |
| 246 | + if ($value !== null) { |
| 247 | + $value = &$this->resolveParameterValue($value); |
| 248 | + } else { |
| 249 | + // resolveParameterValue() will be called in resolveParameterDependency() |
246 | 250 | $value = &$this->resolveParameterDependency($param, $args, $options); |
247 | 251 | } |
248 | 252 |
|
249 | | - $value = &$this->resolveParameterValue($value); |
250 | | - |
251 | 253 | if ($value !== null) { |
252 | 254 | $methodArgs[$dependencyVarName] = &$value; |
253 | 255 |
|
@@ -335,33 +337,66 @@ public function &resolveParameterDependency( |
335 | 337 | if (array_key_exists($dependencyClassName, $args)) { |
336 | 338 | // If an arg provided, use it. |
337 | 339 | return $args[$dependencyClassName]; |
338 | | - } elseif ($this->container->has($dependencyClassName)) { |
339 | | - // If the dependency class name is registered with this container or a parent, use it. |
340 | | - $depObject = $this->container->get($dependencyClassName); |
341 | | - } elseif ( |
342 | | - $autowire |
343 | | - && !$dependency->isAbstract() |
344 | | - && !$dependency->isInterface() |
345 | | - && !$dependency->isTrait() |
346 | | - && $dependency->isInstantiable() |
347 | | - && !$param->allowsNull() |
| 340 | + } |
| 341 | + |
| 342 | + $create = function &() use ( |
| 343 | + $options, |
| 344 | + $param, |
| 345 | + $dependency, |
| 346 | + $autowire, |
| 347 | + $dependencyClassName |
348 | 348 | ) { |
349 | | - // Otherwise we create this object recursive |
| 349 | + $depObject = null; |
| 350 | + |
| 351 | + if ($this->container->has($dependencyClassName)) { |
| 352 | + // If the dependency class name is registered with this container or a parent, use it. |
| 353 | + $depObject = $this->container->get($dependencyClassName); |
| 354 | + } elseif ( |
| 355 | + $autowire |
| 356 | + && !$dependency->isAbstract() |
| 357 | + && !$dependency->isInterface() |
| 358 | + && !$dependency->isTrait() |
| 359 | + && $dependency->isInstantiable() |
| 360 | + && !$param->allowsNull() |
| 361 | + ) { |
| 362 | + // Otherwise we create this object recursive |
| 363 | + |
| 364 | + // Find child args if set |
| 365 | + if (isset($args[$dependencyClassName]) && is_array($args[$dependencyClassName])) { |
| 366 | + $childArgs = $args[$dependencyClassName]; |
| 367 | + } else { |
| 368 | + $childArgs = []; |
| 369 | + } |
350 | 370 |
|
351 | | - // Find child args if set |
352 | | - if (isset($args[$dependencyClassName]) && is_array($args[$dependencyClassName])) { |
353 | | - $childArgs = $args[$dependencyClassName]; |
354 | | - } else { |
355 | | - $childArgs = []; |
| 371 | + $dependencyClassAlias = $this->container->resolveAlias($dependencyClassName); |
| 372 | + |
| 373 | + $depObject = $this->newInstance($dependencyClassAlias, $childArgs, $options); |
356 | 374 | } |
357 | 375 |
|
358 | | - $dependencyClassAlias = $this->container->resolveAlias($dependencyClassName); |
| 376 | + return $depObject; |
| 377 | + }; |
| 378 | + |
| 379 | + $ref = new ReflectionClass($dependencyClassName); |
| 380 | + |
| 381 | + if ( |
| 382 | + ($this->canLazy($ref, $options) || $this->canLazy($param, $options)) |
| 383 | + && !static::isInternal($ref) |
| 384 | + ) { |
| 385 | + $depObject = $ref->newLazyProxy( |
| 386 | + function () use ($create) { |
| 387 | + $value = $create(); |
| 388 | + |
| 389 | + return $this->resolveParameterValue($value); |
| 390 | + } |
| 391 | + ); |
359 | 392 |
|
360 | | - $depObject = $this->newInstance($dependencyClassAlias, $childArgs, $options); |
| 393 | + return $depObject; |
361 | 394 | } |
362 | 395 |
|
| 396 | + $depObject = &$create(); |
| 397 | + |
363 | 398 | if ($depObject instanceof $dependencyClassName) { |
364 | | - return $depObject; |
| 399 | + return $this->resolveParameterValue($depObject); |
365 | 400 | } |
366 | 401 | } |
367 | 402 |
|
@@ -434,10 +469,10 @@ public function &resolveParameterAttributes( |
434 | 469 | /** |
435 | 470 | * Execute a callable with dependencies. |
436 | 471 | * |
437 | | - * @param mixed $callable Do not use callable hint, will check callable after context bounded. |
438 | | - * @param array $args |
| 472 | + * @param mixed $callable Do not use callable hint, will check callable after context bounded. |
| 473 | + * @param array $args |
439 | 474 | * @param object|null $context |
440 | | - * @param int $options |
| 475 | + * @param int $options |
441 | 476 | * |
442 | 477 | * @return mixed |
443 | 478 | * |
@@ -490,4 +525,58 @@ public function mergeOptionsDefaults(int|DIOptions $options): DIOptions |
490 | 525 | { |
491 | 526 | return DIOptions::wrap($options)->withDefaults($this->container->getOptions()); |
492 | 527 | } |
| 528 | + |
| 529 | + public function canLazy( |
| 530 | + string|ReflectionClass|ReflectionParameter|\ReflectionProperty $classOrRef, |
| 531 | + DIOptions $options |
| 532 | + ): bool { |
| 533 | + $ref = is_string($classOrRef) ? new \ReflectionClass($classOrRef) : $classOrRef; |
| 534 | + $lazy = $options->lazy ?? $this->container->options->lazy; |
| 535 | + |
| 536 | + return ($lazy || static::getAttributeFromReflection($ref, Lazy::class)); |
| 537 | + } |
| 538 | + |
| 539 | + public static function isInternal(string|\Reflector $class): bool |
| 540 | + { |
| 541 | + $ref = is_string($class) ? new \ReflectionClass($class) : $class; |
| 542 | + |
| 543 | + if (!$ref instanceof ReflectionClass) { |
| 544 | + return false; |
| 545 | + } |
| 546 | + |
| 547 | + while (!$ref->isInternal()) { |
| 548 | + $ref = $ref->getParentClass(); |
| 549 | + |
| 550 | + if (!$ref) { |
| 551 | + return false; |
| 552 | + } |
| 553 | + |
| 554 | + if ($ref->isInternal()) { |
| 555 | + return true; |
| 556 | + } |
| 557 | + } |
| 558 | + |
| 559 | + return true; |
| 560 | + } |
| 561 | + |
| 562 | + /** |
| 563 | + * @template T |
| 564 | + * |
| 565 | + * @param ReflectionClass|ReflectionParameter|\ReflectionProperty $ref |
| 566 | + * @param class-string<T> $attr |
| 567 | + * |
| 568 | + * @return object|null |
| 569 | + */ |
| 570 | + public static function getAttributeFromReflection( |
| 571 | + ReflectionClass|ReflectionParameter|\ReflectionProperty $ref, |
| 572 | + string $attr |
| 573 | + ): ?object { |
| 574 | + $attrs = $ref->getAttributes($attr, \ReflectionAttribute::IS_INSTANCEOF); |
| 575 | + |
| 576 | + if ($attrs === []) { |
| 577 | + return null; |
| 578 | + } |
| 579 | + |
| 580 | + return $attrs[0]->newInstance(); |
| 581 | + } |
493 | 582 | } |
0 commit comments