[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/joomla/di/src/ -> Container.php (source)

   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  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer