Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
PrototypedArrayNode.php
001 <?php
002
003 /*
004 * This file is part of the Symfony package.
005 *
006 * (c) Fabien Potencier <fabien@symfony.com>
007 *
008 * For the full copyright and license information, please view the LICENSE
009 * file that was distributed with this source code.
010 */
011
012 namespace Symfony\Component\Config\Definition;
013
014 use Symfony\Component\Config\Definition\Exception\DuplicateKeyException;
015 use Symfony\Component\Config\Definition\Exception\Exception;
016 use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
017 use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
018
019 /**
020 * Represents a prototyped Array node in the config tree.
021 *
022 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
023 */
024 class PrototypedArrayNode extends ArrayNode
025 {
026 protected $prototype;
027 protected $keyAttribute;
028 protected $removeKeyAttribute = false;
029 protected $minNumberOfElements = 0;
030 protected $defaultValue = [];
031 protected $defaultChildren;
032 /**
033 * @var NodeInterface[] An array of the prototypes of the simplified value children
034 */
035 private $valuePrototypes = [];
036
037 /**
038 * Sets the minimum number of elements that a prototype based node must
039 * contain. By default this is zero, meaning no elements.
040 *
041 * @param int $number
042 */
043 public function setMinNumberOfElements($number)
044 {
045 $this->minNumberOfElements = $number;
046 }
047
048 /**
049 * Sets the attribute which value is to be used as key.
050 *
051 * This is useful when you have an indexed array that should be an
052 * associative array. You can select an item from within the array
053 * to be the key of the particular item. For example, if "id" is the
054 * "key", then:
055 *
056 * [
057 * ['id' => 'my_name', 'foo' => 'bar'],
058 * ];
059 *
060 * becomes
061 *
062 * [
063 * 'my_name' => ['foo' => 'bar'],
064 * ];
065 *
066 * If you'd like "'id' => 'my_name'" to still be present in the resulting
067 * array, then you can set the second argument of this method to false.
068 *
069 * @param string $attribute The name of the attribute which value is to be used as a key
070 * @param bool $remove Whether or not to remove the key
071 */
072 public function setKeyAttribute($attribute, $remove = true)
073 {
074 $this->keyAttribute = $attribute;
075 $this->removeKeyAttribute = $remove;
076 }
077
078 /**
079 * Retrieves the name of the attribute which value should be used as key.
080 *
081 * @return string|null The name of the attribute
082 */
083 public function getKeyAttribute()
084 {
085 return $this->keyAttribute;
086 }
087
088 /**
089 * Sets the default value of this node.
090 *
091 * @param string $value
092 *
093 * @throws \InvalidArgumentException if the default value is not an array
094 */
095 public function setDefaultValue($value)
096 {
097 if (!\is_array($value)) {
098 throw new \InvalidArgumentException($this->getPath().': the default value of an array node has to be an array.');
099 }
100
101 $this->defaultValue = $value;
102 }
103
104 /**
105 * {@inheritdoc}
106 */
107 public function hasDefaultValue()
108 {
109 return true;
110 }
111
112 /**
113 * Adds default children when none are set.
114 *
115 * @param int|string|array|null $children The number of children|The child name|The children names to be added
116 */
117 public function setAddChildrenIfNoneSet($children = ['defaults'])
118 {
119 if (null === $children) {
120 $this->defaultChildren = ['defaults'];
121 } else {
122 $this->defaultChildren = \is_int($children) && $children > 0 ? range(1, $children) : (array) $children;
123 }
124 }
125
126 /**
127 * {@inheritdoc}
128 *
129 * The default value could be either explicited or derived from the prototype
130 * default value.
131 */
132 public function getDefaultValue()
133 {
134 if (null !== $this->defaultChildren) {
135 $default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : [];
136 $defaults = [];
137 foreach (array_values($this->defaultChildren) as $i => $name) {
138 $defaults[null === $this->keyAttribute ? $i : $name] = $default;
139 }
140
141 return $defaults;
142 }
143
144 return $this->defaultValue;
145 }
146
147 /**
148 * Sets the node prototype.
149 */
150 public function setPrototype(PrototypeNodeInterface $node)
151 {
152 $this->prototype = $node;
153 }
154
155 /**
156 * Retrieves the prototype.
157 *
158 * @return PrototypeNodeInterface The prototype
159 */
160 public function getPrototype()
161 {
162 return $this->prototype;
163 }
164
165 /**
166 * Disable adding concrete children for prototyped nodes.
167 *
168 * @throws Exception
169 */
170 public function addChild(NodeInterface $node)
171 {
172 throw new Exception('A prototyped array node can not have concrete children.');
173 }
174
175 /**
176 * Finalizes the value of this node.
177 *
178 * @param mixed $value
179 *
180 * @return mixed The finalized value
181 *
182 * @throws UnsetKeyException
183 * @throws InvalidConfigurationException if the node doesn't have enough children
184 */
185 protected function finalizeValue($value)
186 {
187 if (false === $value) {
188 throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: "%s".', $this->getPath(), json_encode($value)));
189 }
190
191 foreach ($value as $k => $v) {
192 $prototype = $this->getPrototypeForChild($k);
193 try {
194 $value[$k] = $prototype->finalize($v);
195 } catch (UnsetKeyException $e) {
196 unset($value[$k]);
197 }
198 }
199
200 if (\count($value) < $this->minNumberOfElements) {
201 $ex = new InvalidConfigurationException(sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements));
202 $ex->setPath($this->getPath());
203
204 throw $ex;
205 }
206
207 return $value;
208 }
209
210 /**
211 * Normalizes the value.
212 *
213 * @param mixed $value The value to normalize
214 *
215 * @return mixed The normalized value
216 *
217 * @throws InvalidConfigurationException
218 * @throws DuplicateKeyException
219 */
220 protected function normalizeValue($value)
221 {
222 if (false === $value) {
223 return $value;
224 }
225
226 $value = $this->remapXml($value);
227
228 $isAssoc = array_keys($value) !== range(0, \count($value) - 1);
229 $normalized = [];
230 foreach ($value as $k => $v) {
231 if (null !== $this->keyAttribute && \is_array($v)) {
232 if (!isset($v[$this->keyAttribute]) && \is_int($k) && !$isAssoc) {
233 $ex = new InvalidConfigurationException(sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath()));
234 $ex->setPath($this->getPath());
235
236 throw $ex;
237 } elseif (isset($v[$this->keyAttribute])) {
238 $k = $v[$this->keyAttribute];
239
240 // remove the key attribute when required
241 if ($this->removeKeyAttribute) {
242 unset($v[$this->keyAttribute]);
243 }
244
245 // if only "value" is left
246 if (array_keys($v) === ['value']) {
247 $v = $v['value'];
248 if ($this->prototype instanceof ArrayNode && ($children = $this->prototype->getChildren()) && \array_key_exists('value', $children)) {
249 $valuePrototype = current($this->valuePrototypes) ?: clone $children['value'];
250 $valuePrototype->parent = $this;
251 $originalClosures = $this->prototype->normalizationClosures;
252 if (\is_array($originalClosures)) {
253 $valuePrototypeClosures = $valuePrototype->normalizationClosures;
254 $valuePrototype->normalizationClosures = \is_array($valuePrototypeClosures) ? array_merge($originalClosures, $valuePrototypeClosures) : $originalClosures;
255 }
256 $this->valuePrototypes[$k] = $valuePrototype;
257 }
258 }
259 }
260
261 if (\array_key_exists($k, $normalized)) {
262 $ex = new DuplicateKeyException(sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath()));
263 $ex->setPath($this->getPath());
264
265 throw $ex;
266 }
267 }
268
269 $prototype = $this->getPrototypeForChild($k);
270 if (null !== $this->keyAttribute || $isAssoc) {
271 $normalized[$k] = $prototype->normalize($v);
272 } else {
273 $normalized[] = $prototype->normalize($v);
274 }
275 }
276
277 return $normalized;
278 }
279
280 /**
281 * Merges values together.
282 *
283 * @param mixed $leftSide The left side to merge
284 * @param mixed $rightSide The right side to merge
285 *
286 * @return mixed The merged values
287 *
288 * @throws InvalidConfigurationException
289 * @throws \RuntimeException
290 */
291 protected function mergeValues($leftSide, $rightSide)
292 {
293 if (false === $rightSide) {
294 // if this is still false after the last config has been merged the
295 // finalization pass will take care of removing this key entirely
296 return false;
297 }
298
299 if (false === $leftSide || !$this->performDeepMerging) {
300 return $rightSide;
301 }
302
303 foreach ($rightSide as $k => $v) {
304 // prototype, and key is irrelevant, append the element
305 if (null === $this->keyAttribute) {
306 $leftSide[] = $v;
307 continue;
308 }
309
310 // no conflict
311 if (!\array_key_exists($k, $leftSide)) {
312 if (!$this->allowNewKeys) {
313 $ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file.', $this->getPath()));
314 $ex->setPath($this->getPath());
315
316 throw $ex;
317 }
318
319 $leftSide[$k] = $v;
320 continue;
321 }
322
323 $prototype = $this->getPrototypeForChild($k);
324 $leftSide[$k] = $prototype->merge($leftSide[$k], $v);
325 }
326
327 return $leftSide;
328 }
329
330 /**
331 * Returns a prototype for the child node that is associated to $key in the value array.
332 * For general child nodes, this will be $this->prototype.
333 * But if $this->removeKeyAttribute is true and there are only two keys in the child node:
334 * one is same as this->keyAttribute and the other is 'value', then the prototype will be different.
335 *
336 * For example, assume $this->keyAttribute is 'name' and the value array is as follows:
337 *
338 * [
339 * [
340 * 'name' => 'name001',
341 * 'value' => 'value001'
342 * ]
343 * ]
344 *
345 * Now, the key is 0 and the child node is:
346 *
347 * [
348 * 'name' => 'name001',
349 * 'value' => 'value001'
350 * ]
351 *
352 * When normalizing the value array, the 'name' element will removed from the child node
353 * and its value becomes the new key of the child node:
354 *
355 * [
356 * 'name001' => ['value' => 'value001']
357 * ]
358 *
359 * Now only 'value' element is left in the child node which can be further simplified into a string:
360 *
361 * ['name001' => 'value001']
362 *
363 * Now, the key becomes 'name001' and the child node becomes 'value001' and
364 * the prototype of child node 'name001' should be a ScalarNode instead of an ArrayNode instance.
365 *
366 * @param string $key The key of the child node
367 *
368 * @return mixed The prototype instance
369 */
370 private function getPrototypeForChild($key)
371 {
372 $prototype = isset($this->valuePrototypes[$key]) ? $this->valuePrototypes[$key] : $this->prototype;
373 $prototype->setName($key);
374
375 return $prototype;
376 }
377 }
378