PageRenderTime 43ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/sonata-project/block-bundle/Templating/Helper/BlockHelper.php

https://gitlab.com/cuza/Clinic_Recods
PHP | 480 lines | 272 code | 80 blank | 128 comment | 30 complexity | bb2a9b8dcaf48d79e4570ccc734cea1f MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Sonata Project package.
  4. *
  5. * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
  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 Sonata\BlockBundle\Templating\Helper;
  11. use Doctrine\Common\Util\ClassUtils;
  12. use Sonata\BlockBundle\Block\BlockContextInterface;
  13. use Sonata\BlockBundle\Block\BlockContextManagerInterface;
  14. use Sonata\BlockBundle\Block\BlockRendererInterface;
  15. use Sonata\BlockBundle\Block\BlockServiceManagerInterface;
  16. use Sonata\BlockBundle\Cache\HttpCacheHandlerInterface;
  17. use Sonata\BlockBundle\Event\BlockEvent;
  18. use Sonata\BlockBundle\Model\BlockInterface;
  19. use Sonata\BlockBundle\Util\RecursiveBlockIterator;
  20. use Sonata\Cache\CacheAdapterInterface;
  21. use Sonata\Cache\CacheManagerInterface;
  22. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  23. use Symfony\Component\HttpFoundation\Response;
  24. use Symfony\Component\Stopwatch\Stopwatch;
  25. use Symfony\Component\Templating\Helper\Helper;
  26. class BlockHelper extends Helper
  27. {
  28. /**
  29. * @var BlockServiceManagerInterface
  30. */
  31. private $blockServiceManager;
  32. /**
  33. * @var CacheManagerInterface
  34. */
  35. private $cacheManager;
  36. /**
  37. * @var array
  38. */
  39. private $cacheBlocks;
  40. /**
  41. * @var BlockRendererInterface
  42. */
  43. private $blockRenderer;
  44. /**
  45. * @var BlockContextManagerInterface
  46. */
  47. private $blockContextManager;
  48. /**
  49. * @var HttpCacheHandlerInterface
  50. */
  51. private $cacheHandler;
  52. /**
  53. * @var EventDispatcherInterface
  54. */
  55. private $eventDispatcher;
  56. /**
  57. * This property is a state variable holdings all assets used by the block for the current PHP request
  58. * It is used to correctly render the javascripts and stylesheets tags on the main layout.
  59. *
  60. * @var array
  61. */
  62. private $assets;
  63. /**
  64. * @var array
  65. */
  66. private $traces;
  67. /**
  68. * @var Stopwatch
  69. */
  70. private $stopwatch;
  71. /**
  72. * @param BlockServiceManagerInterface $blockServiceManager
  73. * @param array $cacheBlocks
  74. * @param BlockRendererInterface $blockRenderer
  75. * @param BlockContextManagerInterface $blockContextManager
  76. * @param EventDispatcherInterface $eventDispatcher
  77. * @param CacheManagerInterface $cacheManager
  78. * @param HttpCacheHandlerInterface $cacheHandler
  79. * @param Stopwatch $stopwatch
  80. */
  81. public function __construct(BlockServiceManagerInterface $blockServiceManager, array $cacheBlocks, BlockRendererInterface $blockRenderer,
  82. BlockContextManagerInterface $blockContextManager, EventDispatcherInterface $eventDispatcher,
  83. CacheManagerInterface $cacheManager = null, HttpCacheHandlerInterface $cacheHandler = null, Stopwatch $stopwatch = null)
  84. {
  85. $this->blockServiceManager = $blockServiceManager;
  86. $this->cacheBlocks = $cacheBlocks;
  87. $this->blockRenderer = $blockRenderer;
  88. $this->eventDispatcher = $eventDispatcher;
  89. $this->cacheManager = $cacheManager;
  90. $this->blockContextManager = $blockContextManager;
  91. $this->cacheHandler = $cacheHandler;
  92. $this->stopwatch = $stopwatch;
  93. $this->assets = array(
  94. 'js' => array(),
  95. 'css' => array(),
  96. );
  97. $this->traces = array(
  98. '_events' => array(),
  99. );
  100. }
  101. /**
  102. * {@inheritdoc}
  103. */
  104. public function getName()
  105. {
  106. return 'sonata_block';
  107. }
  108. /**
  109. * @param string $media Unused, only kept to not break existing code
  110. * @param string $basePath Base path to prepend to the stylesheet urls.
  111. *
  112. * @return array|string
  113. */
  114. public function includeJavascripts($media, $basePath = '')
  115. {
  116. $html = '';
  117. foreach ($this->assets['js'] as $javascript) {
  118. $html .= "\n".sprintf('<script src="%s%s" type="text/javascript"></script>', $basePath, $javascript);
  119. }
  120. return $html;
  121. }
  122. /**
  123. * @param string $media The css media type to use: all|screen|...
  124. * @param string $basePath Base path to prepend to the stylesheet urls.
  125. *
  126. * @return array|string
  127. */
  128. public function includeStylesheets($media, $basePath = '')
  129. {
  130. if (0 === count($this->assets['css'])) {
  131. return '';
  132. }
  133. $html = sprintf("<style type='text/css' media='%s'>", $media);
  134. foreach ($this->assets['css'] as $stylesheet) {
  135. $html .= "\n".sprintf('@import url(%s%s);', $basePath, $stylesheet);
  136. }
  137. $html .= "\n</style>";
  138. return $html;
  139. }
  140. /**
  141. * Traverse the parent block and its children to retrieve the correct list css and javascript only for main block.
  142. *
  143. * @param BlockContextInterface $blockContext
  144. * @param array $stats
  145. */
  146. protected function computeAssets(BlockContextInterface $blockContext, array &$stats = null)
  147. {
  148. if ($blockContext->getBlock()->hasParent()) {
  149. return;
  150. }
  151. $service = $this->blockServiceManager->get($blockContext->getBlock());
  152. $assets = array(
  153. 'js' => $service->getJavascripts('all'),
  154. 'css' => $service->getStylesheets('all'),
  155. );
  156. if ($blockContext->getBlock()->hasChildren()) {
  157. $iterator = new \RecursiveIteratorIterator(new RecursiveBlockIterator($blockContext->getBlock()->getChildren()));
  158. foreach ($iterator as $block) {
  159. $assets = array(
  160. 'js' => array_merge($this->blockServiceManager->get($block)->getJavascripts('all'), $assets['js']),
  161. 'css' => array_merge($this->blockServiceManager->get($block)->getStylesheets('all'), $assets['css']),
  162. );
  163. }
  164. }
  165. if ($this->stopwatch) {
  166. $stats['assets'] = $assets;
  167. }
  168. $this->assets = array(
  169. 'js' => array_unique(array_merge($assets['js'], $this->assets['js'])),
  170. 'css' => array_unique(array_merge($assets['css'], $this->assets['css'])),
  171. );
  172. }
  173. /**
  174. * @param BlockInterface $block
  175. *
  176. * @return array
  177. */
  178. protected function startTracing(BlockInterface $block)
  179. {
  180. $this->traces[$block->getId()] = $this->stopwatch->start(sprintf('%s (id: %s, type: %s)', $block->getName(), $block->getId(), $block->getType()));
  181. return array(
  182. 'name' => $block->getName(),
  183. 'type' => $block->getType(),
  184. 'duration' => false,
  185. 'memory_start' => memory_get_usage(true),
  186. 'memory_end' => false,
  187. 'memory_peak' => false,
  188. 'cache' => array(
  189. 'keys' => array(),
  190. 'contextual_keys' => array(),
  191. 'handler' => false,
  192. 'from_cache' => false,
  193. 'ttl' => 0,
  194. 'created_at' => false,
  195. 'lifetime' => 0,
  196. 'age' => 0,
  197. ),
  198. 'assets' => array(
  199. 'js' => array(),
  200. 'css' => array(),
  201. ),
  202. );
  203. }
  204. /**
  205. * @param BlockInterface $block
  206. * @param array $stats
  207. */
  208. protected function stopTracing(BlockInterface $block, array $stats)
  209. {
  210. $e = $this->traces[$block->getId()]->stop();
  211. $this->traces[$block->getId()] = array_merge($stats, array(
  212. 'duration' => $e->getDuration(),
  213. 'memory_end' => memory_get_usage(true),
  214. 'memory_peak' => memory_get_peak_usage(true),
  215. ));
  216. $this->traces[$block->getId()]['cache']['lifetime'] = $this->traces[$block->getId()]['cache']['age'] + $this->traces[$block->getId()]['cache']['ttl'];
  217. }
  218. /**
  219. * @param string $name
  220. * @param array $options
  221. *
  222. * @return string
  223. */
  224. public function renderEvent($name, array $options = array())
  225. {
  226. $eventName = sprintf('sonata.block.event.%s', $name);
  227. $event = $this->eventDispatcher->dispatch($eventName, new BlockEvent($options));
  228. $content = '';
  229. foreach ($event->getBlocks() as $block) {
  230. $content .= $this->render($block);
  231. }
  232. if ($this->stopwatch) {
  233. $this->traces['_events'][uniqid()] = array(
  234. 'template_code' => $name,
  235. 'event_name' => $eventName,
  236. 'blocks' => $this->getEventBlocks($event),
  237. 'listeners' => $this->getEventListeners($eventName),
  238. );
  239. }
  240. return $content;
  241. }
  242. /**
  243. * @param BlockEvent $event
  244. *
  245. * @return array
  246. */
  247. protected function getEventBlocks(BlockEvent $event)
  248. {
  249. $results = array();
  250. foreach ($event->getBlocks() as $block) {
  251. $results[] = array($block->getId(), $block->getType());
  252. }
  253. return $results;
  254. }
  255. /**
  256. * @param string $eventName
  257. *
  258. * @return array
  259. */
  260. protected function getEventListeners($eventName)
  261. {
  262. $results = array();
  263. foreach ($this->eventDispatcher->getListeners($eventName) as $listener) {
  264. if (is_object($listener[0])) {
  265. $results[] = get_class($listener[0]);
  266. } elseif (is_string($listener[0])) {
  267. $results[] = $listener[0];
  268. } elseif ($listener instanceof \Closure) {
  269. $results[] = '{closure}()';
  270. } else {
  271. $results[] = 'Unknown type!';
  272. }
  273. }
  274. return $results;
  275. }
  276. /**
  277. * Check if a given block type exists.
  278. *
  279. * @param string $type Block type to check for
  280. *
  281. * @return bool
  282. */
  283. public function exists($type)
  284. {
  285. return $this->blockContextManager->exists($type);
  286. }
  287. /**
  288. * @param mixed $block
  289. * @param array $options
  290. *
  291. * @return null|Response
  292. */
  293. public function render($block, array $options = array())
  294. {
  295. $blockContext = $this->blockContextManager->get($block, $options);
  296. if (!$blockContext instanceof BlockContextInterface) {
  297. return '';
  298. }
  299. $stats = array();
  300. if ($this->stopwatch) {
  301. $stats = $this->startTracing($blockContext->getBlock());
  302. }
  303. $service = $this->blockServiceManager->get($blockContext->getBlock());
  304. $this->computeAssets($blockContext, $stats);
  305. $useCache = $blockContext->getSetting('use_cache');
  306. $cacheKeys = $response = false;
  307. $cacheService = $useCache ? $this->getCacheService($blockContext->getBlock(), $stats) : false;
  308. if ($cacheService) {
  309. $cacheKeys = array_merge(
  310. $service->getCacheKeys($blockContext->getBlock()),
  311. $blockContext->getSetting('extra_cache_keys')
  312. );
  313. if ($this->stopwatch) {
  314. $stats['cache']['keys'] = $cacheKeys;
  315. }
  316. // Please note, some cache handler will always return true (js for instance)
  317. // This will allows to have a non cacheable block, but the global page can still be cached by
  318. // a reverse proxy, as the generated page will never get the generated Response from the block.
  319. if ($cacheService->has($cacheKeys)) {
  320. $cacheElement = $cacheService->get($cacheKeys);
  321. if ($this->stopwatch) {
  322. $stats['cache']['from_cache'] = false;
  323. }
  324. if (!$cacheElement->isExpired() && $cacheElement->getData() instanceof Response) {
  325. /* @var Response $response */
  326. if ($this->stopwatch) {
  327. $stats['cache']['from_cache'] = true;
  328. }
  329. $response = $cacheElement->getData();
  330. }
  331. }
  332. }
  333. if (!$response) {
  334. $recorder = null;
  335. if ($this->cacheManager) {
  336. $recorder = $this->cacheManager->getRecorder();
  337. if ($recorder) {
  338. $recorder->add($blockContext->getBlock());
  339. $recorder->push();
  340. }
  341. }
  342. $response = $this->blockRenderer->render($blockContext);
  343. $contextualKeys = $recorder ? $recorder->pop() : array();
  344. if ($this->stopwatch) {
  345. $stats['cache']['contextual_keys'] = $contextualKeys;
  346. }
  347. if ($response->isCacheable() && $cacheKeys && $cacheService) {
  348. $cacheService->set($cacheKeys, $response, $response->getTtl(), $contextualKeys);
  349. }
  350. }
  351. if ($this->stopwatch) {
  352. $stats['cache']['created_at'] = $response->getDate();
  353. $stats['cache']['ttl'] = $response->getTtl() ?: 0;
  354. $stats['cache']['age'] = $response->getAge();
  355. }
  356. // update final ttl for the whole Response
  357. if ($this->cacheHandler) {
  358. $this->cacheHandler->updateMetadata($response, $blockContext);
  359. }
  360. if ($this->stopwatch) {
  361. $this->stopTracing($blockContext->getBlock(), $stats);
  362. }
  363. return $response->getContent();
  364. }
  365. /**
  366. * @param BlockInterface $block
  367. * @param array $stats
  368. *
  369. * @return CacheAdapterInterface
  370. */
  371. protected function getCacheService(BlockInterface $block, array &$stats = null)
  372. {
  373. if (!$this->cacheManager) {
  374. return false;
  375. }
  376. // type by block class
  377. $class = ClassUtils::getClass($block);
  378. $cacheServiceId = isset($this->cacheBlocks['by_class'][$class]) ? $this->cacheBlocks['by_class'][$class] : false;
  379. // type by block service
  380. if (!$cacheServiceId) {
  381. $cacheServiceId = isset($this->cacheBlocks['by_type'][$block->getType()]) ? $this->cacheBlocks['by_type'][$block->getType()] : false;
  382. }
  383. if (!$cacheServiceId) {
  384. return false;
  385. }
  386. if ($this->stopwatch) {
  387. $stats['cache']['handler'] = $cacheServiceId;
  388. }
  389. return $this->cacheManager->getCacheService($cacheServiceId);
  390. }
  391. /**
  392. * Returns the rendering traces.
  393. *
  394. * @return array
  395. */
  396. public function getTraces()
  397. {
  398. return $this->traces;
  399. }
  400. }