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

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4.  * This file is part of the Sonata Project package.
  5.  *
  6.  * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
  7.  *
  8.  * For the full copyright and license information, please view the LICENSE
  9.  * file that was distributed with this source code.
  10.  */
  11. namespace Sonata\BlockBundle\Templating\Helper;
  12. use Sonata\BlockBundle\Block\BlockContextManagerInterface;
  13. use Sonata\BlockBundle\Block\BlockRendererInterface;
  14. use Sonata\BlockBundle\Event\BlockEvent;
  15. use Sonata\BlockBundle\Model\BlockInterface;
  16. use Symfony\Component\EventDispatcher\EventDispatcherInterface as EventDispatcherComponentInterface;
  17. use Symfony\Component\Stopwatch\Stopwatch;
  18. use Symfony\Component\Stopwatch\StopwatchEvent;
  19. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  20. /**
  21.  * @phpstan-type Trace = array{
  22.  *     name: string,
  23.  *     type: string,
  24.  *     duration: int|float|false,
  25.  *     memory_start: int|false,
  26.  *     memory_end: int|false,
  27.  *     memory_peak: int|false,
  28.  *     assets: array{
  29.  *         js: string[],
  30.  *         css: string[],
  31.  *     }
  32.  * }
  33.  */
  34. class BlockHelper
  35. {
  36.     /**
  37.      * This property is a state variable holdings all assets used by the block for the current PHP request
  38.      * It is used to correctly render the javascripts and stylesheets tags on the main layout.
  39.      *
  40.      * @var array{css: array<string>, js: array<string>}
  41.      */
  42.     private array $assets = ['css' => [], 'js' => []];
  43.     /**
  44.      * @var array<StopwatchEvent|array<string, mixed>>
  45.      *
  46.      * @phpstan-var array<StopwatchEvent|Trace>
  47.      */
  48.     private array $traces = [];
  49.     /**
  50.      * @var array<string, mixed>
  51.      */
  52.     private array $eventTraces = [];
  53.     /**
  54.      * @internal
  55.      */
  56.     public function __construct(
  57.         private BlockRendererInterface $blockRenderer,
  58.         private BlockContextManagerInterface $blockContextManager,
  59.         private EventDispatcherInterface $eventDispatcher,
  60.         private ?Stopwatch $stopwatch null
  61.     ) {
  62.     }
  63.     /**
  64.      * @param string $media    Unused, only kept to not break existing code
  65.      * @param string $basePath Base path to prepend to the stylesheet urls
  66.      *
  67.      * @return string
  68.      */
  69.     public function includeJavascripts($media$basePath '')
  70.     {
  71.         $html '';
  72.         foreach ($this->assets['js'] as $javascript) {
  73.             $html .= "\n".sprintf('<script src="%s%s" type="text/javascript"></script>'$basePath$javascript);
  74.         }
  75.         return $html;
  76.     }
  77.     /**
  78.      * @param string $media    The css media type to use: all|screen|...
  79.      * @param string $basePath Base path to prepend to the stylesheet urls
  80.      *
  81.      * @return string
  82.      */
  83.     public function includeStylesheets($media$basePath '')
  84.     {
  85.         if (=== \count($this->assets['css'])) {
  86.             return '';
  87.         }
  88.         $html sprintf("<style type='text/css' media='%s'>"$media);
  89.         foreach ($this->assets['css'] as $stylesheet) {
  90.             $html .= "\n".sprintf('@import url(%s%s);'$basePath$stylesheet);
  91.         }
  92.         $html .= "\n</style>";
  93.         return $html;
  94.     }
  95.     /**
  96.      * @param array<string, mixed> $options
  97.      */
  98.     public function renderEvent(string $name, array $options = []): string
  99.     {
  100.         $eventName sprintf('sonata.block.event.%s'$name);
  101.         $event $this->eventDispatcher->dispatch(new BlockEvent($options), $eventName);
  102.         $content '';
  103.         foreach ($event->getBlocks() as $block) {
  104.             $content .= $this->render($block);
  105.         }
  106.         if (null !== $this->stopwatch) {
  107.             $this->eventTraces[uniqid(''true)] = [
  108.                 'template_code' => $name,
  109.                 'event_name' => $eventName,
  110.                 'blocks' => $this->getEventBlocks($event),
  111.                 'listeners' => $this->getEventListeners($eventName),
  112.             ];
  113.         }
  114.         return $content;
  115.     }
  116.     /**
  117.      * Check if a given block type exists.
  118.      *
  119.      * @param string $type Block type to check for
  120.      */
  121.     public function exists(string $type): bool
  122.     {
  123.         return $this->blockContextManager->exists($type);
  124.     }
  125.     /**
  126.      * @param string|array<string, mixed>|BlockInterface $block
  127.      * @param array<string, mixed>                       $options
  128.      */
  129.     public function render(string|array|BlockInterface $block, array $options = []): string
  130.     {
  131.         $blockContext $this->blockContextManager->get($block$options);
  132.         $stats null;
  133.         if (null !== $this->stopwatch) {
  134.             $stats $this->startTracing($blockContext->getBlock());
  135.         }
  136.         $response $this->blockRenderer->render($blockContext);
  137.         if (null !== $stats) {
  138.             $this->stopTracing($blockContext->getBlock(), $stats);
  139.         }
  140.         return (string) $response->getContent();
  141.     }
  142.     /**
  143.      * Returns the rendering traces.
  144.      *
  145.      * @return array<string, mixed>
  146.      */
  147.     public function getTraces(): array
  148.     {
  149.         return ['_events' => $this->eventTraces] + $this->traces;
  150.     }
  151.     /**
  152.      * @param array<string, mixed> $stats
  153.      *
  154.      * @phpstan-param Trace $stats
  155.      */
  156.     private function stopTracing(BlockInterface $block, array $stats): void
  157.     {
  158.         $event $this->traces[$block->getId() ?? ''];
  159.         if (!$event instanceof StopwatchEvent) {
  160.             throw new \InvalidArgumentException(
  161.                 sprintf('The block %s has no stopwatch event to stop.'$block->getId() ?? '')
  162.             );
  163.         }
  164.         $event->stop();
  165.         $this->traces[$block->getId() ?? ''] = [
  166.             'duration' => $event->getDuration(),
  167.             'memory_end' => memory_get_usage(true),
  168.             'memory_peak' => memory_get_peak_usage(true),
  169.         ] + $stats;
  170.     }
  171.     /**
  172.      * @return array<array{string|int, string}>
  173.      */
  174.     private function getEventBlocks(BlockEvent $event): array
  175.     {
  176.         $results = [];
  177.         foreach ($event->getBlocks() as $block) {
  178.             $results[] = [$block->getId() ?? ''$block->getType() ?? ''];
  179.         }
  180.         return $results;
  181.     }
  182.     /**
  183.      * @return string[]
  184.      */
  185.     private function getEventListeners(string $eventName): array
  186.     {
  187.         $results = [];
  188.         if (!$this->eventDispatcher instanceof EventDispatcherComponentInterface) {
  189.             return $results;
  190.         }
  191.         foreach ($this->eventDispatcher->getListeners($eventName) as $listener) {
  192.             if ($listener instanceof \Closure) {
  193.                 $results[] = '{closure}()';
  194.             } elseif (\is_array($listener) && \is_object($listener[0])) {
  195.                 $results[] = $listener[0]::class;
  196.             } elseif (\is_array($listener) && \is_string($listener[0])) {
  197.                 $results[] = $listener[0];
  198.             } else {
  199.                 $results[] = 'Unknown type!';
  200.             }
  201.         }
  202.         return $results;
  203.     }
  204.     /**
  205.      * @return array<string, mixed>
  206.      *
  207.      * @phpstan-return Trace
  208.      */
  209.     private function startTracing(BlockInterface $block): array
  210.     {
  211.         if (null !== $this->stopwatch) {
  212.             $this->traces[$block->getId() ?? ''] = $this->stopwatch->start(
  213.                 sprintf(
  214.                     '%s (id: %s, type: %s)',
  215.                     $block->getName() ?? '',
  216.                     $block->getId() ?? '',
  217.                     $block->getType() ?? ''
  218.                 )
  219.             );
  220.         }
  221.         return [
  222.             'name' => $block->getName() ?? '',
  223.             'type' => $block->getType() ?? '',
  224.             'duration' => false,
  225.             'memory_start' => memory_get_usage(true),
  226.             'memory_end' => false,
  227.             'memory_peak' => false,
  228.             'assets' => [
  229.                 'js' => [],
  230.                 'css' => [],
  231.             ],
  232.         ];
  233.     }
  234. }