1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Symfony\Component\Config\Definition\Builder;
13:
14: use Symfony\Component\Config\Definition\ArrayNode;
15: use Symfony\Component\Config\Definition\PrototypedArrayNode;
16: use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
17:
18: 19: 20: 21: 22:
23: class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface
24: {
25: protected $performDeepMerging;
26: protected ;
27: protected $children;
28: protected $prototype;
29: protected $atLeastOne;
30: protected $allowNewKeys;
31: protected $key;
32: protected $removeKeyItem;
33: protected $addDefaults;
34: protected $addDefaultChildren;
35: protected $nodeBuilder;
36: protected $normalizeKeys;
37:
38: 39: 40:
41: public function __construct($name, NodeParentInterface $parent = null)
42: {
43: parent::__construct($name, $parent);
44:
45: $this->children = array();
46: $this->addDefaults = false;
47: $this->addDefaultChildren = false;
48: $this->allowNewKeys = true;
49: $this->atLeastOne = false;
50: $this->allowEmptyValue = true;
51: $this->performDeepMerging = true;
52: $this->nullEquivalent = array();
53: $this->trueEquivalent = array();
54: $this->normalizeKeys = true;
55: }
56:
57: 58: 59: 60: 61:
62: public function setBuilder(NodeBuilder $builder)
63: {
64: $this->nodeBuilder = $builder;
65: }
66:
67: 68: 69: 70: 71:
72: public function children()
73: {
74: return $this->getNodeBuilder();
75: }
76:
77: 78: 79: 80: 81: 82: 83:
84: public function prototype($type)
85: {
86: return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this);
87: }
88:
89: 90: 91: 92: 93: 94: 95: 96: 97:
98: public function addDefaultsIfNotSet()
99: {
100: $this->addDefaults = true;
101:
102: return $this;
103: }
104:
105: 106: 107: 108: 109: 110: 111: 112: 113:
114: public function addDefaultChildrenIfNoneSet($children = null)
115: {
116: $this->addDefaultChildren = $children;
117:
118: return $this;
119: }
120:
121: 122: 123: 124: 125: 126: 127:
128: public function requiresAtLeastOneElement()
129: {
130: $this->atLeastOne = true;
131:
132: return $this;
133: }
134:
135: 136: 137: 138: 139: 140: 141:
142: public function disallowNewKeysInSubsequentConfigs()
143: {
144: $this->allowNewKeys = false;
145:
146: return $this;
147: }
148:
149: 150: 151: 152: 153: 154: 155: 156:
157: public function fixXmlConfig($singular, $plural = null)
158: {
159: $this->normalization()->remap($singular, $plural);
160:
161: return $this;
162: }
163:
164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191:
192: public function useAttributeAsKey($name, $removeKeyItem = true)
193: {
194: $this->key = $name;
195: $this->removeKeyItem = $removeKeyItem;
196:
197: return $this;
198: }
199:
200: 201: 202: 203: 204: 205: 206:
207: public function canBeUnset($allow = true)
208: {
209: $this->merge()->allowUnset($allow);
210:
211: return $this;
212: }
213:
214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228:
229: public function canBeEnabled()
230: {
231: $this
232: ->addDefaultsIfNotSet()
233: ->treatFalseLike(array('enabled' => false))
234: ->treatTrueLike(array('enabled' => true))
235: ->treatNullLike(array('enabled' => true))
236: ->beforeNormalization()
237: ->ifArray()
238: ->then(function($v) {
239: $v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true;
240:
241: return $v;
242: })
243: ->end()
244: ->children()
245: ->booleanNode('enabled')
246: ->defaultFalse()
247: ;
248:
249: return $this;
250: }
251:
252: 253: 254: 255: 256: 257: 258:
259: public function canBeDisabled()
260: {
261: $this
262: ->addDefaultsIfNotSet()
263: ->treatFalseLike(array('enabled' => false))
264: ->treatTrueLike(array('enabled' => true))
265: ->treatNullLike(array('enabled' => true))
266: ->children()
267: ->booleanNode('enabled')
268: ->defaultTrue()
269: ;
270:
271: return $this;
272: }
273:
274: 275: 276: 277: 278:
279: public function performNoDeepMerging()
280: {
281: $this->performDeepMerging = false;
282:
283: return $this;
284: }
285:
286: 287: 288: 289: 290: 291: 292: 293: 294: 295:
296: public function ()
297: {
298: $this->ignoreExtraKeys = true;
299:
300: return $this;
301: }
302:
303: 304: 305: 306: 307: 308: 309:
310: public function normalizeKeys($bool)
311: {
312: $this->normalizeKeys = (Boolean) $bool;
313:
314: return $this;
315: }
316:
317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331:
332: public function append(NodeDefinition $node)
333: {
334: $this->children[$node->name] = $node->setParent($this);
335:
336: return $this;
337: }
338:
339: 340: 341: 342: 343:
344: protected function getNodeBuilder()
345: {
346: if (null === $this->nodeBuilder) {
347: $this->nodeBuilder = new NodeBuilder();
348: }
349:
350: return $this->nodeBuilder->setParent($this);
351: }
352:
353: 354: 355:
356: protected function createNode()
357: {
358: if (null === $this->prototype) {
359: $node = new ArrayNode($this->name, $this->parent);
360:
361: $this->validateConcreteNode($node);
362:
363: $node->setAddIfNotSet($this->addDefaults);
364:
365: foreach ($this->children as $child) {
366: $child->parent = $node;
367: $node->addChild($child->getNode());
368: }
369: } else {
370: $node = new PrototypedArrayNode($this->name, $this->parent);
371:
372: $this->validatePrototypeNode($node);
373:
374: if (null !== $this->key) {
375: $node->setKeyAttribute($this->key, $this->removeKeyItem);
376: }
377:
378: if (true === $this->atLeastOne) {
379: $node->setMinNumberOfElements(1);
380: }
381:
382: if ($this->default) {
383: $node->setDefaultValue($this->defaultValue);
384: }
385:
386: if (false !== $this->addDefaultChildren) {
387: $node->setAddChildrenIfNoneSet($this->addDefaultChildren);
388: if ($this->prototype instanceof static && null === $this->prototype->prototype) {
389: $this->prototype->addDefaultsIfNotSet();
390: }
391: }
392:
393: $this->prototype->parent = $node;
394: $node->setPrototype($this->prototype->getNode());
395: }
396:
397: $node->setAllowNewKeys($this->allowNewKeys);
398: $node->addEquivalentValue(null, $this->nullEquivalent);
399: $node->addEquivalentValue(true, $this->trueEquivalent);
400: $node->addEquivalentValue(false, $this->falseEquivalent);
401: $node->setPerformDeepMerging($this->performDeepMerging);
402: $node->setRequired($this->required);
403: $node->setIgnoreExtraKeys($this->ignoreExtraKeys);
404: $node->setNormalizeKeys($this->normalizeKeys);
405:
406: if (null !== $this->normalization) {
407: $node->setNormalizationClosures($this->normalization->before);
408: $node->setXmlRemappings($this->normalization->remappings);
409: }
410:
411: if (null !== $this->merge) {
412: $node->setAllowOverwrite($this->merge->allowOverwrite);
413: $node->setAllowFalse($this->merge->allowFalse);
414: }
415:
416: if (null !== $this->validation) {
417: $node->setFinalValidationClosures($this->validation->rules);
418: }
419:
420: return $node;
421: }
422:
423: 424: 425: 426: 427: 428: 429:
430: protected function validateConcreteNode(ArrayNode $node)
431: {
432: $path = $node->getPath();
433:
434: if (null !== $this->key) {
435: throw new InvalidDefinitionException(
436: sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s"', $path)
437: );
438: }
439:
440: if (true === $this->atLeastOne) {
441: throw new InvalidDefinitionException(
442: sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s"', $path)
443: );
444: }
445:
446: if ($this->default) {
447: throw new InvalidDefinitionException(
448: sprintf('->defaultValue() is not applicable to concrete nodes at path "%s"', $path)
449: );
450: }
451:
452: if (false !== $this->addDefaultChildren) {
453: throw new InvalidDefinitionException(
454: sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s"', $path)
455: );
456: }
457: }
458:
459: 460: 461: 462: 463: 464: 465:
466: protected function validatePrototypeNode(PrototypedArrayNode $node)
467: {
468: $path = $node->getPath();
469:
470: if ($this->addDefaults) {
471: throw new InvalidDefinitionException(
472: sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s"', $path)
473: );
474: }
475:
476: if (false !== $this->addDefaultChildren) {
477: if ($this->default) {
478: throw new InvalidDefinitionException(
479: sprintf('A default value and default children might not be used together at path "%s"', $path)
480: );
481: }
482:
483: if (null !== $this->key && (null === $this->addDefaultChildren || is_integer($this->addDefaultChildren) && $this->addDefaultChildren > 0)) {
484: throw new InvalidDefinitionException(
485: sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s"', $path)
486: );
487: }
488:
489: if (null === $this->key && (is_string($this->addDefaultChildren) || is_array($this->addDefaultChildren))) {
490: throw new InvalidDefinitionException(
491: sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s"', $path)
492: );
493: }
494: }
495: }
496: }
497: