1<?php
2
3/**
4 * League.Uri (https://uri.thephpleague.com)
5 *
6 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12declare(strict_types=1);
13
14namespace League\Uri\UriTemplate;
15
16use League\Uri\Exceptions\TemplateCanNotBeExpanded;
17use function gettype;
18use function is_array;
19use function is_bool;
20use function is_object;
21use function is_scalar;
22use function method_exists;
23use function sprintf;
24
25final class VariableBag
26{
27    /**
28     * @var array<string,string|array<string>>
29     */
30    private $variables = [];
31
32    /**
33     * @param iterable<string,mixed> $variables
34     */
35    public function __construct(iterable $variables = [])
36    {
37        foreach ($variables as $name => $value) {
38            $this->assign($name, $value);
39        }
40    }
41
42    public static function __set_state(array $properties): self
43    {
44        return new self($properties['variables']);
45    }
46
47    /**
48     * @return array<string,string|array<string>>
49     */
50    public function all(): array
51    {
52        return $this->variables;
53    }
54
55    /**
56     * Fetches the variable value if none found returns null.
57     *
58     * @return null|string|array<string>
59     */
60    public function fetch(string $name)
61    {
62        return $this->variables[$name] ?? null;
63    }
64
65    /**
66     * @param string|array<string> $value
67     */
68    public function assign(string $name, $value): void
69    {
70        $this->variables[$name] = $this->normalizeValue($value, $name, true);
71    }
72
73    /**
74     * @param mixed $value the value to be expanded
75     *
76     * @throws TemplateCanNotBeExpanded if the value contains nested list
77     *
78     * @return string|array<string>
79     */
80    private function normalizeValue($value, string $name, bool $isNestedListAllowed)
81    {
82        if (is_bool($value)) {
83            return true === $value ? '1' : '0';
84        }
85
86        if (null === $value || is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) {
87            return (string) $value;
88        }
89
90        if (!is_array($value)) {
91            throw new \TypeError(sprintf('The variable '.$name.' must be NULL, a scalar or a stringable object `%s` given', gettype($value)));
92        }
93
94        if (!$isNestedListAllowed) {
95            throw TemplateCanNotBeExpanded::dueToNestedListOfValue($name);
96        }
97
98        foreach ($value as &$var) {
99            $var = self::normalizeValue($var, $name, false);
100        }
101        unset($var);
102
103        return $value;
104    }
105
106    /**
107     * Replaces elements from passed variables into the current instance.
108     */
109    public function replace(VariableBag $variables): self
110    {
111        $instance = clone $this;
112        $instance->variables += $variables->variables;
113
114        return $instance;
115    }
116}
117