text: <?php
  
  /*
   * This file is part of the Symfony package.
   *
   * (c) Fabien Potencier <fabien@symfony.com>
   *
   * For the full copyright and license information, please view the LICENSE
   * file that was distributed with this source code.
   */
  
  namespace Symfony\Bridge\Twig\Form;
  
  use Symfony\Component\Form\AbstractRendererEngine;
  use Symfony\Component\Form\FormView;
  
  /**
   * @author Bernhard Schussek <bschussek@gmail.com>
   */
  class TwigRendererEngine extends AbstractRendererEngine implements TwigRendererEngineInterface
  {
      /**
       * @var \Twig_Environment
       */
      private $environment;
  
      /**
       * @var \Twig_Template
       */
      private $template;
  
      /**
       * {@inheritdoc}
       */
      public function setEnvironment(\Twig_Environment $environment)
      {
          $this->environment = $environment;
      }
  
      /**
       * {@inheritdoc}
       */
      public function renderBlock(FormView $view, $resource, $blockName, array $variables = array())
      {
          $cacheKey = $view->vars[self::CACHE_KEY_VAR];
  
          $context = $this->environment->mergeGlobals($variables);
  
          ob_start();
  
          // By contract,This method can only be called after getting the resource
          // (which is passed to the method). Getting a resource for the first time
          // (with an empty cache) is guaranteed to invoke loadResourcesFromTheme(),
          // where the property $template is initialized.
  
          // We do not call renderBlock here to avoid too many nested level calls
          // (XDebug limits the level to 100 by default)
          $this->template->displayBlock($blockName, $context, $this->resources[$cacheKey]);
  
          return ob_get_clean();
      }
  
      /**
       * Loads the cache with the resource for a given block name.
       *
       * This implementation eagerly loads all blocks of the themes assigned to the given view
       * and all of its ancestors views. This is necessary, because Twig receives the
       * list of blocks later. At that point, all blocks must already be loaded, for the
       * case that the function "block()" is used in the Twig template.
       *
       * @see getResourceForBlock()
       *
       * @param string   $cacheKey  The cache key of the form view.
       * @param FormView $view      The form view for finding the applying themes.
       * @param string   $blockName The name of the block to load.
       *
       * @return Boolean True if the resource could be loaded, false otherwise.
       */
      protected function loadResourceForBlockName($cacheKey, FormView $view, $blockName)
      {
          // The caller guarantees that $this->resources[$cacheKey][$block] is
          // not set, but it doesn't have to check whether $this->resources[$cacheKey]
          // is set. If $this->resources[$cacheKey] is set, all themes for this
          // $cacheKey are already loaded (due to the eager population, see doc comment).
          if (isset($this->resources[$cacheKey])) {
              // As said in the previous, the caller guarantees that
              // $this->resources[$cacheKey][$block] is not set. Since the themes are
              // already loaded, it can only be a non-existing block.
              $this->resources[$cacheKey][$blockName] = false;
  
              return false;
          }
  
          // Recursively try to find the block in the themes assigned to $view,
          // then of its parent view, then of the parent view of the parent and so on.
          // When the root view is reached in this recursion, also the default
          // themes are taken into account.
  
          // Check each theme whether it contains the searched block
          if (isset($this->themes[$cacheKey])) {
              for ($i = count($this->themes[$cacheKey]) - 1; $i >= 0; --$i) {
                  $this->loadResourcesFromTheme($cacheKey, $this->themes[$cacheKey][$i]);
                  // CONTINUE LOADING (see doc comment)
              }
          }
  
          // Check the default themes once we reach the root view without success
          if (!$view->parent) {
              for ($i = count($this->defaultThemes) - 1; $i >= 0; --$i) {
                  $this->loadResourcesFromTheme($cacheKey, $this->defaultThemes[$i]);
                  // CONTINUE LOADING (see doc comment)
              }
          }
  
          // Proceed with the themes of the parent view
          if ($view->parent) {
              $parentCacheKey = $view->parent->vars[self::CACHE_KEY_VAR];
  
              if (!isset($this->resources[$parentCacheKey])) {
                  $this->loadResourceForBlockName($parentCacheKey, $view->parent, $blockName);
              }
  
              // EAGER CACHE POPULATION (see doc comment)
              foreach ($this->resources[$parentCacheKey] as $nestedBlockName => $resource) {
                  if (!isset($this->resources[$cacheKey][$nestedBlockName])) {
                      $this->resources[$cacheKey][$nestedBlockName] = $resource;
                  }
              }
          }
  
          // Even though we loaded the themes, it can happen that none of them
          // contains the searched block
          if (!isset($this->resources[$cacheKey][$blockName])) {
              // Cache that we didn't find anything to speed up further accesses
              $this->resources[$cacheKey][$blockName] = false;
          }
  
          return false !== $this->resources[$cacheKey][$blockName];
      }
  
      /**
       * Loads the resources for all blocks in a theme.
       *
       * @param string $cacheKey The cache key for storing the resource.
       * @param mixed  $theme    The theme to load the block from. This parameter
       *                         is passed by reference, because it might be necessary
       *                         to initialize the theme first. Any changes made to
       *                         this variable will be kept and be available upon
       *                         further calls to this method using the same theme.
       */
      protected function loadResourcesFromTheme($cacheKey, &$theme)
      {
          if (!$theme instanceof \Twig_Template) {
              /* @var \Twig_Template $theme */
              $theme = $this->environment->loadTemplate($theme);
          }
  
          if (null === $this->template) {
              // Store the first \Twig_Template instance that we find so that
              // we can call displayBlock() later on. It doesn't matter *which*
              // template we use for that, since we pass the used blocks manually
              // anyway.
              $this->template = $theme;
          }
  
          // Use a separate variable for the inheritance traversal, because
          // theme is a reference and we don't want to change it.
          $currentTheme = $theme;
  
          // The do loop takes care of template inheritance.
          // Add blocks from all templates in the inheritance tree, but avoid
          // overriding blocks already set.
          do {
              foreach ($currentTheme->getBlocks() as $block => $blockData) {
                  if (!isset($this->resources[$cacheKey][$block])) {
                      // The resource given back is the key to the bucket that
                      // contains this block.
                      $this->resources[$cacheKey][$block] = $blockData;
                  }
              }
          } while (false !== $currentTheme = $currentTheme->getParent(array()));
      }
  }
  