1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Symfony\Component\Config\Definition;
13:
14: use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
15: use Symfony\Component\Config\Definition\Exception\DuplicateKeyException;
16: use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
17: use Symfony\Component\Config\Definition\Exception\Exception;
18:
19: 20: 21: 22: 23:
24: class PrototypedArrayNode extends ArrayNode
25: {
26: protected $prototype;
27: protected $keyAttribute;
28: protected $removeKeyAttribute;
29: protected $minNumberOfElements;
30: protected $defaultValue;
31: protected $defaultChildren;
32:
33: 34: 35: 36: 37: 38:
39: public function __construct($name, NodeInterface $parent = null)
40: {
41: parent::__construct($name, $parent);
42:
43: $this->minNumberOfElements = 0;
44: $this->defaultValue = array();
45: }
46:
47: 48: 49: 50: 51: 52:
53: public function setMinNumberOfElements($number)
54: {
55: $this->minNumberOfElements = $number;
56: }
57:
58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81:
82: public function setKeyAttribute($attribute, $remove = true)
83: {
84: $this->keyAttribute = $attribute;
85: $this->removeKeyAttribute = $remove;
86: }
87:
88: 89: 90: 91: 92:
93: public function getKeyAttribute()
94: {
95: return $this->keyAttribute;
96: }
97:
98: 99: 100: 101: 102: 103: 104:
105: public function setDefaultValue($value)
106: {
107: if (!is_array($value)) {
108: throw new \InvalidArgumentException($this->getPath().': the default value of an array node has to be an array.');
109: }
110:
111: $this->defaultValue = $value;
112: }
113:
114: 115: 116: 117: 118:
119: public function hasDefaultValue()
120: {
121: return true;
122: }
123:
124: 125: 126: 127: 128:
129: public function setAddChildrenIfNoneSet($children = array('defaults'))
130: {
131: if (null === $children) {
132: $this->defaultChildren = array('defaults');
133: } else {
134: $this->defaultChildren = is_integer($children) && $children > 0 ? range(1, $children) : (array) $children;
135: }
136: }
137:
138: 139: 140: 141: 142: 143: 144: 145:
146: public function getDefaultValue()
147: {
148: if (null !== $this->defaultChildren) {
149: $default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : array();
150: $defaults = array();
151: foreach (array_values($this->defaultChildren) as $i => $name) {
152: $defaults[null === $this->keyAttribute ? $i : $name] = $default;
153: }
154:
155: return $defaults;
156: }
157:
158: return $this->defaultValue;
159: }
160:
161: 162: 163: 164: 165:
166: public function setPrototype(PrototypeNodeInterface $node)
167: {
168: $this->prototype = $node;
169: }
170:
171: 172: 173: 174: 175:
176: public function getPrototype()
177: {
178: return $this->prototype;
179: }
180:
181: 182: 183: 184: 185: 186: 187:
188: public function addChild(NodeInterface $node)
189: {
190: throw new Exception('A prototyped array node can not have concrete children.');
191: }
192:
193: 194: 195: 196: 197: 198: 199: 200: 201: 202:
203: protected function finalizeValue($value)
204: {
205: if (false === $value) {
206: $msg = sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value));
207: throw new UnsetKeyException($msg);
208: }
209:
210: foreach ($value as $k => $v) {
211: $this->prototype->setName($k);
212: try {
213: $value[$k] = $this->prototype->finalize($v);
214: } catch (UnsetKeyException $unset) {
215: unset($value[$k]);
216: }
217: }
218:
219: if (count($value) < $this->minNumberOfElements) {
220: $msg = sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements);
221: $ex = new InvalidConfigurationException($msg);
222: $ex->setPath($this->getPath());
223:
224: throw $ex;
225: }
226:
227: return $value;
228: }
229:
230: 231: 232: 233: 234: 235: 236: 237: 238: 239:
240: protected function normalizeValue($value)
241: {
242: if (false === $value) {
243: return $value;
244: }
245:
246: $value = $this->remapXml($value);
247:
248: $isAssoc = array_keys($value) !== range(0, count($value) -1);
249: $normalized = array();
250: foreach ($value as $k => $v) {
251: if (null !== $this->keyAttribute && is_array($v)) {
252: if (!isset($v[$this->keyAttribute]) && is_int($k) && !$isAssoc) {
253: $msg = sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath());
254: $ex = new InvalidConfigurationException($msg);
255: $ex->setPath($this->getPath());
256:
257: throw $ex;
258: } elseif (isset($v[$this->keyAttribute])) {
259: $k = $v[$this->keyAttribute];
260:
261:
262: if ($this->removeKeyAttribute) {
263: unset($v[$this->keyAttribute]);
264: }
265:
266:
267: if (1 == count($v) && isset($v['value'])) {
268: $v = $v['value'];
269: }
270: }
271:
272: if (array_key_exists($k, $normalized)) {
273: $msg = sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath());
274: $ex = new DuplicateKeyException($msg);
275: $ex->setPath($this->getPath());
276:
277: throw $ex;
278: }
279: }
280:
281: $this->prototype->setName($k);
282: if (null !== $this->keyAttribute || $isAssoc) {
283: $normalized[$k] = $this->prototype->normalize($v);
284: } else {
285: $normalized[] = $this->prototype->normalize($v);
286: }
287: }
288:
289: return $normalized;
290: }
291:
292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302:
303: protected function mergeValues($leftSide, $rightSide)
304: {
305: if (false === $rightSide) {
306:
307:
308: return false;
309: }
310:
311: if (false === $leftSide || !$this->performDeepMerging) {
312: return $rightSide;
313: }
314:
315: foreach ($rightSide as $k => $v) {
316:
317: if (null === $this->keyAttribute) {
318: $leftSide[] = $v;
319: continue;
320: }
321:
322:
323: if (!array_key_exists($k, $leftSide)) {
324: if (!$this->allowNewKeys) {
325: $ex = new InvalidConfigurationException(sprintf(
326: 'You are not allowed to define new elements for path "%s". ' .
327: 'Please define all elements for this path in one config file.',
328: $this->getPath()
329: ));
330: $ex->setPath($this->getPath());
331:
332: throw $ex;
333: }
334:
335: $leftSide[$k] = $v;
336: continue;
337: }
338:
339: $this->prototype->setName($k);
340: $leftSide[$k] = $this->prototype->merge($leftSide[$k], $v);
341: }
342:
343: return $leftSide;
344: }
345: }
346: