[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/symfony/options-resolver/ -> OptionsResolver.php (source)

   1  <?php
   2  
   3  /*
   4   * This file is part of the Symfony package.
   5   *
   6   * (c) Fabien Potencier <[email protected]>
   7   *
   8   * For the full copyright and license information, please view the LICENSE
   9   * file that was distributed with this source code.
  10   */
  11  
  12  namespace Symfony\Component\OptionsResolver;
  13  
  14  use Symfony\Component\OptionsResolver\Exception\AccessException;
  15  use Symfony\Component\OptionsResolver\Exception\InvalidArgumentException;
  16  use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
  17  use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
  18  use Symfony\Component\OptionsResolver\Exception\NoSuchOptionException;
  19  use Symfony\Component\OptionsResolver\Exception\OptionDefinitionException;
  20  use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException;
  21  
  22  /**
  23   * Validates options and merges them with default values.
  24   *
  25   * @author Bernhard Schussek <[email protected]>
  26   * @author Tobias Schultze <http://tobion.de>
  27   */
  28  class OptionsResolver implements Options
  29  {
  30      private const VALIDATION_FUNCTIONS = [
  31          'bool' => 'is_bool',
  32          'boolean' => 'is_bool',
  33          'int' => 'is_int',
  34          'integer' => 'is_int',
  35          'long' => 'is_int',
  36          'float' => 'is_float',
  37          'double' => 'is_float',
  38          'real' => 'is_float',
  39          'numeric' => 'is_numeric',
  40          'string' => 'is_string',
  41          'scalar' => 'is_scalar',
  42          'array' => 'is_array',
  43          'iterable' => 'is_iterable',
  44          'countable' => 'is_countable',
  45          'callable' => 'is_callable',
  46          'object' => 'is_object',
  47          'resource' => 'is_resource',
  48      ];
  49  
  50      /**
  51       * The names of all defined options.
  52       */
  53      private $defined = [];
  54  
  55      /**
  56       * The default option values.
  57       */
  58      private $defaults = [];
  59  
  60      /**
  61       * A list of closure for nested options.
  62       *
  63       * @var \Closure[][]
  64       */
  65      private $nested = [];
  66  
  67      /**
  68       * The names of required options.
  69       */
  70      private $required = [];
  71  
  72      /**
  73       * The resolved option values.
  74       */
  75      private $resolved = [];
  76  
  77      /**
  78       * A list of normalizer closures.
  79       *
  80       * @var \Closure[][]
  81       */
  82      private $normalizers = [];
  83  
  84      /**
  85       * A list of accepted values for each option.
  86       */
  87      private $allowedValues = [];
  88  
  89      /**
  90       * A list of accepted types for each option.
  91       */
  92      private $allowedTypes = [];
  93  
  94      /**
  95       * A list of info messages for each option.
  96       */
  97      private $info = [];
  98  
  99      /**
 100       * A list of closures for evaluating lazy options.
 101       */
 102      private $lazy = [];
 103  
 104      /**
 105       * A list of lazy options whose closure is currently being called.
 106       *
 107       * This list helps detecting circular dependencies between lazy options.
 108       */
 109      private $calling = [];
 110  
 111      /**
 112       * A list of deprecated options.
 113       */
 114      private $deprecated = [];
 115  
 116      /**
 117       * The list of options provided by the user.
 118       */
 119      private $given = [];
 120  
 121      /**
 122       * Whether the instance is locked for reading.
 123       *
 124       * Once locked, the options cannot be changed anymore. This is
 125       * necessary in order to avoid inconsistencies during the resolving
 126       * process. If any option is changed after being read, all evaluated
 127       * lazy options that depend on this option would become invalid.
 128       */
 129      private $locked = false;
 130  
 131      private $parentsOptions = [];
 132  
 133      /**
 134       * Whether the whole options definition is marked as array prototype.
 135       */
 136      private $prototype;
 137  
 138      /**
 139       * The prototype array's index that is being read.
 140       */
 141      private $prototypeIndex;
 142  
 143      /**
 144       * Sets the default value of a given option.
 145       *
 146       * If the default value should be set based on other options, you can pass
 147       * a closure with the following signature:
 148       *
 149       *     function (Options $options) {
 150       *         // ...
 151       *     }
 152       *
 153       * The closure will be evaluated when {@link resolve()} is called. The
 154       * closure has access to the resolved values of other options through the
 155       * passed {@link Options} instance:
 156       *
 157       *     function (Options $options) {
 158       *         if (isset($options['port'])) {
 159       *             // ...
 160       *         }
 161       *     }
 162       *
 163       * If you want to access the previously set default value, add a second
 164       * argument to the closure's signature:
 165       *
 166       *     $options->setDefault('name', 'Default Name');
 167       *
 168       *     $options->setDefault('name', function (Options $options, $previousValue) {
 169       *         // 'Default Name' === $previousValue
 170       *     });
 171       *
 172       * This is mostly useful if the configuration of the {@link Options} object
 173       * is spread across different locations of your code, such as base and
 174       * sub-classes.
 175       *
 176       * If you want to define nested options, you can pass a closure with the
 177       * following signature:
 178       *
 179       *     $options->setDefault('database', function (OptionsResolver $resolver) {
 180       *         $resolver->setDefined(['dbname', 'host', 'port', 'user', 'pass']);
 181       *     }
 182       *
 183       * To get access to the parent options, add a second argument to the closure's
 184       * signature:
 185       *
 186       *     function (OptionsResolver $resolver, Options $parent) {
 187       *         // 'default' === $parent['connection']
 188       *     }
 189       *
 190       * @param string $option The name of the option
 191       * @param mixed  $value  The default value of the option
 192       *
 193       * @return $this
 194       *
 195       * @throws AccessException If called from a lazy option or normalizer
 196       */
 197      public function setDefault(string $option, $value)
 198      {
 199          // Setting is not possible once resolving starts, because then lazy
 200          // options could manipulate the state of the object, leading to
 201          // inconsistent results.
 202          if ($this->locked) {
 203              throw new AccessException('Default values cannot be set from a lazy option or normalizer.');
 204          }
 205  
 206          // If an option is a closure that should be evaluated lazily, store it
 207          // in the "lazy" property.
 208          if ($value instanceof \Closure) {
 209              $reflClosure = new \ReflectionFunction($value);
 210              $params = $reflClosure->getParameters();
 211  
 212              if (isset($params[0]) && Options::class === $this->getParameterClassName($params[0])) {
 213                  // Initialize the option if no previous value exists
 214                  if (!isset($this->defaults[$option])) {
 215                      $this->defaults[$option] = null;
 216                  }
 217  
 218                  // Ignore previous lazy options if the closure has no second parameter
 219                  if (!isset($this->lazy[$option]) || !isset($params[1])) {
 220                      $this->lazy[$option] = [];
 221                  }
 222  
 223                  // Store closure for later evaluation
 224                  $this->lazy[$option][] = $value;
 225                  $this->defined[$option] = true;
 226  
 227                  // Make sure the option is processed and is not nested anymore
 228                  unset($this->resolved[$option], $this->nested[$option]);
 229  
 230                  return $this;
 231              }
 232  
 233              if (isset($params[0]) && null !== ($type = $params[0]->getType()) && self::class === $type->getName() && (!isset($params[1]) || (($type = $params[1]->getType()) instanceof \ReflectionNamedType && Options::class === $type->getName()))) {
 234                  // Store closure for later evaluation
 235                  $this->nested[$option][] = $value;
 236                  $this->defaults[$option] = [];
 237                  $this->defined[$option] = true;
 238  
 239                  // Make sure the option is processed and is not lazy anymore
 240                  unset($this->resolved[$option], $this->lazy[$option]);
 241  
 242                  return $this;
 243              }
 244          }
 245  
 246          // This option is not lazy nor nested anymore
 247          unset($this->lazy[$option], $this->nested[$option]);
 248  
 249          // Yet undefined options can be marked as resolved, because we only need
 250          // to resolve options with lazy closures, normalizers or validation
 251          // rules, none of which can exist for undefined options
 252          // If the option was resolved before, update the resolved value
 253          if (!isset($this->defined[$option]) || \array_key_exists($option, $this->resolved)) {
 254              $this->resolved[$option] = $value;
 255          }
 256  
 257          $this->defaults[$option] = $value;
 258          $this->defined[$option] = true;
 259  
 260          return $this;
 261      }
 262  
 263      /**
 264       * @return $this
 265       *
 266       * @throws AccessException If called from a lazy option or normalizer
 267       */
 268      public function setDefaults(array $defaults)
 269      {
 270          foreach ($defaults as $option => $value) {
 271              $this->setDefault($option, $value);
 272          }
 273  
 274          return $this;
 275      }
 276  
 277      /**
 278       * Returns whether a default value is set for an option.
 279       *
 280       * Returns true if {@link setDefault()} was called for this option.
 281       * An option is also considered set if it was set to null.
 282       *
 283       * @return bool
 284       */
 285      public function hasDefault(string $option)
 286      {
 287          return \array_key_exists($option, $this->defaults);
 288      }
 289  
 290      /**
 291       * Marks one or more options as required.
 292       *
 293       * @param string|string[] $optionNames One or more option names
 294       *
 295       * @return $this
 296       *
 297       * @throws AccessException If called from a lazy option or normalizer
 298       */
 299      public function setRequired($optionNames)
 300      {
 301          if ($this->locked) {
 302              throw new AccessException('Options cannot be made required from a lazy option or normalizer.');
 303          }
 304  
 305          foreach ((array) $optionNames as $option) {
 306              $this->defined[$option] = true;
 307              $this->required[$option] = true;
 308          }
 309  
 310          return $this;
 311      }
 312  
 313      /**
 314       * Returns whether an option is required.
 315       *
 316       * An option is required if it was passed to {@link setRequired()}.
 317       *
 318       * @return bool
 319       */
 320      public function isRequired(string $option)
 321      {
 322          return isset($this->required[$option]);
 323      }
 324  
 325      /**
 326       * Returns the names of all required options.
 327       *
 328       * @return string[]
 329       *
 330       * @see isRequired()
 331       */
 332      public function getRequiredOptions()
 333      {
 334          return array_keys($this->required);
 335      }
 336  
 337      /**
 338       * Returns whether an option is missing a default value.
 339       *
 340       * An option is missing if it was passed to {@link setRequired()}, but not
 341       * to {@link setDefault()}. This option must be passed explicitly to
 342       * {@link resolve()}, otherwise an exception will be thrown.
 343       *
 344       * @return bool
 345       */
 346      public function isMissing(string $option)
 347      {
 348          return isset($this->required[$option]) && !\array_key_exists($option, $this->defaults);
 349      }
 350  
 351      /**
 352       * Returns the names of all options missing a default value.
 353       *
 354       * @return string[]
 355       */
 356      public function getMissingOptions()
 357      {
 358          return array_keys(array_diff_key($this->required, $this->defaults));
 359      }
 360  
 361      /**
 362       * Defines a valid option name.
 363       *
 364       * Defines an option name without setting a default value. The option will
 365       * be accepted when passed to {@link resolve()}. When not passed, the
 366       * option will not be included in the resolved options.
 367       *
 368       * @param string|string[] $optionNames One or more option names
 369       *
 370       * @return $this
 371       *
 372       * @throws AccessException If called from a lazy option or normalizer
 373       */
 374      public function setDefined($optionNames)
 375      {
 376          if ($this->locked) {
 377              throw new AccessException('Options cannot be defined from a lazy option or normalizer.');
 378          }
 379  
 380          foreach ((array) $optionNames as $option) {
 381              $this->defined[$option] = true;
 382          }
 383  
 384          return $this;
 385      }
 386  
 387      /**
 388       * Returns whether an option is defined.
 389       *
 390       * Returns true for any option passed to {@link setDefault()},
 391       * {@link setRequired()} or {@link setDefined()}.
 392       *
 393       * @return bool
 394       */
 395      public function isDefined(string $option)
 396      {
 397          return isset($this->defined[$option]);
 398      }
 399  
 400      /**
 401       * Returns the names of all defined options.
 402       *
 403       * @return string[]
 404       *
 405       * @see isDefined()
 406       */
 407      public function getDefinedOptions()
 408      {
 409          return array_keys($this->defined);
 410      }
 411  
 412      public function isNested(string $option): bool
 413      {
 414          return isset($this->nested[$option]);
 415      }
 416  
 417      /**
 418       * Deprecates an option, allowed types or values.
 419       *
 420       * Instead of passing the message, you may also pass a closure with the
 421       * following signature:
 422       *
 423       *     function (Options $options, $value): string {
 424       *         // ...
 425       *     }
 426       *
 427       * The closure receives the value as argument and should return a string.
 428       * Return an empty string to ignore the option deprecation.
 429       *
 430       * The closure is invoked when {@link resolve()} is called. The parameter
 431       * passed to the closure is the value of the option after validating it
 432       * and before normalizing it.
 433       *
 434       * @param string          $package The name of the composer package that is triggering the deprecation
 435       * @param string          $version The version of the package that introduced the deprecation
 436       * @param string|\Closure $message The deprecation message to use
 437       *
 438       * @return $this
 439       */
 440      public function setDeprecated(string $option/*, string $package, string $version, $message = 'The option "%name%" is deprecated.' */): self
 441      {
 442          if ($this->locked) {
 443              throw new AccessException('Options cannot be deprecated from a lazy option or normalizer.');
 444          }
 445  
 446          if (!isset($this->defined[$option])) {
 447              throw new UndefinedOptionsException(sprintf('The option "%s" does not exist, defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
 448          }
 449  
 450          $args = \func_get_args();
 451  
 452          if (\func_num_args() < 3) {
 453              trigger_deprecation('symfony/options-resolver', '5.1', 'The signature of method "%s()" requires 2 new arguments: "string $package, string $version", not defining them is deprecated.', __METHOD__);
 454  
 455              $message = $args[1] ?? 'The option "%name%" is deprecated.';
 456              $package = $version = '';
 457          } else {
 458              $package = $args[1];
 459              $version = $args[2];
 460              $message = $args[3] ?? 'The option "%name%" is deprecated.';
 461          }
 462  
 463          if (!\is_string($message) && !$message instanceof \Closure) {
 464              throw new InvalidArgumentException(sprintf('Invalid type for deprecation message argument, expected string or \Closure, but got "%s".', get_debug_type($message)));
 465          }
 466  
 467          // ignore if empty string
 468          if ('' === $message) {
 469              return $this;
 470          }
 471  
 472          $this->deprecated[$option] = [
 473              'package' => $package,
 474              'version' => $version,
 475              'message' => $message,
 476          ];
 477  
 478          // Make sure the option is processed
 479          unset($this->resolved[$option]);
 480  
 481          return $this;
 482      }
 483  
 484      public function isDeprecated(string $option): bool
 485      {
 486          return isset($this->deprecated[$option]);
 487      }
 488  
 489      /**
 490       * Sets the normalizer for an option.
 491       *
 492       * The normalizer should be a closure with the following signature:
 493       *
 494       *     function (Options $options, $value) {
 495       *         // ...
 496       *     }
 497       *
 498       * The closure is invoked when {@link resolve()} is called. The closure
 499       * has access to the resolved values of other options through the passed
 500       * {@link Options} instance.
 501       *
 502       * The second parameter passed to the closure is the value of
 503       * the option.
 504       *
 505       * The resolved option value is set to the return value of the closure.
 506       *
 507       * @return $this
 508       *
 509       * @throws UndefinedOptionsException If the option is undefined
 510       * @throws AccessException           If called from a lazy option or normalizer
 511       */
 512      public function setNormalizer(string $option, \Closure $normalizer)
 513      {
 514          if ($this->locked) {
 515              throw new AccessException('Normalizers cannot be set from a lazy option or normalizer.');
 516          }
 517  
 518          if (!isset($this->defined[$option])) {
 519              throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
 520          }
 521  
 522          $this->normalizers[$option] = [$normalizer];
 523  
 524          // Make sure the option is processed
 525          unset($this->resolved[$option]);
 526  
 527          return $this;
 528      }
 529  
 530      /**
 531       * Adds a normalizer for an option.
 532       *
 533       * The normalizer should be a closure with the following signature:
 534       *
 535       *     function (Options $options, $value): mixed {
 536       *         // ...
 537       *     }
 538       *
 539       * The closure is invoked when {@link resolve()} is called. The closure
 540       * has access to the resolved values of other options through the passed
 541       * {@link Options} instance.
 542       *
 543       * The second parameter passed to the closure is the value of
 544       * the option.
 545       *
 546       * The resolved option value is set to the return value of the closure.
 547       *
 548       * @return $this
 549       *
 550       * @throws UndefinedOptionsException If the option is undefined
 551       * @throws AccessException           If called from a lazy option or normalizer
 552       */
 553      public function addNormalizer(string $option, \Closure $normalizer, bool $forcePrepend = false): self
 554      {
 555          if ($this->locked) {
 556              throw new AccessException('Normalizers cannot be set from a lazy option or normalizer.');
 557          }
 558  
 559          if (!isset($this->defined[$option])) {
 560              throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
 561          }
 562  
 563          if ($forcePrepend) {
 564              $this->normalizers[$option] = $this->normalizers[$option] ?? [];
 565              array_unshift($this->normalizers[$option], $normalizer);
 566          } else {
 567              $this->normalizers[$option][] = $normalizer;
 568          }
 569  
 570          // Make sure the option is processed
 571          unset($this->resolved[$option]);
 572  
 573          return $this;
 574      }
 575  
 576      /**
 577       * Sets allowed values for an option.
 578       *
 579       * Instead of passing values, you may also pass a closures with the
 580       * following signature:
 581       *
 582       *     function ($value) {
 583       *         // return true or false
 584       *     }
 585       *
 586       * The closure receives the value as argument and should return true to
 587       * accept the value and false to reject the value.
 588       *
 589       * @param string $option        The option name
 590       * @param mixed  $allowedValues One or more acceptable values/closures
 591       *
 592       * @return $this
 593       *
 594       * @throws UndefinedOptionsException If the option is undefined
 595       * @throws AccessException           If called from a lazy option or normalizer
 596       */
 597      public function setAllowedValues(string $option, $allowedValues)
 598      {
 599          if ($this->locked) {
 600              throw new AccessException('Allowed values cannot be set from a lazy option or normalizer.');
 601          }
 602  
 603          if (!isset($this->defined[$option])) {
 604              throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
 605          }
 606  
 607          $this->allowedValues[$option] = \is_array($allowedValues) ? $allowedValues : [$allowedValues];
 608  
 609          // Make sure the option is processed
 610          unset($this->resolved[$option]);
 611  
 612          return $this;
 613      }
 614  
 615      /**
 616       * Adds allowed values for an option.
 617       *
 618       * The values are merged with the allowed values defined previously.
 619       *
 620       * Instead of passing values, you may also pass a closures with the
 621       * following signature:
 622       *
 623       *     function ($value) {
 624       *         // return true or false
 625       *     }
 626       *
 627       * The closure receives the value as argument and should return true to
 628       * accept the value and false to reject the value.
 629       *
 630       * @param string $option        The option name
 631       * @param mixed  $allowedValues One or more acceptable values/closures
 632       *
 633       * @return $this
 634       *
 635       * @throws UndefinedOptionsException If the option is undefined
 636       * @throws AccessException           If called from a lazy option or normalizer
 637       */
 638      public function addAllowedValues(string $option, $allowedValues)
 639      {
 640          if ($this->locked) {
 641              throw new AccessException('Allowed values cannot be added from a lazy option or normalizer.');
 642          }
 643  
 644          if (!isset($this->defined[$option])) {
 645              throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
 646          }
 647  
 648          if (!\is_array($allowedValues)) {
 649              $allowedValues = [$allowedValues];
 650          }
 651  
 652          if (!isset($this->allowedValues[$option])) {
 653              $this->allowedValues[$option] = $allowedValues;
 654          } else {
 655              $this->allowedValues[$option] = array_merge($this->allowedValues[$option], $allowedValues);
 656          }
 657  
 658          // Make sure the option is processed
 659          unset($this->resolved[$option]);
 660  
 661          return $this;
 662      }
 663  
 664      /**
 665       * Sets allowed types for an option.
 666       *
 667       * Any type for which a corresponding is_<type>() function exists is
 668       * acceptable. Additionally, fully-qualified class or interface names may
 669       * be passed.
 670       *
 671       * @param string|string[] $allowedTypes One or more accepted types
 672       *
 673       * @return $this
 674       *
 675       * @throws UndefinedOptionsException If the option is undefined
 676       * @throws AccessException           If called from a lazy option or normalizer
 677       */
 678      public function setAllowedTypes(string $option, $allowedTypes)
 679      {
 680          if ($this->locked) {
 681              throw new AccessException('Allowed types cannot be set from a lazy option or normalizer.');
 682          }
 683  
 684          if (!isset($this->defined[$option])) {
 685              throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
 686          }
 687  
 688          $this->allowedTypes[$option] = (array) $allowedTypes;
 689  
 690          // Make sure the option is processed
 691          unset($this->resolved[$option]);
 692  
 693          return $this;
 694      }
 695  
 696      /**
 697       * Adds allowed types for an option.
 698       *
 699       * The types are merged with the allowed types defined previously.
 700       *
 701       * Any type for which a corresponding is_<type>() function exists is
 702       * acceptable. Additionally, fully-qualified class or interface names may
 703       * be passed.
 704       *
 705       * @param string|string[] $allowedTypes One or more accepted types
 706       *
 707       * @return $this
 708       *
 709       * @throws UndefinedOptionsException If the option is undefined
 710       * @throws AccessException           If called from a lazy option or normalizer
 711       */
 712      public function addAllowedTypes(string $option, $allowedTypes)
 713      {
 714          if ($this->locked) {
 715              throw new AccessException('Allowed types cannot be added from a lazy option or normalizer.');
 716          }
 717  
 718          if (!isset($this->defined[$option])) {
 719              throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
 720          }
 721  
 722          if (!isset($this->allowedTypes[$option])) {
 723              $this->allowedTypes[$option] = (array) $allowedTypes;
 724          } else {
 725              $this->allowedTypes[$option] = array_merge($this->allowedTypes[$option], (array) $allowedTypes);
 726          }
 727  
 728          // Make sure the option is processed
 729          unset($this->resolved[$option]);
 730  
 731          return $this;
 732      }
 733  
 734      /**
 735       * Defines an option configurator with the given name.
 736       */
 737      public function define(string $option): OptionConfigurator
 738      {
 739          if (isset($this->defined[$option])) {
 740              throw new OptionDefinitionException(sprintf('The option "%s" is already defined.', $option));
 741          }
 742  
 743          return new OptionConfigurator($option, $this);
 744      }
 745  
 746      /**
 747       * Sets an info message for an option.
 748       *
 749       * @return $this
 750       *
 751       * @throws UndefinedOptionsException If the option is undefined
 752       * @throws AccessException           If called from a lazy option or normalizer
 753       */
 754      public function setInfo(string $option, string $info): self
 755      {
 756          if ($this->locked) {
 757              throw new AccessException('The Info message cannot be set from a lazy option or normalizer.');
 758          }
 759  
 760          if (!isset($this->defined[$option])) {
 761              throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
 762          }
 763  
 764          $this->info[$option] = $info;
 765  
 766          return $this;
 767      }
 768  
 769      /**
 770       * Gets the info message for an option.
 771       */
 772      public function getInfo(string $option): ?string
 773      {
 774          if (!isset($this->defined[$option])) {
 775              throw new UndefinedOptionsException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
 776          }
 777  
 778          return $this->info[$option] ?? null;
 779      }
 780  
 781      /**
 782       * Marks the whole options definition as array prototype.
 783       *
 784       * @return $this
 785       *
 786       * @throws AccessException If called from a lazy option, a normalizer or a root definition
 787       */
 788      public function setPrototype(bool $prototype): self
 789      {
 790          if ($this->locked) {
 791              throw new AccessException('The prototype property cannot be set from a lazy option or normalizer.');
 792          }
 793  
 794          if (null === $this->prototype && $prototype) {
 795              throw new AccessException('The prototype property cannot be set from a root definition.');
 796          }
 797  
 798          $this->prototype = $prototype;
 799  
 800          return $this;
 801      }
 802  
 803      public function isPrototype(): bool
 804      {
 805          return $this->prototype ?? false;
 806      }
 807  
 808      /**
 809       * Removes the option with the given name.
 810       *
 811       * Undefined options are ignored.
 812       *
 813       * @param string|string[] $optionNames One or more option names
 814       *
 815       * @return $this
 816       *
 817       * @throws AccessException If called from a lazy option or normalizer
 818       */
 819      public function remove($optionNames)
 820      {
 821          if ($this->locked) {
 822              throw new AccessException('Options cannot be removed from a lazy option or normalizer.');
 823          }
 824  
 825          foreach ((array) $optionNames as $option) {
 826              unset($this->defined[$option], $this->defaults[$option], $this->required[$option], $this->resolved[$option]);
 827              unset($this->lazy[$option], $this->normalizers[$option], $this->allowedTypes[$option], $this->allowedValues[$option], $this->info[$option]);
 828          }
 829  
 830          return $this;
 831      }
 832  
 833      /**
 834       * Removes all options.
 835       *
 836       * @return $this
 837       *
 838       * @throws AccessException If called from a lazy option or normalizer
 839       */
 840      public function clear()
 841      {
 842          if ($this->locked) {
 843              throw new AccessException('Options cannot be cleared from a lazy option or normalizer.');
 844          }
 845  
 846          $this->defined = [];
 847          $this->defaults = [];
 848          $this->nested = [];
 849          $this->required = [];
 850          $this->resolved = [];
 851          $this->lazy = [];
 852          $this->normalizers = [];
 853          $this->allowedTypes = [];
 854          $this->allowedValues = [];
 855          $this->deprecated = [];
 856          $this->info = [];
 857  
 858          return $this;
 859      }
 860  
 861      /**
 862       * Merges options with the default values stored in the container and
 863       * validates them.
 864       *
 865       * Exceptions are thrown if:
 866       *
 867       *  - Undefined options are passed;
 868       *  - Required options are missing;
 869       *  - Options have invalid types;
 870       *  - Options have invalid values.
 871       *
 872       * @return array
 873       *
 874       * @throws UndefinedOptionsException If an option name is undefined
 875       * @throws InvalidOptionsException   If an option doesn't fulfill the
 876       *                                   specified validation rules
 877       * @throws MissingOptionsException   If a required option is missing
 878       * @throws OptionDefinitionException If there is a cyclic dependency between
 879       *                                   lazy options and/or normalizers
 880       * @throws NoSuchOptionException     If a lazy option reads an unavailable option
 881       * @throws AccessException           If called from a lazy option or normalizer
 882       */
 883      public function resolve(array $options = [])
 884      {
 885          if ($this->locked) {
 886              throw new AccessException('Options cannot be resolved from a lazy option or normalizer.');
 887          }
 888  
 889          // Allow this method to be called multiple times
 890          $clone = clone $this;
 891  
 892          // Make sure that no unknown options are passed
 893          $diff = array_diff_key($options, $clone->defined);
 894  
 895          if (\count($diff) > 0) {
 896              ksort($clone->defined);
 897              ksort($diff);
 898  
 899              throw new UndefinedOptionsException(sprintf((\count($diff) > 1 ? 'The options "%s" do not exist.' : 'The option "%s" does not exist.').' Defined options are: "%s".', $this->formatOptions(array_keys($diff)), implode('", "', array_keys($clone->defined))));
 900          }
 901  
 902          // Override options set by the user
 903          foreach ($options as $option => $value) {
 904              $clone->given[$option] = true;
 905              $clone->defaults[$option] = $value;
 906              unset($clone->resolved[$option], $clone->lazy[$option]);
 907          }
 908  
 909          // Check whether any required option is missing
 910          $diff = array_diff_key($clone->required, $clone->defaults);
 911  
 912          if (\count($diff) > 0) {
 913              ksort($diff);
 914  
 915              throw new MissingOptionsException(sprintf(\count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', $this->formatOptions(array_keys($diff))));
 916          }
 917  
 918          // Lock the container
 919          $clone->locked = true;
 920  
 921          // Now process the individual options. Use offsetGet(), which resolves
 922          // the option itself and any options that the option depends on
 923          foreach ($clone->defaults as $option => $_) {
 924              $clone->offsetGet($option);
 925          }
 926  
 927          return $clone->resolved;
 928      }
 929  
 930      /**
 931       * Returns the resolved value of an option.
 932       *
 933       * @param bool $triggerDeprecation Whether to trigger the deprecation or not (true by default)
 934       *
 935       * @return mixed
 936       *
 937       * @throws AccessException           If accessing this method outside of
 938       *                                   {@link resolve()}
 939       * @throws NoSuchOptionException     If the option is not set
 940       * @throws InvalidOptionsException   If the option doesn't fulfill the
 941       *                                   specified validation rules
 942       * @throws OptionDefinitionException If there is a cyclic dependency between
 943       *                                   lazy options and/or normalizers
 944       */
 945      #[\ReturnTypeWillChange]
 946      public function offsetGet($option, bool $triggerDeprecation = true)
 947      {
 948          if (!$this->locked) {
 949              throw new AccessException('Array access is only supported within closures of lazy options and normalizers.');
 950          }
 951  
 952          // Shortcut for resolved options
 953          if (isset($this->resolved[$option]) || \array_key_exists($option, $this->resolved)) {
 954              if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || $this->calling) && \is_string($this->deprecated[$option]['message'])) {
 955                  trigger_deprecation($this->deprecated[$option]['package'], $this->deprecated[$option]['version'], strtr($this->deprecated[$option]['message'], ['%name%' => $option]));
 956              }
 957  
 958              return $this->resolved[$option];
 959          }
 960  
 961          // Check whether the option is set at all
 962          if (!isset($this->defaults[$option]) && !\array_key_exists($option, $this->defaults)) {
 963              if (!isset($this->defined[$option])) {
 964                  throw new NoSuchOptionException(sprintf('The option "%s" does not exist. Defined options are: "%s".', $this->formatOptions([$option]), implode('", "', array_keys($this->defined))));
 965              }
 966  
 967              throw new NoSuchOptionException(sprintf('The optional option "%s" has no value set. You should make sure it is set with "isset" before reading it.', $this->formatOptions([$option])));
 968          }
 969  
 970          $value = $this->defaults[$option];
 971  
 972          // Resolve the option if it is a nested definition
 973          if (isset($this->nested[$option])) {
 974              // If the closure is already being called, we have a cyclic dependency
 975              if (isset($this->calling[$option])) {
 976                  throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
 977              }
 978  
 979              if (!\is_array($value)) {
 980                  throw new InvalidOptionsException(sprintf('The nested option "%s" with value %s is expected to be of type array, but is of type "%s".', $this->formatOptions([$option]), $this->formatValue($value), get_debug_type($value)));
 981              }
 982  
 983              // The following section must be protected from cyclic calls.
 984              $this->calling[$option] = true;
 985              try {
 986                  $resolver = new self();
 987                  $resolver->prototype = false;
 988                  $resolver->parentsOptions = $this->parentsOptions;
 989                  $resolver->parentsOptions[] = $option;
 990                  foreach ($this->nested[$option] as $closure) {
 991                      $closure($resolver, $this);
 992                  }
 993  
 994                  if ($resolver->prototype) {
 995                      $values = [];
 996                      foreach ($value as $index => $prototypeValue) {
 997                          if (!\is_array($prototypeValue)) {
 998                              throw new InvalidOptionsException(sprintf('The value of the option "%s" is expected to be of type array of array, but is of type array of "%s".', $this->formatOptions([$option]), get_debug_type($prototypeValue)));
 999                          }
1000  
1001                          $resolver->prototypeIndex = $index;
1002                          $values[$index] = $resolver->resolve($prototypeValue);
1003                      }
1004                      $value = $values;
1005                  } else {
1006                      $value = $resolver->resolve($value);
1007                  }
1008              } finally {
1009                  $resolver->prototypeIndex = null;
1010                  unset($this->calling[$option]);
1011              }
1012          }
1013  
1014          // Resolve the option if the default value is lazily evaluated
1015          if (isset($this->lazy[$option])) {
1016              // If the closure is already being called, we have a cyclic
1017              // dependency
1018              if (isset($this->calling[$option])) {
1019                  throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
1020              }
1021  
1022              // The following section must be protected from cyclic
1023              // calls. Set $calling for the current $option to detect a cyclic
1024              // dependency
1025              // BEGIN
1026              $this->calling[$option] = true;
1027              try {
1028                  foreach ($this->lazy[$option] as $closure) {
1029                      $value = $closure($this, $value);
1030                  }
1031              } finally {
1032                  unset($this->calling[$option]);
1033              }
1034              // END
1035          }
1036  
1037          // Validate the type of the resolved option
1038          if (isset($this->allowedTypes[$option])) {
1039              $valid = true;
1040              $invalidTypes = [];
1041  
1042              foreach ($this->allowedTypes[$option] as $type) {
1043                  if ($valid = $this->verifyTypes($type, $value, $invalidTypes)) {
1044                      break;
1045                  }
1046              }
1047  
1048              if (!$valid) {
1049                  $fmtActualValue = $this->formatValue($value);
1050                  $fmtAllowedTypes = implode('" or "', $this->allowedTypes[$option]);
1051                  $fmtProvidedTypes = implode('|', array_keys($invalidTypes));
1052                  $allowedContainsArrayType = \count(array_filter($this->allowedTypes[$option], static function ($item) {
1053                      return str_ends_with($item, '[]');
1054                  })) > 0;
1055  
1056                  if (\is_array($value) && $allowedContainsArrayType) {
1057                      throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but one of the elements is of type "%s".', $this->formatOptions([$option]), $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes));
1058                  }
1059  
1060                  throw new InvalidOptionsException(sprintf('The option "%s" with value %s is expected to be of type "%s", but is of type "%s".', $this->formatOptions([$option]), $fmtActualValue, $fmtAllowedTypes, $fmtProvidedTypes));
1061              }
1062          }
1063  
1064          // Validate the value of the resolved option
1065          if (isset($this->allowedValues[$option])) {
1066              $success = false;
1067              $printableAllowedValues = [];
1068  
1069              foreach ($this->allowedValues[$option] as $allowedValue) {
1070                  if ($allowedValue instanceof \Closure) {
1071                      if ($allowedValue($value)) {
1072                          $success = true;
1073                          break;
1074                      }
1075  
1076                      // Don't include closures in the exception message
1077                      continue;
1078                  }
1079  
1080                  if ($value === $allowedValue) {
1081                      $success = true;
1082                      break;
1083                  }
1084  
1085                  $printableAllowedValues[] = $allowedValue;
1086              }
1087  
1088              if (!$success) {
1089                  $message = sprintf(
1090                      'The option "%s" with value %s is invalid.',
1091                      $option,
1092                      $this->formatValue($value)
1093                  );
1094  
1095                  if (\count($printableAllowedValues) > 0) {
1096                      $message .= sprintf(
1097                          ' Accepted values are: %s.',
1098                          $this->formatValues($printableAllowedValues)
1099                      );
1100                  }
1101  
1102                  if (isset($this->info[$option])) {
1103                      $message .= sprintf(' Info: %s.', $this->info[$option]);
1104                  }
1105  
1106                  throw new InvalidOptionsException($message);
1107              }
1108          }
1109  
1110          // Check whether the option is deprecated
1111          // and it is provided by the user or is being called from a lazy evaluation
1112          if ($triggerDeprecation && isset($this->deprecated[$option]) && (isset($this->given[$option]) || ($this->calling && \is_string($this->deprecated[$option]['message'])))) {
1113              $deprecation = $this->deprecated[$option];
1114              $message = $this->deprecated[$option]['message'];
1115  
1116              if ($message instanceof \Closure) {
1117                  // If the closure is already being called, we have a cyclic dependency
1118                  if (isset($this->calling[$option])) {
1119                      throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
1120                  }
1121  
1122                  $this->calling[$option] = true;
1123                  try {
1124                      if (!\is_string($message = $message($this, $value))) {
1125                          throw new InvalidOptionsException(sprintf('Invalid type for deprecation message, expected string but got "%s", return an empty string to ignore.', get_debug_type($message)));
1126                      }
1127                  } finally {
1128                      unset($this->calling[$option]);
1129                  }
1130              }
1131  
1132              if ('' !== $message) {
1133                  trigger_deprecation($deprecation['package'], $deprecation['version'], strtr($message, ['%name%' => $option]));
1134              }
1135          }
1136  
1137          // Normalize the validated option
1138          if (isset($this->normalizers[$option])) {
1139              // If the closure is already being called, we have a cyclic
1140              // dependency
1141              if (isset($this->calling[$option])) {
1142                  throw new OptionDefinitionException(sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
1143              }
1144  
1145              // The following section must be protected from cyclic
1146              // calls. Set $calling for the current $option to detect a cyclic
1147              // dependency
1148              // BEGIN
1149              $this->calling[$option] = true;
1150              try {
1151                  foreach ($this->normalizers[$option] as $normalizer) {
1152                      $value = $normalizer($this, $value);
1153                  }
1154              } finally {
1155                  unset($this->calling[$option]);
1156              }
1157              // END
1158          }
1159  
1160          // Mark as resolved
1161          $this->resolved[$option] = $value;
1162  
1163          return $value;
1164      }
1165  
1166      private function verifyTypes(string $type, $value, array &$invalidTypes, int $level = 0): bool
1167      {
1168          if (\is_array($value) && '[]' === substr($type, -2)) {
1169              $type = substr($type, 0, -2);
1170              $valid = true;
1171  
1172              foreach ($value as $val) {
1173                  if (!$this->verifyTypes($type, $val, $invalidTypes, $level + 1)) {
1174                      $valid = false;
1175                  }
1176              }
1177  
1178              return $valid;
1179          }
1180  
1181          if (('null' === $type && null === $value) || (isset(self::VALIDATION_FUNCTIONS[$type]) ? self::VALIDATION_FUNCTIONS[$type]($value) : $value instanceof $type)) {
1182              return true;
1183          }
1184  
1185          if (!$invalidTypes || $level > 0) {
1186              $invalidTypes[get_debug_type($value)] = true;
1187          }
1188  
1189          return false;
1190      }
1191  
1192      /**
1193       * Returns whether a resolved option with the given name exists.
1194       *
1195       * @param string $option The option name
1196       *
1197       * @return bool
1198       *
1199       * @throws AccessException If accessing this method outside of {@link resolve()}
1200       *
1201       * @see \ArrayAccess::offsetExists()
1202       */
1203      #[\ReturnTypeWillChange]
1204      public function offsetExists($option)
1205      {
1206          if (!$this->locked) {
1207              throw new AccessException('Array access is only supported within closures of lazy options and normalizers.');
1208          }
1209  
1210          return \array_key_exists($option, $this->defaults);
1211      }
1212  
1213      /**
1214       * Not supported.
1215       *
1216       * @return void
1217       *
1218       * @throws AccessException
1219       */
1220      #[\ReturnTypeWillChange]
1221      public function offsetSet($option, $value)
1222      {
1223          throw new AccessException('Setting options via array access is not supported. Use setDefault() instead.');
1224      }
1225  
1226      /**
1227       * Not supported.
1228       *
1229       * @return void
1230       *
1231       * @throws AccessException
1232       */
1233      #[\ReturnTypeWillChange]
1234      public function offsetUnset($option)
1235      {
1236          throw new AccessException('Removing options via array access is not supported. Use remove() instead.');
1237      }
1238  
1239      /**
1240       * Returns the number of set options.
1241       *
1242       * This may be only a subset of the defined options.
1243       *
1244       * @return int
1245       *
1246       * @throws AccessException If accessing this method outside of {@link resolve()}
1247       *
1248       * @see \Countable::count()
1249       */
1250      #[\ReturnTypeWillChange]
1251      public function count()
1252      {
1253          if (!$this->locked) {
1254              throw new AccessException('Counting is only supported within closures of lazy options and normalizers.');
1255          }
1256  
1257          return \count($this->defaults);
1258      }
1259  
1260      /**
1261       * Returns a string representation of the value.
1262       *
1263       * This method returns the equivalent PHP tokens for most scalar types
1264       * (i.e. "false" for false, "1" for 1 etc.). Strings are always wrapped
1265       * in double quotes (").
1266       *
1267       * @param mixed $value The value to format as string
1268       */
1269      private function formatValue($value): string
1270      {
1271          if (\is_object($value)) {
1272              return \get_class($value);
1273          }
1274  
1275          if (\is_array($value)) {
1276              return 'array';
1277          }
1278  
1279          if (\is_string($value)) {
1280              return '"'.$value.'"';
1281          }
1282  
1283          if (\is_resource($value)) {
1284              return 'resource';
1285          }
1286  
1287          if (null === $value) {
1288              return 'null';
1289          }
1290  
1291          if (false === $value) {
1292              return 'false';
1293          }
1294  
1295          if (true === $value) {
1296              return 'true';
1297          }
1298  
1299          return (string) $value;
1300      }
1301  
1302      /**
1303       * Returns a string representation of a list of values.
1304       *
1305       * Each of the values is converted to a string using
1306       * {@link formatValue()}. The values are then concatenated with commas.
1307       *
1308       * @see formatValue()
1309       */
1310      private function formatValues(array $values): string
1311      {
1312          foreach ($values as $key => $value) {
1313              $values[$key] = $this->formatValue($value);
1314          }
1315  
1316          return implode(', ', $values);
1317      }
1318  
1319      private function formatOptions(array $options): string
1320      {
1321          if ($this->parentsOptions) {
1322              $prefix = array_shift($this->parentsOptions);
1323              if ($this->parentsOptions) {
1324                  $prefix .= sprintf('[%s]', implode('][', $this->parentsOptions));
1325              }
1326  
1327              if ($this->prototype && null !== $this->prototypeIndex) {
1328                  $prefix .= sprintf('[%s]', $this->prototypeIndex);
1329              }
1330  
1331              $options = array_map(static function (string $option) use ($prefix): string {
1332                  return sprintf('%s[%s]', $prefix, $option);
1333              }, $options);
1334          }
1335  
1336          return implode('", "', $options);
1337      }
1338  
1339      private function getParameterClassName(\ReflectionParameter $parameter): ?string
1340      {
1341          if (!($type = $parameter->getType()) instanceof \ReflectionNamedType || $type->isBuiltin()) {
1342              return null;
1343          }
1344  
1345          return $type->getName();
1346      }
1347  }


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