src/Cms/CoreBundle/Service/SceneRenderer.php line 194

Open in your IDE?
  1. <?php
  2. namespace Cms\CoreBundle\Service;
  3. use ArrayAccess;
  4. use Cms\CoreBundle\Model\Scenes\AbstractScene;
  5. use Cms\WidgetBundle\Service\WidgetDependencyInjector;
  6. use Twig\Environment;
  7. /**
  8.  * Class SceneRenderer
  9.  * @package Cms\CoreBundle\Service
  10.  */
  11. final class SceneRenderer
  12. {
  13.     /**
  14.      * @var Environment
  15.      */
  16.     private Environment $twig;
  17.     /**
  18.      * @var WidgetDependencyInjector
  19.      */
  20.     private WidgetDependencyInjector $widgetDependencyInjector;
  21.     /**
  22.      * @var array|AbstractScene[]
  23.      */
  24.     private array $stack = [];
  25.     /**
  26.      * @var mixed
  27.      */
  28.     private $extras;
  29.     /**
  30.      * @param Environment $twig
  31.      * @param WidgetDependencyInjector $widgetDependencyInjector
  32.      */
  33.     public function __construct(
  34.         Environment $twig,
  35.         WidgetDependencyInjector $widgetDependencyInjector
  36.     )
  37.     {
  38.         $this->twig $twig;
  39.         $this->widgetDependencyInjector $widgetDependencyInjector;
  40.     }
  41.     /**
  42.      * @param string $template
  43.      * @param array $parameters
  44.      * @return string
  45.      */
  46.     public function renderRawFile(string $template, array $parameters = []): string
  47.     {
  48.         return $this->twig->render(
  49.             $template,
  50.             $parameters,
  51.         );
  52.     }
  53.     /**
  54.      * @param string $source
  55.      * @param array $parameters
  56.      * @return string
  57.      */
  58.     public function renderRawString(string $source, array $parameters = []): string
  59.     {
  60.         return $this->twig
  61.             ->createTemplate($source)
  62.             ->render($parameters);
  63.     }
  64.     /**
  65.      * Used to launch a new rendering stack.
  66.      * If we are currently rendering something else, an error is thrown.
  67.      *
  68.      * @param AbstractScene $scene
  69.      * @param mixed $extras
  70.      * @return string
  71.      */
  72.     public function render(AbstractScene $scene$extras null): string
  73.     {
  74.         // this should only be called if we are not already rendering something else
  75.         if ($this->isRendering()) {
  76.             throw new \LogicException();
  77.         }
  78.         // set current globals
  79.         $this->extras $extras;
  80.         // run render logic
  81.         $result $this->doRender($scene);
  82.         // clear globals
  83.         $this->extras null;
  84.         // done
  85.         return $result;
  86.     }
  87.     /**
  88.      * This should be used when scenes need to spin off "child" scenes to help with rendering.
  89.      *
  90.      * @param AbstractScene $scene
  91.      * @return string
  92.      * @throws \Exception
  93.      */
  94.     public function subrender(AbstractScene $scene): string
  95.     {
  96.         // this should only be called if we are already rendering something
  97.         if ($this->isRendering() === false) {
  98.             throw new \LogicException();
  99.         }
  100.         // run render code after forking
  101.         return $this->doRender(
  102.             $this->currentScene()->fork($scene)
  103.         );
  104.     }
  105.     /**
  106.      * Adds a new scene to the stack.
  107.      * Runs checks to help ensure legal state of rendering.
  108.      *
  109.      * @param AbstractScene $scene
  110.      * @throws \Exception
  111.      */
  112.     private function pushScene(AbstractScene $scene): void
  113.     {
  114.         // prevent us from rendering the same scene
  115.         if (in_array($scene$this->stacktrue)) {
  116.             throw new \RuntimeException();
  117.         }
  118.         // push onto front of queue
  119.         array_unshift($this->stack$scene);
  120.     }
  121.     /**
  122.      * Removes the currently processing scene from the stack.
  123.      * Runs checks to help ensure legal state of rendering.
  124.      *
  125.      * @param AbstractScene $scene
  126.      * @throws \Exception
  127.      */
  128.     private function popScene(AbstractScene $scene): void
  129.     {
  130.         // if there are no scenes, this was called incorrectly
  131.         if (count($this->stack) === 0) {
  132.             throw new \RuntimeException();
  133.         }
  134.         // make sure we are in fact the first thing in the queue
  135.         if ($this->stack[0] !== $scene) {
  136.             throw new \RuntimeException();
  137.         }
  138.         // can pop us off the front
  139.         array_shift($this->stack);
  140.     }
  141.     /**
  142.      * Ensures that a template is rendered properly for a scene.
  143.      *
  144.      * @param AbstractScene $scene
  145.      * @return string
  146.      */
  147.     private function obtainTemplate(AbstractScene $scene): string
  148.     {
  149.         // try to generate
  150.         $template $scene->generateTemplate(
  151.             $this->widgetDependencyInjector->getContainer($this),
  152.             $this->extras,
  153.         );
  154.         // make sure template is valid
  155.         if ( ! is_string($template) || $template === '') {
  156.             throw new \RuntimeException();
  157.         }
  158.         return $template;
  159.     }
  160.     /**
  161.      * Ensures that parameters are generated properly for a scene.
  162.      *
  163.      * @param AbstractScene $scene
  164.      * @return iterable
  165.      */
  166.     private function obtainParameters(AbstractScene $scene): iterable
  167.     {
  168.         // try to generate
  169.         $parameters $scene->generateParameters(
  170.             $this->widgetDependencyInjector->getContainer($this),
  171.             $this->extras,
  172.         );
  173.         // init params if not given
  174.         if ($parameters === null) {
  175.             $parameters = [];
  176.         }
  177.         // make sure params are valid
  178.         if ( ! is_array($parameters) && ! $parameters instanceof ArrayAccess) {
  179.             throw new \RuntimeException();
  180.         }
  181.         return $parameters;
  182.     }
  183.     /**
  184.      * Responsible for rendering out a thing based on the current context stack.
  185.      *
  186.      * @param AbstractScene $scene
  187.      * @return string
  188.      */
  189.     private function doRender(AbstractScene $scene): string
  190.     {
  191.         // push us onto the stack
  192.         $this->pushScene($scene);
  193.         // get things we need
  194.         $template $this->obtainTemplate($scene);
  195.         $parameters $this->obtainParameters($scene);
  196.         // actually do the twig rendering
  197.         if ($scene->isFileBased()) {
  198.             $result $this->renderRawFile($template, (array) $parameters);
  199.         } else {
  200.             $result $this->renderRawString($template, (array) $parameters);
  201.         }
  202.         // we are done, can pop us off the stack
  203.         $this->popScene($scene);
  204.         // done, send back content that was rendered
  205.         return $result;
  206.     }
  207.     /**
  208.      * Get the current item in the rendering stack.
  209.      *
  210.      * @return AbstractScene|null
  211.      */
  212.     public function currentScene(): ?AbstractScene
  213.     {
  214.         // if we are not rendering, nothing to report
  215.         // pull from the front of the queue
  216.         return $this->isRendering() ? $this->stack[0] : null;
  217.     }
  218.     /**
  219.      * Obtain the rendering stack.
  220.      *
  221.      * @return array|AbstractScene[]
  222.      */
  223.     public function currentStack(): array
  224.     {
  225.         return $this->stack;
  226.     }
  227.     /**
  228.      * Determines the first context that started the stack, if any.
  229.      *
  230.      * @return AbstractScene|null
  231.      */
  232.     public function initialScene(): ?AbstractScene
  233.     {
  234.         // there is nothing to report if we are not rendering
  235.         // need to pull the last off of the queue
  236.         return $this->isRendering() ? $this->stack[count($this->stack) - 1] : null;
  237.     }
  238.     /**
  239.      * Determines whether we are currently in a rendering stack.
  240.      *
  241.      * @return bool
  242.      */
  243.     public function isRendering(): bool
  244.     {
  245.         return (count($this->stack) !== 0);
  246.     }
  247. }