Block.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\View\Layout\Generator;
  7. use Magento\Framework\App\State;
  8. use Magento\Framework\Exception\LocalizedException;
  9. use Magento\Framework\ObjectManager\Config\Reader\Dom;
  10. use Magento\Framework\View\Element\Template;
  11. use Magento\Framework\View\Layout;
  12. /**
  13. * Class Block
  14. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  15. */
  16. class Block implements Layout\GeneratorInterface
  17. {
  18. /**
  19. * Type of generator
  20. */
  21. const TYPE = 'block';
  22. /**
  23. * @var \Magento\Framework\View\Element\BlockFactory
  24. */
  25. protected $blockFactory;
  26. /**
  27. * @var \Magento\Framework\Data\Argument\InterpreterInterface
  28. */
  29. protected $argumentInterpreter;
  30. /**
  31. * @var \Magento\Framework\Event\ManagerInterface
  32. */
  33. protected $eventManager;
  34. /**
  35. * @var \Psr\Log\LoggerInterface
  36. */
  37. protected $logger;
  38. /**
  39. * @var \Magento\Framework\App\Config\ScopeConfigInterface
  40. */
  41. protected $scopeConfig;
  42. /**
  43. * @var \Magento\Framework\App\ScopeResolverInterface
  44. */
  45. protected $scopeResolver;
  46. /**
  47. * @var State
  48. */
  49. protected $appState;
  50. /**
  51. * @var \Magento\Framework\View\Element\ExceptionHandlerBlock
  52. */
  53. protected $exceptionHandlerBlockFactory;
  54. /**
  55. * Default block class name. Will be used if no class name is specified in block configuration.
  56. *
  57. * @var string
  58. */
  59. private $defaultClass;
  60. /**
  61. * @param \Magento\Framework\View\Element\BlockFactory $blockFactory
  62. * @param \Magento\Framework\Data\Argument\InterpreterInterface $argumentInterpreter
  63. * @param \Magento\Framework\Event\ManagerInterface $eventManager
  64. * @param \Psr\Log\LoggerInterface $logger
  65. * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
  66. * @param \Magento\Framework\App\ScopeResolverInterface $scopeResolver
  67. * @param \Magento\Framework\View\Element\ExceptionHandlerBlockFactory $exceptionHandlerBlockFactory
  68. * @param State $appState
  69. * @param string $defaultClass
  70. */
  71. public function __construct(
  72. \Magento\Framework\View\Element\BlockFactory $blockFactory,
  73. \Magento\Framework\Data\Argument\InterpreterInterface $argumentInterpreter,
  74. \Magento\Framework\Event\ManagerInterface $eventManager,
  75. \Psr\Log\LoggerInterface $logger,
  76. \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
  77. \Magento\Framework\App\ScopeResolverInterface $scopeResolver,
  78. \Magento\Framework\View\Element\ExceptionHandlerBlockFactory $exceptionHandlerBlockFactory,
  79. State $appState,
  80. $defaultClass = Template::class
  81. ) {
  82. $this->blockFactory = $blockFactory;
  83. $this->argumentInterpreter = $argumentInterpreter;
  84. $this->eventManager = $eventManager;
  85. $this->logger = $logger;
  86. $this->scopeConfig = $scopeConfig;
  87. $this->scopeResolver = $scopeResolver;
  88. $this->exceptionHandlerBlockFactory = $exceptionHandlerBlockFactory;
  89. $this->appState = $appState;
  90. $this->defaultClass = $defaultClass;
  91. }
  92. /**
  93. * @inheritdoc
  94. */
  95. public function getType()
  96. {
  97. return self::TYPE;
  98. }
  99. /**
  100. * Creates block object based on data and add it to the layout
  101. *
  102. * @param Layout\Reader\Context $readerContext
  103. * @param Context $generatorContext
  104. * @return $this
  105. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  106. */
  107. public function process(Layout\Reader\Context $readerContext, Layout\Generator\Context $generatorContext)
  108. {
  109. $scheduledStructure = $readerContext->getScheduledStructure();
  110. $layout = $generatorContext->getLayout();
  111. $structure = $generatorContext->getStructure();
  112. /** @var $blocks \Magento\Framework\View\Element\AbstractBlock[] */
  113. $blocks = [];
  114. $blockActions = [];
  115. // Instantiate blocks and collect all actions data
  116. foreach ($scheduledStructure->getElements() as $elementName => $element) {
  117. list($type, $data) = $element;
  118. if ($type === self::TYPE) {
  119. try {
  120. $block = $this->generateBlock($scheduledStructure, $structure, $elementName);
  121. $blocks[$elementName] = $block;
  122. $layout->setBlock($elementName, $block);
  123. if (!empty($data['actions'])) {
  124. $blockActions[$elementName] = $data['actions'];
  125. }
  126. } catch (\Exception $e) {
  127. $this->handleRenderException($e);
  128. unset($blocks[$elementName]);
  129. }
  130. }
  131. }
  132. // Set layout instance to all generated block (trigger _prepareLayout method)
  133. foreach ($blocks as $elementName => $block) {
  134. try {
  135. $block->setLayout($layout);
  136. $this->eventManager->dispatch('core_layout_block_create_after', ['block' => $block]);
  137. } catch (\Exception $e) {
  138. $this->handleRenderException($e);
  139. $layout->setBlock(
  140. $elementName,
  141. $this->exceptionHandlerBlockFactory->create(['blockName' => $elementName])
  142. );
  143. unset($blockActions[$elementName]);
  144. }
  145. $scheduledStructure->unsetElement($elementName);
  146. }
  147. // Run all actions after layout initialization
  148. foreach ($blockActions as $elementName => $actions) {
  149. try {
  150. foreach ($actions as $action) {
  151. list($methodName, $actionArguments, $configPath, $scopeType) = $action;
  152. if (empty($configPath)
  153. || $this->scopeConfig->isSetFlag($configPath, $scopeType, $this->scopeResolver->getScope())
  154. ) {
  155. $this->generateAction($blocks[$elementName], $methodName, $actionArguments);
  156. }
  157. }
  158. } catch (\Exception $e) {
  159. $this->handleRenderException($e);
  160. $layout->setBlock(
  161. $elementName,
  162. $this->exceptionHandlerBlockFactory->create(['blockName' => $elementName])
  163. );
  164. }
  165. }
  166. return $this;
  167. }
  168. /**
  169. * Handle exceptions during rendering process
  170. *
  171. * @param \Exception $cause
  172. * @throws \Exception
  173. * @return void
  174. */
  175. protected function handleRenderException(\Exception $cause)
  176. {
  177. if ($this->appState->getMode() === State::MODE_DEVELOPER) {
  178. throw $cause;
  179. }
  180. $message = ($cause instanceof LocalizedException) ? $cause->getLogMessage() : $cause->getMessage();
  181. $this->logger->critical($message);
  182. }
  183. /**
  184. * Create block and set related data
  185. *
  186. * @param \Magento\Framework\View\Layout\ScheduledStructure $scheduledStructure
  187. * @param \Magento\Framework\View\Layout\Data\Structure $structure
  188. * @param string $elementName
  189. * @return \Magento\Framework\View\Element\AbstractBlock
  190. */
  191. protected function generateBlock(
  192. Layout\ScheduledStructure $scheduledStructure,
  193. Layout\Data\Structure $structure,
  194. $elementName
  195. ) {
  196. list(, $data) = $scheduledStructure->getElement($elementName);
  197. $attributes = $data['attributes'];
  198. if (!empty($attributes['group'])) {
  199. $structure->addToParentGroup($elementName, $attributes['group']);
  200. }
  201. if (!empty($attributes['display'])) {
  202. $structure->setAttribute($elementName, 'display', $attributes['display']);
  203. }
  204. // create block
  205. $className = isset($attributes['class']) && !empty($attributes['class']) ?
  206. $attributes['class'] : $this->defaultClass;
  207. $block = $this->createBlock($className, $elementName, [
  208. 'data' => $this->evaluateArguments($data['arguments'])
  209. ]);
  210. if (!empty($attributes['template'])) {
  211. $block->setTemplate($attributes['template']);
  212. }
  213. if (!empty($attributes['ttl'])) {
  214. $ttl = (int)$attributes['ttl'];
  215. $block->setTtl($ttl);
  216. }
  217. return $block;
  218. }
  219. /**
  220. * Create block instance
  221. *
  222. * @param string|\Magento\Framework\View\Element\AbstractBlock $block
  223. * @param string $name
  224. * @param array $arguments
  225. * @return \Magento\Framework\View\Element\AbstractBlock
  226. */
  227. public function createBlock($block, $name, array $arguments = [])
  228. {
  229. $block = $this->getBlockInstance($block, $arguments);
  230. $block->setType(get_class($block));
  231. $block->setNameInLayout($name);
  232. $block->addData(isset($arguments['data']) ? $arguments['data'] : []);
  233. return $block;
  234. }
  235. /**
  236. * Create block object instance based on block type
  237. *
  238. * @param string|\Magento\Framework\View\Element\AbstractBlock $block
  239. * @param array $arguments
  240. * @throws \Magento\Framework\Exception\LocalizedException
  241. * @return \Magento\Framework\View\Element\AbstractBlock
  242. */
  243. protected function getBlockInstance($block, array $arguments = [])
  244. {
  245. $e = null;
  246. if ($block && is_string($block)) {
  247. try {
  248. $block = $this->blockFactory->createBlock($block, $arguments);
  249. } catch (\ReflectionException $e) {
  250. $this->logger->critical($e->getMessage());
  251. }
  252. }
  253. if (!$block instanceof \Magento\Framework\View\Element\AbstractBlock) {
  254. throw new LocalizedException(
  255. new \Magento\Framework\Phrase(
  256. 'Invalid block type: %1',
  257. [is_object($block) ? get_class($block) : (string) $block]
  258. ),
  259. $e
  260. );
  261. }
  262. return $block;
  263. }
  264. /**
  265. * Run action defined in layout update
  266. *
  267. * @param \Magento\Framework\View\Element\AbstractBlock $block
  268. * @param string $methodName
  269. * @param array $actionArguments
  270. * @return void
  271. */
  272. protected function generateAction($block, $methodName, $actionArguments)
  273. {
  274. $profilerKey = 'BLOCK_ACTION:' . $block->getNameInLayout() . '>' . $methodName;
  275. \Magento\Framework\Profiler::start($profilerKey);
  276. $args = $this->evaluateArguments($actionArguments);
  277. call_user_func_array([$block, $methodName], $args);
  278. \Magento\Framework\Profiler::stop($profilerKey);
  279. }
  280. /**
  281. * Compute and return argument values
  282. *
  283. * @param array $arguments
  284. * @return array
  285. */
  286. protected function evaluateArguments(array $arguments)
  287. {
  288. $result = [];
  289. foreach ($arguments as $argumentName => $argumentData) {
  290. if (!isset($argumentData[Dom::TYPE_ATTRIBUTE])) {
  291. $result[$argumentName] = $argumentData;
  292. continue;
  293. }
  294. $result[$argumentName] = $this->argumentInterpreter->evaluate($argumentData);
  295. }
  296. return $result;
  297. }
  298. }