PageRenderTime 50ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/symfony/config/Symfony/Component/Config/Definition/PrototypedArrayNode.php

https://bitbucket.org/prauscher/att
PHP | 342 lines | 170 code | 42 blank | 130 comment | 27 complexity | ac47d2b5dcbbb42502223aae2351db4e MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.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. namespace Symfony\Component\Config\Definition;
  11. use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
  12. use Symfony\Component\Config\Definition\Exception\DuplicateKeyException;
  13. use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
  14. use Symfony\Component\Config\Definition\Exception\Exception;
  15. /**
  16. * Represents a prototyped Array node in the config tree.
  17. *
  18. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  19. */
  20. class PrototypedArrayNode extends ArrayNode
  21. {
  22. protected $prototype;
  23. protected $keyAttribute;
  24. protected $removeKeyAttribute;
  25. protected $minNumberOfElements;
  26. protected $defaultValue;
  27. protected $defaultChildren;
  28. /**
  29. * Constructor.
  30. *
  31. * @param string $name The Node's name
  32. * @param NodeInterface $parent The node parent
  33. */
  34. public function __construct($name, NodeInterface $parent = null)
  35. {
  36. parent::__construct($name, $parent);
  37. $this->minNumberOfElements = 0;
  38. $this->defaultValue = array();
  39. }
  40. /**
  41. * Sets the minimum number of elements that a prototype based node must
  42. * contain. By default this is zero, meaning no elements.
  43. *
  44. * @param integer $number
  45. */
  46. public function setMinNumberOfElements($number)
  47. {
  48. $this->minNumberOfElements = $number;
  49. }
  50. /**
  51. * Sets the attribute which value is to be used as key.
  52. *
  53. * This is useful when you have an indexed array that should be an
  54. * associative array. You can select an item from within the array
  55. * to be the key of the particular item. For example, if "id" is the
  56. * "key", then:
  57. *
  58. * array(
  59. * array('id' => 'my_name', 'foo' => 'bar'),
  60. * );
  61. *
  62. * becomes
  63. *
  64. * array(
  65. * 'my_name' => array('foo' => 'bar'),
  66. * );
  67. *
  68. * If you'd like "'id' => 'my_name'" to still be present in the resulting
  69. * array, then you can set the second argument of this method to false.
  70. *
  71. * @param string $attribute The name of the attribute which value is to be used as a key
  72. * @param Boolean $remove Whether or not to remove the key
  73. */
  74. public function setKeyAttribute($attribute, $remove = true)
  75. {
  76. $this->keyAttribute = $attribute;
  77. $this->removeKeyAttribute = $remove;
  78. }
  79. /**
  80. * Retrieves the name of the attribute which value should be used as key.
  81. *
  82. * @return string The name of the attribute
  83. */
  84. public function getKeyAttribute()
  85. {
  86. return $this->keyAttribute;
  87. }
  88. /**
  89. * Sets the default value of this node.
  90. *
  91. * @param string $value
  92. *
  93. * @throws \InvalidArgumentException if the default value is not an array
  94. */
  95. public function setDefaultValue($value)
  96. {
  97. if (!is_array($value)) {
  98. throw new \InvalidArgumentException($this->getPath().': the default value of an array node has to be an array.');
  99. }
  100. $this->defaultValue = $value;
  101. }
  102. /**
  103. * Checks if the node has a default value.
  104. *
  105. * @return Boolean
  106. */
  107. public function hasDefaultValue()
  108. {
  109. return true;
  110. }
  111. /**
  112. * Adds default children when none are set.
  113. *
  114. * @param integer|string|array|null $children The number of children|The child name|The children names to be added
  115. */
  116. public function setAddChildrenIfNoneSet($children = array('defaults'))
  117. {
  118. if (null === $children) {
  119. $this->defaultChildren = array('defaults');
  120. } else {
  121. $this->defaultChildren = is_integer($children) && $children > 0 ? range(1, $children) : (array) $children;
  122. }
  123. }
  124. /**
  125. * Retrieves the default value.
  126. *
  127. * The default value could be either explicited or derived from the prototype
  128. * default value.
  129. *
  130. * @return array The default value
  131. */
  132. public function getDefaultValue()
  133. {
  134. if (null !== $this->defaultChildren) {
  135. $default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : array();
  136. $defaults = array();
  137. foreach (array_values($this->defaultChildren) as $i => $name) {
  138. $defaults[null === $this->keyAttribute ? $i : $name] = $default;
  139. }
  140. return $defaults;
  141. }
  142. return $this->defaultValue;
  143. }
  144. /**
  145. * Sets the node prototype.
  146. *
  147. * @param PrototypeNodeInterface $node
  148. */
  149. public function setPrototype(PrototypeNodeInterface $node)
  150. {
  151. $this->prototype = $node;
  152. }
  153. /**
  154. * Retrieves the prototype
  155. *
  156. * @return PrototypeNodeInterface The prototype
  157. */
  158. public function getPrototype()
  159. {
  160. return $this->prototype;
  161. }
  162. /**
  163. * Disable adding concrete children for prototyped nodes.
  164. *
  165. * @param NodeInterface $node The child node to add
  166. *
  167. * @throws \RuntimeException Prototyped array nodes can't have concrete children.
  168. */
  169. public function addChild(NodeInterface $node)
  170. {
  171. throw new Exception('A prototyped array node can not have concrete children.');
  172. }
  173. /**
  174. * Finalizes the value of this node.
  175. *
  176. * @param mixed $value
  177. *
  178. * @return mixed The finalized value
  179. *
  180. * @throws UnsetKeyException
  181. * @throws InvalidConfigurationException if the node doesn't have enough children
  182. */
  183. protected function finalizeValue($value)
  184. {
  185. if (false === $value) {
  186. $msg = sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value));
  187. throw new UnsetKeyException($msg);
  188. }
  189. foreach ($value as $k => $v) {
  190. $this->prototype->setName($k);
  191. try {
  192. $value[$k] = $this->prototype->finalize($v);
  193. } catch (UnsetKeyException $unset) {
  194. unset($value[$k]);
  195. }
  196. }
  197. if (count($value) < $this->minNumberOfElements) {
  198. $msg = sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements);
  199. $ex = new InvalidConfigurationException($msg);
  200. $ex->setPath($this->getPath());
  201. throw $ex;
  202. }
  203. return $value;
  204. }
  205. /**
  206. * Normalizes the value.
  207. *
  208. * @param mixed $value The value to normalize
  209. *
  210. * @return mixed The normalized value
  211. */
  212. protected function normalizeValue($value)
  213. {
  214. if (false === $value) {
  215. return $value;
  216. }
  217. $value = $this->remapXml($value);
  218. $isAssoc = array_keys($value) !== range(0, count($value) -1);
  219. $normalized = array();
  220. foreach ($value as $k => $v) {
  221. if (null !== $this->keyAttribute && is_array($v)) {
  222. if (!isset($v[$this->keyAttribute]) && is_int($k) && !$isAssoc) {
  223. $msg = sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath());
  224. $ex = new InvalidConfigurationException($msg);
  225. $ex->setPath($this->getPath());
  226. throw $ex;
  227. } elseif (isset($v[$this->keyAttribute])) {
  228. $k = $v[$this->keyAttribute];
  229. // remove the key attribute when required
  230. if ($this->removeKeyAttribute) {
  231. unset($v[$this->keyAttribute]);
  232. }
  233. // if only "value" is left
  234. if (1 == count($v) && isset($v['value'])) {
  235. $v = $v['value'];
  236. }
  237. }
  238. if (array_key_exists($k, $normalized)) {
  239. $msg = sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath());
  240. $ex = new DuplicateKeyException($msg);
  241. $ex->setPath($this->getPath());
  242. throw $ex;
  243. }
  244. }
  245. $this->prototype->setName($k);
  246. if (null !== $this->keyAttribute || $isAssoc) {
  247. $normalized[$k] = $this->prototype->normalize($v);
  248. } else {
  249. $normalized[] = $this->prototype->normalize($v);
  250. }
  251. }
  252. return $normalized;
  253. }
  254. /**
  255. * Merges values together.
  256. *
  257. * @param mixed $leftSide The left side to merge.
  258. * @param mixed $rightSide The right side to merge.
  259. *
  260. * @return mixed The merged values
  261. *
  262. * @throws InvalidConfigurationException
  263. * @throws \RuntimeException
  264. */
  265. protected function mergeValues($leftSide, $rightSide)
  266. {
  267. if (false === $rightSide) {
  268. // if this is still false after the last config has been merged the
  269. // finalization pass will take care of removing this key entirely
  270. return false;
  271. }
  272. if (false === $leftSide || !$this->performDeepMerging) {
  273. return $rightSide;
  274. }
  275. foreach ($rightSide as $k => $v) {
  276. // prototype, and key is irrelevant, so simply append the element
  277. if (null === $this->keyAttribute) {
  278. $leftSide[] = $v;
  279. continue;
  280. }
  281. // no conflict
  282. if (!array_key_exists($k, $leftSide)) {
  283. if (!$this->allowNewKeys) {
  284. $ex = new InvalidConfigurationException(sprintf(
  285. 'You are not allowed to define new elements for path "%s". ' .
  286. 'Please define all elements for this path in one config file.',
  287. $this->getPath()
  288. ));
  289. $ex->setPath($this->getPath());
  290. throw $ex;
  291. }
  292. $leftSide[$k] = $v;
  293. continue;
  294. }
  295. $this->prototype->setName($k);
  296. $leftSide[$k] = $this->prototype->merge($leftSide[$k], $v);
  297. }
  298. return $leftSide;
  299. }
  300. }