VariableBag.php 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. <?php
  2. /**
  3. * League.Uri (https://uri.thephpleague.com)
  4. *
  5. * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. declare(strict_types=1);
  11. namespace League\Uri\UriTemplate;
  12. use ArrayAccess;
  13. use BackedEnum;
  14. use Closure;
  15. use Countable;
  16. use IteratorAggregate;
  17. use Stringable;
  18. use Traversable;
  19. use function array_filter;
  20. use function is_bool;
  21. use function is_scalar;
  22. use const ARRAY_FILTER_USE_BOTH;
  23. /**
  24. * @internal The class exposes the internal representation of variable bags
  25. *
  26. * @phpstan-type InputValue string|bool|int|float|array<string|bool|int|float>
  27. *
  28. * @implements ArrayAccess<string, InputValue>
  29. * @implements IteratorAggregate<string, InputValue>
  30. */
  31. final class VariableBag implements ArrayAccess, Countable, IteratorAggregate
  32. {
  33. /**
  34. * @var array<string,string|array<string>>
  35. */
  36. private array $variables = [];
  37. /**
  38. * @param iterable<array-key, InputValue> $variables
  39. */
  40. public function __construct(iterable $variables = [])
  41. {
  42. foreach ($variables as $name => $value) {
  43. $this->assign((string) $name, $value);
  44. }
  45. }
  46. public function count(): int
  47. {
  48. return count($this->variables);
  49. }
  50. public function getIterator(): Traversable
  51. {
  52. yield from $this->variables;
  53. }
  54. public function offsetExists(mixed $offset): bool
  55. {
  56. return array_key_exists($offset, $this->variables);
  57. }
  58. public function offsetUnset(mixed $offset): void
  59. {
  60. unset($this->variables[$offset]);
  61. }
  62. public function offsetSet(mixed $offset, mixed $value): void
  63. {
  64. $this->assign($offset, $value); /* @phpstan-ignore-line */
  65. }
  66. public function offsetGet(mixed $offset): mixed
  67. {
  68. return $this->fetch($offset);
  69. }
  70. /**
  71. * Tells whether the bag is empty or not.
  72. */
  73. public function isEmpty(): bool
  74. {
  75. return [] === $this->variables;
  76. }
  77. /**
  78. * Tells whether the bag is empty or not.
  79. */
  80. public function isNotEmpty(): bool
  81. {
  82. return [] !== $this->variables;
  83. }
  84. public function equals(mixed $value): bool
  85. {
  86. return $value instanceof self
  87. && $this->variables === $value->variables;
  88. }
  89. /**
  90. * Fetches the variable value if none found returns null.
  91. *
  92. * @return null|string|array<string>
  93. */
  94. public function fetch(string $name): null|string|array
  95. {
  96. return $this->variables[$name] ?? null;
  97. }
  98. /**
  99. * @param Stringable|InputValue $value
  100. */
  101. public function assign(string $name, BackedEnum|Stringable|string|bool|int|float|array|null $value): void
  102. {
  103. $this->variables[$name] = $this->normalizeValue($value, $name, true);
  104. }
  105. /**
  106. * @param Stringable|InputValue $value
  107. *
  108. * @throws TemplateCanNotBeExpanded if the value contains nested list
  109. */
  110. private function normalizeValue(
  111. BackedEnum|Stringable|string|float|int|bool|array|null $value,
  112. string $name,
  113. bool $isNestedListAllowed
  114. ): array|string {
  115. if ($value instanceof BackedEnum) {
  116. $value = $value->value;
  117. }
  118. return match (true) {
  119. is_bool($value) => true === $value ? '1' : '0',
  120. (null === $value || is_scalar($value) || $value instanceof Stringable) => (string) $value,
  121. !$isNestedListAllowed => throw TemplateCanNotBeExpanded::dueToNestedListOfValue($name),
  122. default => array_map(fn ($var): array|string => self::normalizeValue($var, $name, false), $value),
  123. };
  124. }
  125. /**
  126. * Replaces elements from passed variables into the current instance.
  127. */
  128. public function replace(VariableBag $variables): self
  129. {
  130. return new self($this->variables + $variables->variables);
  131. }
  132. /**
  133. * Filters elements using the closure.
  134. */
  135. public function filter(Closure $fn): self
  136. {
  137. return new self(array_filter($this->variables, $fn, ARRAY_FILTER_USE_BOTH));
  138. }
  139. }