ExtendsMapper.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. /**
  7. * System Configuration Extends Mapper
  8. */
  9. namespace Magento\Config\Model\Config\Structure\Mapper;
  10. /**
  11. * @api
  12. * @since 100.0.2
  13. */
  14. class ExtendsMapper extends \Magento\Config\Model\Config\Structure\AbstractMapper
  15. {
  16. /**
  17. * System configuration array
  18. *
  19. * @var array
  20. */
  21. protected $_systemConfiguration;
  22. /**
  23. * List of already extended notes (used to break circular extends)
  24. *
  25. * @var string[]
  26. */
  27. protected $_extendedNodesList = [];
  28. /**
  29. * Class that can convert relative paths from "extends" node to absolute
  30. *
  31. * @var \Magento\Config\Model\Config\Structure\Mapper\Helper\RelativePathConverter
  32. */
  33. protected $_pathConverter;
  34. /**
  35. * @param \Magento\Config\Model\Config\Structure\Mapper\Helper\RelativePathConverter $pathConverted
  36. */
  37. public function __construct(
  38. \Magento\Config\Model\Config\Structure\Mapper\Helper\RelativePathConverter $pathConverted
  39. ) {
  40. $this->_pathConverter = $pathConverted;
  41. }
  42. /**
  43. * Apply map
  44. *
  45. * @param array $data
  46. * @return array
  47. */
  48. public function map(array $data)
  49. {
  50. if (!isset($data['config']['system']['sections']) || !is_array($data['config']['system']['sections'])) {
  51. return $data;
  52. }
  53. $this->_systemConfiguration = & $data['config']['system']['sections'];
  54. foreach (array_keys($this->_systemConfiguration) as $nodeName) {
  55. $this->_traverseAndExtend($nodeName);
  56. }
  57. return $data;
  58. }
  59. /**
  60. * Recursively traverse through configuration and apply extends
  61. *
  62. * @param string $path
  63. * @return void
  64. */
  65. protected function _traverseAndExtend($path)
  66. {
  67. $node = $this->_getDataByPath($path);
  68. if (!is_array($node)) {
  69. return;
  70. }
  71. if (!empty($node['extends'])) {
  72. $node = $this->_extendNode($path, $node['extends']);
  73. }
  74. if (!empty($node['children'])) {
  75. foreach (array_keys($node['children']) as $childName) {
  76. $this->_traverseAndExtend($path . '/' . $childName);
  77. }
  78. }
  79. }
  80. /**
  81. * Get data from config by it's path
  82. *
  83. * @param string $path
  84. * @return array
  85. */
  86. protected function _getDataByPath($path)
  87. {
  88. $result = $this->_systemConfiguration;
  89. $pathParts = $this->_transformPathToKeysList($path);
  90. foreach ($pathParts as $part) {
  91. $result = isset($result[$part]) ? $result[$part] : null;
  92. if ($result === null) {
  93. return $result;
  94. }
  95. }
  96. return $result;
  97. }
  98. /**
  99. * Extend node that located under $path in configuration with configuration that is located under $extendSourceNode
  100. *
  101. * @param string $path
  102. * @param string $extendSourceNode
  103. * @return array
  104. * @throws \InvalidArgumentException
  105. */
  106. protected function _extendNode($path, $extendSourceNode)
  107. {
  108. $currentNodeData = $this->_getDataByPath($path);
  109. if (in_array($path, $this->_extendedNodesList)) {
  110. return $currentNodeData;
  111. }
  112. $extendSourcePath = $this->_pathConverter->convert($path, $extendSourceNode);
  113. $data = $this->_getDataByPath($extendSourcePath);
  114. if (!$data) {
  115. throw new \InvalidArgumentException(
  116. sprintf('Invalid path in extends attribute of config/system/sections/%s node', $path)
  117. );
  118. }
  119. if (isset($data['extends'])) {
  120. $data = $this->_extendNode($extendSourcePath, $data['extends']);
  121. }
  122. $resultingData = $this->_mergeData($data, $currentNodeData);
  123. $this->_replaceData($path, $resultingData);
  124. $this->_extendedNodesList[] = $path;
  125. return $resultingData;
  126. }
  127. /**
  128. * Recursively merge two arrays (it overwrites scalars in $arr1 with appropriate scalars from $arr2
  129. * and merges array values)
  130. *
  131. * @param array $arr1
  132. * @param array $arr2
  133. * @return array
  134. */
  135. protected function _mergeData($arr1, $arr2)
  136. {
  137. foreach ($arr2 as $key => $value) {
  138. if (isset($arr1[$key]) && is_array($arr1[$key]) && is_array($value)) {
  139. $arr1[$key] = $this->_mergeData($arr1[$key], $value);
  140. } else {
  141. $arr1[$key] = $value;
  142. }
  143. }
  144. return $arr1;
  145. }
  146. /**
  147. * Replace data in config by path
  148. *
  149. * @param string $path
  150. * @param array $newData
  151. * @return void
  152. */
  153. protected function _replaceData($path, $newData)
  154. {
  155. $pathParts = $this->_transformPathToKeysList($path);
  156. $result = & $this->_systemConfiguration;
  157. foreach ($pathParts as $part) {
  158. if (!isset($result[$part])) {
  159. return;
  160. }
  161. $result = & $result[$part];
  162. }
  163. $result = $newData;
  164. }
  165. /**
  166. * Transform path to list of keys
  167. *
  168. * @param string $path
  169. * @return string[]
  170. */
  171. protected function _transformPathToKeysList($path)
  172. {
  173. $path = str_replace('/', '/children/', $path);
  174. return explode('/', $path);
  175. }
  176. }