UiComponentFactory.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\View\Element;
  7. use Magento\Framework\Config\DataInterface;
  8. use Magento\Framework\Config\DataInterfaceFactory;
  9. use Magento\Framework\DataObject;
  10. use Magento\Framework\Exception\LocalizedException;
  11. use Magento\Framework\ObjectManagerInterface;
  12. use Magento\Framework\Data\Argument\InterpreterInterface;
  13. use Magento\Framework\View\Element\UiComponent\ContextInterface;
  14. use Magento\Framework\View\Element\UiComponent\Config\ManagerInterface;
  15. use Magento\Framework\View\Element\UiComponent\ContextFactory;
  16. use Magento\Framework\Phrase;
  17. use Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface;
  18. use Magento\Framework\View\Element\UiComponent\Factory\ComponentFactoryInterface;
  19. /**
  20. * Class UiComponentFactory
  21. *
  22. * @api
  23. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  24. * @since 100.0.2
  25. */
  26. class UiComponentFactory extends DataObject
  27. {
  28. /**
  29. * Object manager
  30. *
  31. * @var ObjectManagerInterface
  32. */
  33. protected $objectManager;
  34. /**
  35. * Argument interpreter
  36. *
  37. * @var InterpreterInterface
  38. */
  39. protected $argumentInterpreter;
  40. /**
  41. * @var ContextFactory
  42. */
  43. protected $contextFactory;
  44. /**
  45. * UI component manager
  46. *
  47. * @deprecated 101.0.0
  48. * @var ManagerInterface
  49. */
  50. protected $componentManager;
  51. /**
  52. * @var ComponentFactoryInterface[]
  53. */
  54. private $componentChildFactories;
  55. /**
  56. * @var DataInterfaceFactory
  57. */
  58. private $configFactory;
  59. /**
  60. * @var \Magento\Ui\Config\Reader\Definition\Data
  61. */
  62. private $definitionData;
  63. /**
  64. * @param ObjectManagerInterface $objectManager
  65. * @param ManagerInterface $componentManager
  66. * @param InterpreterInterface $argumentInterpreter
  67. * @param ContextFactory $contextFactory
  68. * @param DataInterfaceFactory|null $configFactory
  69. * @param array $data
  70. * @param array $componentChildFactories
  71. * @param DataInterface|null $definitionData
  72. */
  73. public function __construct(
  74. ObjectManagerInterface $objectManager,
  75. ManagerInterface $componentManager,
  76. InterpreterInterface $argumentInterpreter,
  77. ContextFactory $contextFactory,
  78. array $data = [],
  79. array $componentChildFactories = [],
  80. DataInterface $definitionData = null,
  81. DataInterfaceFactory $configFactory = null
  82. ) {
  83. $this->objectManager = $objectManager;
  84. $this->componentManager = $componentManager;
  85. $this->argumentInterpreter = $argumentInterpreter;
  86. $this->contextFactory = $contextFactory;
  87. $this->componentChildFactories = $componentChildFactories;
  88. $this->configFactory = $configFactory ?: $this->objectManager->get(DataInterfaceFactory::class);
  89. parent::__construct($data);
  90. $this->definitionData = $definitionData ?:
  91. $this->objectManager->get(DataInterface::class);
  92. }
  93. /**
  94. * Create child components
  95. *
  96. * @param array $bundleComponents
  97. * @param ContextInterface $renderContext
  98. * @param string $identifier
  99. * @param array $arguments
  100. * @return UiComponentInterface
  101. */
  102. protected function createChildComponent(
  103. array &$bundleComponents,
  104. ContextInterface $renderContext,
  105. $identifier,
  106. array $arguments = []
  107. ) {
  108. $componentArguments = &$bundleComponents['arguments'];
  109. list($className, $componentArguments) = $this->argumentsResolver($identifier, $bundleComponents);
  110. if (isset($componentArguments['data']['disabled']) && (int)$componentArguments['data']['disabled']) {
  111. return null;
  112. }
  113. /**
  114. * Add an ability to fill component variables from child factory.
  115. */
  116. $bundleComponents['components'] = [];
  117. $components = &$bundleComponents['components'];
  118. if (isset($this->componentChildFactories[$className])) {
  119. $factory = $this->componentChildFactories[$className];
  120. /**
  121. * Factory return nothing
  122. * because factory should put created components in the right place
  123. */
  124. $factory->create($bundleComponents, $arguments);
  125. } else {
  126. foreach ($bundleComponents['children'] as $childrenIdentifier => $childrenData) {
  127. $children = $this->createChildComponent(
  128. $childrenData,
  129. $renderContext,
  130. $childrenIdentifier,
  131. $arguments
  132. );
  133. $components[$childrenIdentifier] = $children;
  134. }
  135. }
  136. $components = array_filter($components);
  137. $componentArguments['components'] = $components;
  138. /**
  139. * Prevent passing ACL restricted blocks to htmlContent constructor
  140. */
  141. if (isset($componentArguments['block']) && !$componentArguments['block']) {
  142. return null;
  143. }
  144. if (!isset($componentArguments['context'])) {
  145. $componentArguments['context'] = $renderContext;
  146. }
  147. return $this->objectManager->create($className, $componentArguments);
  148. }
  149. /**
  150. * Resolve arguments
  151. *
  152. * @param string $identifier
  153. * @param array $componentData
  154. * @return array
  155. */
  156. protected function argumentsResolver($identifier, array $componentData)
  157. {
  158. $attributes = $componentData[ManagerInterface::COMPONENT_ATTRIBUTES_KEY];
  159. $className = $attributes['class'];
  160. unset($attributes['class']);
  161. $arguments = $componentData[ManagerInterface::COMPONENT_ARGUMENTS_KEY];
  162. if (!isset($arguments['data'])) {
  163. $arguments['data'] = [];
  164. }
  165. unset($attributes['component']);
  166. $arguments['data'] = array_merge($arguments['data'], ['name' => $identifier], $attributes);
  167. return [$className, $arguments];
  168. }
  169. /**
  170. * Create component object
  171. *
  172. * @param string $identifier
  173. * @param string $name
  174. * @param array $arguments
  175. * @return UiComponentInterface
  176. * @throws \Magento\Framework\Exception\LocalizedException
  177. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  178. */
  179. public function create($identifier, $name = null, array $arguments = [])
  180. {
  181. if ($name === null) {
  182. $componentData = $this->configFactory->create(['componentName' => $identifier])->get($identifier);
  183. $bundleComponents = [$identifier => $componentData];
  184. list($className, $componentArguments) = $this->argumentsResolver(
  185. $identifier,
  186. $bundleComponents[$identifier]
  187. );
  188. $componentArguments = array_replace_recursive($componentArguments, $arguments);
  189. if (!isset($componentArguments['context'])) {
  190. $componentArguments['context'] = $this->contextFactory->create(
  191. ['namespace' => $identifier]
  192. );
  193. }
  194. $reverseMerge = isset($componentArguments['data']['reverseMetadataMerge'])
  195. && $componentArguments['data']['reverseMetadataMerge'];
  196. $bundleComponents = $this->mergeMetadata($identifier, $bundleComponents, $reverseMerge);
  197. $children = $bundleComponents[$identifier]['children'];
  198. } else {
  199. $rawComponentData = $this->definitionData->get($name);
  200. list($className, $componentArguments) = $this->argumentsResolver($identifier, $rawComponentData);
  201. $componentArguments = array_replace_recursive($componentArguments, $arguments);
  202. $children = isset($componentArguments['data']['config']['children']) ?
  203. $componentArguments['data']['config']['children'] : [];
  204. $children = $this->getBundleChildren($children);
  205. }
  206. $className = isset($componentArguments['config']['class']) ?
  207. $componentArguments['config']['class'] : $className;
  208. $components = [];
  209. foreach ($children as $childrenIdentifier => $childrenData) {
  210. $children = $this->createChildComponent(
  211. $childrenData,
  212. $componentArguments['context'],
  213. $childrenIdentifier,
  214. $arguments
  215. );
  216. $components[$childrenIdentifier] = $children;
  217. }
  218. $components = array_filter($components);
  219. $componentArguments['components'] = $components;
  220. /** @var \Magento\Framework\View\Element\UiComponentInterface $component */
  221. $component = $this->objectManager->create(
  222. $className,
  223. $componentArguments
  224. );
  225. return $component;
  226. }
  227. /**
  228. * Get bundle children
  229. *
  230. * @param array $children
  231. * @return array
  232. * @throws LocalizedException
  233. * @since 100.1.0
  234. */
  235. protected function getBundleChildren(array $children = [])
  236. {
  237. $bundleChildren = [];
  238. foreach ($children as $identifier => $config) {
  239. if (!isset($config['componentType'])) {
  240. throw new LocalizedException(new Phrase(
  241. 'The "componentType" configuration parameter is required for the "%1" component.',
  242. $identifier
  243. ));
  244. }
  245. if (!isset($componentArguments['context'])) {
  246. throw new LocalizedException(
  247. new \Magento\Framework\Phrase(
  248. 'An error occurred with the UI component. Each component needs context. Verify and try again.'
  249. )
  250. );
  251. }
  252. $rawComponentData = $this->definitionData->get($config['componentType']);
  253. list(, $componentArguments) = $this->argumentsResolver($identifier, $rawComponentData);
  254. $arguments = array_replace_recursive($componentArguments, ['data' => ['config' => $config]]);
  255. $rawComponentData[ManagerInterface::COMPONENT_ARGUMENTS_KEY] = $arguments;
  256. $bundleChildren[$identifier] = $rawComponentData;
  257. $bundleChildren[$identifier]['children'] = [];
  258. if (isset($arguments['data']['config']['children'])) {
  259. $bundleChildren[$identifier]['children'] = $this->getBundleChildren(
  260. $arguments['data']['config']['children']
  261. );
  262. }
  263. }
  264. return $bundleChildren;
  265. }
  266. /**
  267. * Merge data provider's metadata to components
  268. *
  269. * @param string $identifier
  270. * @param array $bundleComponents
  271. * @param bool $reverseMerge
  272. * @return array
  273. * @since 100.1.0
  274. */
  275. protected function mergeMetadata($identifier, array $bundleComponents, $reverseMerge = false)
  276. {
  277. $dataProvider = $this->getDataProvider($identifier, $bundleComponents);
  278. if ($dataProvider instanceof DataProviderInterface) {
  279. $metadata = [
  280. $identifier => [
  281. 'children' => $dataProvider->getMeta(),
  282. ],
  283. ];
  284. $bundleComponents = $this->mergeMetadataItem($bundleComponents, $metadata, $reverseMerge);
  285. }
  286. return $bundleComponents;
  287. }
  288. /**
  289. * Find element in components or its containers and merge data to it
  290. *
  291. * @param array $bundleComponents
  292. * @param string $name
  293. * @param array $data
  294. * @param bool $reverseMerge
  295. * @return array
  296. * @since 100.1.0
  297. */
  298. protected function mergeMetadataElement(array $bundleComponents, $name, array $data, $reverseMerge = false)
  299. {
  300. if (isset($bundleComponents[$name])) {
  301. $bundleComponents[$name] = $reverseMerge
  302. ? array_replace_recursive($data, $bundleComponents[$name])
  303. : array_replace_recursive($bundleComponents[$name], $data);
  304. return [$bundleComponents, true];
  305. } else {
  306. foreach ($bundleComponents as &$childData) {
  307. if (isset($childData['attributes']['class'])
  308. && is_a($childData['attributes']['class'], \Magento\Ui\Component\Container::class, true)
  309. && isset($childData['children']) && is_array($childData['children'])
  310. ) {
  311. list($childData['children'], $isMerged) = $this->mergeMetadataElement(
  312. $childData['children'],
  313. $name,
  314. $data,
  315. $reverseMerge
  316. );
  317. if ($isMerged) {
  318. return [$bundleComponents, true];
  319. }
  320. }
  321. }
  322. }
  323. return [$bundleComponents, false];
  324. }
  325. /**
  326. * Merge metadata item to components
  327. *
  328. * @param array $bundleComponents
  329. * @param array $metadata
  330. * @param bool $reverseMerge
  331. * @return array
  332. * @throws LocalizedException
  333. * @since 100.1.0
  334. */
  335. protected function mergeMetadataItem(array $bundleComponents, array $metadata, $reverseMerge = false)
  336. {
  337. foreach ($metadata as $name => $data) {
  338. $selfData = $data;
  339. if (isset($selfData['children'])) {
  340. unset($selfData['children']);
  341. }
  342. list($bundleComponents, $isMerged) = $this->mergeMetadataElement(
  343. $bundleComponents,
  344. $name,
  345. $selfData,
  346. $reverseMerge
  347. );
  348. if (!$isMerged) {
  349. if (!isset($data['arguments']['data']['config']['componentType'])) {
  350. throw new LocalizedException(new Phrase(
  351. 'The "componentType" configuration parameter is required for the "%1" component.',
  352. [$name]
  353. ));
  354. }
  355. $rawComponentData = $this->definitionData->get(
  356. $data['arguments']['data']['config']['componentType']
  357. );
  358. list(, $componentArguments) = $this->argumentsResolver($name, $rawComponentData);
  359. $arguments = array_replace_recursive($componentArguments, $data['arguments']);
  360. $rawComponentData[ManagerInterface::COMPONENT_ARGUMENTS_KEY] = $arguments;
  361. $bundleComponents[$name] = $rawComponentData;
  362. $bundleComponents[$name]['children'] = [];
  363. }
  364. if (isset($data['children']) && is_array($data['children'])) {
  365. $bundleComponents[$name]['children'] = $this->mergeMetadataItem(
  366. $bundleComponents[$name]['children'],
  367. $data['children'],
  368. $reverseMerge
  369. );
  370. }
  371. }
  372. return $bundleComponents;
  373. }
  374. /**
  375. * Find and return data provider
  376. *
  377. * @param string $identifier
  378. * @param array $bundleComponents
  379. * @return DataProviderInterface|null
  380. * @since 100.1.0
  381. */
  382. protected function getDataProvider($identifier, array $bundleComponents)
  383. {
  384. foreach ($bundleComponents[$identifier]['children'] as $childrenData) {
  385. if (isset($childrenData['arguments']['dataProvider'])
  386. && $childrenData['arguments']['dataProvider'] instanceof DataProviderInterface
  387. ) {
  388. return $childrenData['arguments']['dataProvider'];
  389. }
  390. }
  391. return null;
  392. }
  393. }