[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Part of the Joomla Framework DI Package 4 * 5 * @copyright Copyright (C) 2013 - 2018 Open Source Matters, Inc. All rights reserved. 6 * @license GNU General Public License version 2 or later; see LICENSE 7 */ 8 9 namespace Joomla\DI; 10 11 use Joomla\DI\Exception\DependencyResolutionException; 12 use Joomla\DI\Exception\KeyNotFoundException; 13 use Joomla\DI\Exception\ProtectedKeyException; 14 use Psr\Container\ContainerInterface; 15 16 /** 17 * The Container class. 18 * 19 * @since 1.0 20 */ 21 class Container implements ContainerInterface 22 { 23 /** 24 * Holds the key aliases. 25 * 26 * Format: 27 * 'alias' => 'key' 28 * 29 * @var array 30 * @since 1.0 31 */ 32 protected $aliases = []; 33 34 /** 35 * Holds the resources. 36 * 37 * @var ContainerResource[] 38 * @since 2.0.0 39 */ 40 protected $resources = []; 41 42 /** 43 * Parent for hierarchical containers. 44 * 45 * In fact, this can be any PSR-11 compatible container, which gets decorated by this 46 * 47 * @var Container|ContainerInterface|null 48 * @since 1.0 49 */ 50 protected $parent; 51 52 /** 53 * Holds the service tag mapping. 54 * 55 * @var array 56 * @since 1.5.0 57 */ 58 protected $tags = []; 59 60 /** 61 * Constructor for the DI Container 62 * 63 * @param ContainerInterface|null $parent Parent for hierarchical containers. 64 * 65 * @since 1.0 66 */ 67 public function __construct(?ContainerInterface $parent = null) 68 { 69 $this->parent = $parent; 70 } 71 72 /** 73 * Retrieve a resource 74 * 75 * @param string $resourceName Name of the resource to get. 76 * 77 * @return mixed The requested resource 78 * 79 * @since 1.0 80 * @throws KeyNotFoundException 81 */ 82 public function get($resourceName) 83 { 84 $key = $this->resolveAlias($resourceName); 85 86 if (!isset($this->resources[$key])) 87 { 88 if ($this->parent instanceof ContainerInterface && $this->parent->has($key)) 89 { 90 return $this->parent->get($key); 91 } 92 93 throw new KeyNotFoundException(sprintf("Resource '%s' has not been registered with the container.", $resourceName)); 94 } 95 96 return $this->resources[$key]->getInstance(); 97 } 98 99 /** 100 * Check if specified resource exists. 101 * 102 * @param string $resourceName Name of the resource to check. 103 * 104 * @return boolean true if key is defined, false otherwise 105 * 106 * @since 1.5.0 107 */ 108 public function has($resourceName) 109 { 110 $key = $this->resolveAlias($resourceName); 111 112 if (!isset($this->resources[$key])) 113 { 114 if ($this->parent instanceof ContainerInterface) 115 { 116 return $this->parent->has($key); 117 } 118 119 return false; 120 } 121 122 return true; 123 } 124 125 /** 126 * Method to check if specified dataStore key exists. 127 * 128 * @param string $key Name of the dataStore key to check. 129 * 130 * @return boolean True for success 131 * 132 * @since 1.0 133 * @deprecated 3.0 Use ContainerInterface::has() instead 134 */ 135 public function exists($key) 136 { 137 trigger_deprecation( 138 'joomla/di', 139 '1.5.0', 140 '%s() is deprecated and will be removed in 3.0, use %s::has() instead.', 141 __METHOD__, 142 ContainerInterface::class 143 ); 144 145 return $this->has($key); 146 } 147 148 /** 149 * Create an alias for a given key for easy access. 150 * 151 * @param string $alias The alias name 152 * @param string $key The key to alias 153 * 154 * @return $this 155 * 156 * @since 1.0 157 */ 158 public function alias($alias, $key) 159 { 160 $this->aliases[$alias] = $key; 161 162 return $this; 163 } 164 165 /** 166 * Resolve a resource name. 167 * 168 * If the resource name is an alias, the corresponding key is returned. 169 * If the resource name is not an alias, the resource name is returned unchanged. 170 * 171 * @param string $resourceName The key to search for. 172 * 173 * @return string 174 * 175 * @since 1.0 176 */ 177 protected function resolveAlias($resourceName) 178 { 179 return $this->aliases[$resourceName] ?? $resourceName; 180 } 181 182 /** 183 * Check whether a resource is shared 184 * 185 * @param string $resourceName Name of the resource to check. 186 * 187 * @return boolean 188 * 189 * @since 2.0.0 190 */ 191 public function isShared(string $resourceName): bool 192 { 193 return $this->hasFlag($resourceName, 'isShared', true); 194 } 195 196 /** 197 * Check whether a resource is protected 198 * 199 * @param string $resourceName Name of the resource to check. 200 * 201 * @return boolean 202 * 203 * @since 2.0.0 204 */ 205 public function isProtected(string $resourceName): bool 206 { 207 return $this->hasFlag($resourceName, 'isProtected', true); 208 } 209 210 /** 211 * Check whether a flag (i.e., one of 'shared' or 'protected') is set 212 * 213 * @param string $resourceName Name of the resource to check. 214 * @param string $method Method to delegate to 215 * @param boolean $default Default return value 216 * 217 * @return boolean 218 * 219 * @since 2.0.0 220 * @throws KeyNotFoundException 221 */ 222 private function hasFlag(string $resourceName, string $method, bool $default = true): bool 223 { 224 $key = $this->resolveAlias($resourceName); 225 226 if (isset($this->resources[$key])) 227 { 228 return \call_user_func([$this->resources[$key], $method]); 229 } 230 231 if ($this->parent instanceof self) 232 { 233 return \call_user_func([$this->parent, $method], $key); 234 } 235 236 if ($this->parent instanceof ContainerInterface && $this->parent->has($key)) 237 { 238 // We don't know if the parent supports the 'shared' or 'protected' concept, so we assume the default 239 return $default; 240 } 241 242 throw new KeyNotFoundException(sprintf("Resource '%s' has not been registered with the container.", $resourceName)); 243 } 244 245 /** 246 * Assign a tag to services. 247 * 248 * @param string $tag The tag name 249 * @param array $keys The service keys to tag 250 * 251 * @return $this 252 * 253 * @since 1.5.0 254 */ 255 public function tag($tag, array $keys) 256 { 257 foreach ($keys as $key) 258 { 259 $resolvedKey = $this->resolveAlias($key); 260 261 if (!isset($this->tags[$tag])) 262 { 263 $this->tags[$tag] = []; 264 } 265 266 $this->tags[$tag][] = $resolvedKey; 267 } 268 269 // Prune duplicates 270 $this->tags[$tag] = array_unique($this->tags[$tag]); 271 272 return $this; 273 } 274 275 /** 276 * Fetch all services registered to the given tag. 277 * 278 * @param string $tag The tag name 279 * 280 * @return array The resolved services for the given tag 281 * 282 * @since 1.5.0 283 */ 284 public function getTagged($tag) 285 { 286 $services = []; 287 288 if (isset($this->tags[$tag])) 289 { 290 foreach ($this->tags[$tag] as $service) 291 { 292 $services[] = $this->get($service); 293 } 294 } 295 296 return $services; 297 } 298 299 /** 300 * Build an object of the requested class 301 * 302 * Creates an instance of the class specified by $resourceName with all dependencies injected. 303 * If the dependencies cannot be completely resolved, a DependencyResolutionException is thrown. 304 * 305 * @param string $resourceName The class name to build. 306 * @param boolean $shared True to create a shared resource. 307 * 308 * @return object|false Instance of class specified by $resourceName with all dependencies injected. 309 * Returns an object if the class exists and false otherwise 310 * 311 * @since 1.0 312 * @throws DependencyResolutionException if the object could not be built (due to missing information) 313 */ 314 public function buildObject($resourceName, $shared = false) 315 { 316 static $buildStack = []; 317 318 $key = $this->resolveAlias($resourceName); 319 320 if (\in_array($key, $buildStack, true)) 321 { 322 $buildStack = []; 323 324 throw new DependencyResolutionException(sprintf('Cannot resolve circular dependency for "%s"', $key)); 325 } 326 327 $buildStack[] = $key; 328 329 if ($this->has($key)) 330 { 331 $resource = $this->get($key); 332 array_pop($buildStack); 333 334 return $resource; 335 } 336 337 try 338 { 339 $reflection = new \ReflectionClass($key); 340 } 341 catch (\ReflectionException $e) 342 { 343 array_pop($buildStack); 344 345 return false; 346 } 347 348 if (!$reflection->isInstantiable()) 349 { 350 $buildStack = []; 351 352 if ($reflection->isInterface()) 353 { 354 throw new DependencyResolutionException( 355 sprintf('There is no service for "%s" defined, cannot autowire a class service for an interface.', $key) 356 ); 357 } 358 359 if ($reflection->isAbstract()) 360 { 361 throw new DependencyResolutionException( 362 sprintf('There is no service for "%s" defined, cannot autowire an abstract class.', $key) 363 ); 364 } 365 366 throw new DependencyResolutionException(sprintf('"%s" cannot be instantiated.', $key)); 367 } 368 369 $constructor = $reflection->getConstructor(); 370 371 // If there are no parameters, just return a new object. 372 if ($constructor === null) 373 { 374 // There is no constructor, just return a new object. 375 $callback = function () use ($key) 376 { 377 return new $key; 378 }; 379 } 380 else 381 { 382 $newInstanceArgs = $this->getMethodArgs($constructor); 383 384 $callback = function () use ($reflection, $newInstanceArgs) 385 { 386 return $reflection->newInstanceArgs($newInstanceArgs); 387 }; 388 } 389 390 $this->set($key, $callback, $shared); 391 392 $resource = $this->get($key); 393 array_pop($buildStack); 394 395 return $resource; 396 } 397 398 /** 399 * Convenience method for building a shared object. 400 * 401 * @param string $resourceName The class name to build. 402 * 403 * @return object|false Instance of class specified by $resourceName with all dependencies injected. 404 * Returns an object if the class exists and false otherwise 405 * 406 * @since 1.0 407 */ 408 public function buildSharedObject($resourceName) 409 { 410 return $this->buildObject($resourceName, true); 411 } 412 413 /** 414 * Create a child Container with a new property scope that has the ability to access the parent scope when resolving. 415 * 416 * @return Container A new container with the current as a parent 417 * 418 * @since 1.0 419 */ 420 public function createChild() 421 { 422 return new static($this); 423 } 424 425 /** 426 * Extend a defined service Closure by wrapping the existing one with a new callable function. 427 * 428 * This works very similar to a decorator pattern. Note that this only works on service Closures 429 * that have been defined in the current container, not parent containers. 430 * 431 * @param string $resourceName The unique identifier for the Closure or property. 432 * @param callable $callable A callable to wrap the original service Closure. 433 * 434 * @return void 435 * 436 * @since 1.0 437 * @throws KeyNotFoundException 438 */ 439 public function extend($resourceName, callable $callable) 440 { 441 $key = $this->resolveAlias($resourceName); 442 $resource = $this->getResource($key, true); 443 444 $closure = function ($c) use ($callable, $resource) 445 { 446 return $callable($resource->getInstance(), $c); 447 }; 448 449 $this->set($key, $closure, $resource->isShared()); 450 } 451 452 /** 453 * Build an array of method arguments. 454 * 455 * @param \ReflectionMethod $method Method for which to build the argument array. 456 * 457 * @return array Array of arguments to pass to the method. 458 * 459 * @since 1.0 460 * @throws DependencyResolutionException 461 */ 462 private function getMethodArgs(\ReflectionMethod $method): array 463 { 464 $methodArgs = []; 465 466 foreach ($method->getParameters() as $param) 467 { 468 // Check for a typehinted dependency 469 if ($param->hasType()) 470 { 471 $dependency = $param->getType(); 472 473 // Don't support PHP 8 union types 474 if ($dependency instanceof \ReflectionUnionType) 475 { 476 // If this is a nullable parameter, then don't error out 477 if ($param->allowsNull()) 478 { 479 $methodArgs[] = null; 480 481 continue; 482 } 483 484 throw new DependencyResolutionException( 485 sprintf( 486 'Could not resolve the parameter "$%s" of "%s::%s()": Union typehints are not supported.', 487 $param->name, 488 $method->class, 489 $method->name 490 ) 491 ); 492 } 493 494 // Check for a class, if it doesn't have one then it is a scalar type, which we cannot handle if a mandatory argument 495 if ($dependency->isBuiltin()) 496 { 497 // If the param is optional, then fall through to the optional param handling later in this method 498 if (!$param->isOptional()) 499 { 500 $message = 'Could not resolve the parameter "$%s" of "%s::%s()":'; 501 $message .= ' Scalar parameters cannot be autowired and the parameter does not have a default value.'; 502 503 throw new DependencyResolutionException( 504 sprintf( 505 $message, 506 $param->name, 507 $method->class, 508 $method->name 509 ) 510 ); 511 } 512 } 513 else 514 { 515 $dependencyClassName = $dependency->getName(); 516 517 // Check that class or interface exists 518 if (!interface_exists($dependencyClassName) && !class_exists($dependencyClassName)) 519 { 520 // If this is a nullable parameter, then don't error out 521 if ($param->allowsNull()) 522 { 523 $methodArgs[] = null; 524 525 continue; 526 } 527 528 throw new DependencyResolutionException( 529 sprintf( 530 'Could not resolve the parameter "$%s" of "%s::%s()": The "%s" class does not exist.', 531 $param->name, 532 $method->class, 533 $method->name, 534 $dependencyClassName 535 ) 536 ); 537 } 538 539 // If the dependency class name is registered with this container or a parent, use it. 540 if ($this->getResource($dependencyClassName) !== null) 541 { 542 $depObject = $this->get($dependencyClassName); 543 } 544 else 545 { 546 try 547 { 548 $depObject = $this->buildObject($dependencyClassName); 549 } 550 catch (DependencyResolutionException $exception) 551 { 552 // If this is a nullable parameter, then don't error out 553 if ($param->allowsNull()) 554 { 555 $methodArgs[] = null; 556 557 continue; 558 } 559 560 $message = 'Could not resolve the parameter "$%s" of "%s::%s()":'; 561 $message .= ' No service for "%s" exists and the dependency could not be autowired.'; 562 563 throw new DependencyResolutionException( 564 sprintf( 565 $message, 566 $param->name, 567 $method->class, 568 $method->name, 569 $dependencyClassName 570 ), 571 0, 572 $exception 573 ); 574 } 575 } 576 577 if ($depObject instanceof $dependencyClassName) 578 { 579 $methodArgs[] = $depObject; 580 581 continue; 582 } 583 } 584 } 585 586 // If there is a default parameter and it can be read, use it. 587 if ($param->isOptional() && $param->isDefaultValueAvailable()) 588 { 589 try 590 { 591 $methodArgs[] = $param->getDefaultValue(); 592 593 continue; 594 } 595 catch (\ReflectionException $exception) 596 { 597 throw new DependencyResolutionException( 598 sprintf( 599 'Could not resolve the parameter "$%s" of "%s::%s()": Unable to read the default parameter value.', 600 $param->name, 601 $method->class, 602 $method->name 603 ), 604 0, 605 $exception 606 ); 607 } 608 } 609 610 // If an untyped variadic argument, skip it 611 if (!$param->hasType() && $param->isVariadic()) 612 { 613 continue; 614 } 615 616 // At this point the argument cannot be resolved, most likely cause is an untyped required argument 617 throw new DependencyResolutionException( 618 sprintf( 619 'Could not resolve the parameter "$%s" of "%s::%s()": The argument is untyped and has no default value.', 620 $param->name, 621 $method->class, 622 $method->name 623 ) 624 ); 625 } 626 627 return $methodArgs; 628 } 629 630 /** 631 * Set a resource to the container. If the value is null the resource is removed. 632 * 633 * @param string $key Name of resources key to set. 634 * @param mixed $value Callable function to run or string to retrive when requesting the specified $key. 635 * @param boolean $shared True to create and store a shared instance. 636 * @param boolean $protected True to protect this item from being overwritten. Useful for services. 637 * 638 * @return $this 639 * 640 * @since 1.0 641 * @throws ProtectedKeyException Thrown if the provided key is already set and is protected. 642 */ 643 public function set($key, $value, $shared = false, $protected = false) 644 { 645 $key = $this->resolveAlias($key); 646 647 $hasKey = $this->has($key); 648 649 if ($hasKey && $this->isProtected($key)) 650 { 651 throw new ProtectedKeyException(sprintf("Key %s is protected and can't be overwritten.", $key)); 652 } 653 654 if ($value === null && $hasKey) 655 { 656 unset($this->resources[$key]); 657 658 return $this; 659 } 660 661 $mode = $shared ? ContainerResource::SHARE : ContainerResource::NO_SHARE; 662 $mode |= $protected ? ContainerResource::PROTECT : ContainerResource::NO_PROTECT; 663 664 $this->resources[$key] = new ContainerResource($this, $value, $mode); 665 666 return $this; 667 } 668 669 /** 670 * Shortcut method for creating protected keys. 671 * 672 * @param string $key Name of dataStore key to set. 673 * @param mixed $value Callable function to run or string to retrive when requesting the specified $key. 674 * @param boolean $shared True to create and store a shared instance. 675 * 676 * @return $this 677 * 678 * @since 1.0 679 */ 680 public function protect($key, $value, $shared = false) 681 { 682 return $this->set($key, $value, $shared, true); 683 } 684 685 /** 686 * Shortcut method for creating shared keys. 687 * 688 * @param string $key Name of dataStore key to set. 689 * @param mixed $value Callable function to run or string to retrive when requesting the specified $key. 690 * @param boolean $protected True to protect this item from being overwritten. Useful for services. 691 * 692 * @return $this 693 * 694 * @since 1.0 695 */ 696 public function share($key, $value, $protected = false) 697 { 698 return $this->set($key, $value, true, $protected); 699 } 700 701 /** 702 * Get the raw data assigned to a key. 703 * 704 * @param string $key The key for which to get the stored item. 705 * @param boolean $bail Throw an exception, if the key is not found 706 * 707 * @return ContainerResource|null The resource if present, or null if instructed to not bail 708 * 709 * @since 2.0.0 710 * @throws KeyNotFoundException 711 */ 712 public function getResource(string $key, bool $bail = false): ?ContainerResource 713 { 714 if (isset($this->resources[$key])) 715 { 716 return $this->resources[$key]; 717 } 718 719 if ($this->parent instanceof self) 720 { 721 return $this->parent->getResource($key); 722 } 723 724 if ($this->parent instanceof ContainerInterface && $this->parent->has($key)) 725 { 726 return new ContainerResource($this, $this->parent->get($key), ContainerResource::SHARE | ContainerResource::PROTECT); 727 } 728 729 if ($bail) 730 { 731 throw new KeyNotFoundException(sprintf('Key %s has not been registered with the container.', $key)); 732 } 733 734 return null; 735 } 736 737 /** 738 * Method to force the container to return a new instance of the results of the callback for requested $key. 739 * 740 * @param string $key Name of the resources key to get. 741 * 742 * @return mixed Results of running the callback for the specified key. 743 * 744 * @since 1.0 745 */ 746 public function getNewInstance($key) 747 { 748 $key = $this->resolveAlias($key); 749 750 $this->getResource($key, true)->reset(); 751 752 return $this->get($key); 753 } 754 755 /** 756 * Register a service provider to the container. 757 * 758 * @param ServiceProviderInterface $provider The service provider to register. 759 * 760 * @return $this 761 * 762 * @since 1.0 763 */ 764 public function registerServiceProvider(ServiceProviderInterface $provider) 765 { 766 $provider->register($this); 767 768 return $this; 769 } 770 771 /** 772 * Retrieve the keys for services assigned to this container. 773 * 774 * @return array 775 * 776 * @since 1.5.0 777 */ 778 public function getKeys() 779 { 780 return array_unique(array_merge(array_keys($this->aliases), array_keys($this->resources))); 781 } 782 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |