TwigRendererEngine.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  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 Symfony\Bridge\Twig\Form;
  11. use Symfony\Component\Form\AbstractRendererEngine;
  12. use Symfony\Component\Form\FormView;
  13. /**
  14. * @author Bernhard Schussek <bschussek@gmail.com>
  15. */
  16. class TwigRendererEngine extends AbstractRendererEngine implements TwigRendererEngineInterface
  17. {
  18. /**
  19. * @var \Twig_Environment
  20. */
  21. private $environment;
  22. /**
  23. * @var \Twig_Template
  24. */
  25. private $template;
  26. /**
  27. * {@inheritdoc}
  28. */
  29. public function setEnvironment(\Twig_Environment $environment)
  30. {
  31. $this->environment = $environment;
  32. }
  33. /**
  34. * {@inheritdoc}
  35. */
  36. public function renderBlock(FormView $view, $resource, $blockName, array $variables = array())
  37. {
  38. $cacheKey = $view->vars[self::CACHE_KEY_VAR];
  39. $context = $this->environment->mergeGlobals($variables);
  40. ob_start();
  41. // By contract,This method can only be called after getting the resource
  42. // (which is passed to the method). Getting a resource for the first time
  43. // (with an empty cache) is guaranteed to invoke loadResourcesFromTheme(),
  44. // where the property $template is initialized.
  45. // We do not call renderBlock here to avoid too many nested level calls
  46. // (XDebug limits the level to 100 by default)
  47. $this->template->displayBlock($blockName, $context, $this->resources[$cacheKey]);
  48. return ob_get_clean();
  49. }
  50. /**
  51. * Loads the cache with the resource for a given block name.
  52. *
  53. * This implementation eagerly loads all blocks of the themes assigned to the given view
  54. * and all of its ancestors views. This is necessary, because Twig receives the
  55. * list of blocks later. At that point, all blocks must already be loaded, for the
  56. * case that the function "block()" is used in the Twig template.
  57. *
  58. * @see getResourceForBlock()
  59. *
  60. * @param string $cacheKey The cache key of the form view.
  61. * @param FormView $view The form view for finding the applying themes.
  62. * @param string $blockName The name of the block to load.
  63. *
  64. * @return Boolean True if the resource could be loaded, false otherwise.
  65. */
  66. protected function loadResourceForBlockName($cacheKey, FormView $view, $blockName)
  67. {
  68. // The caller guarantees that $this->resources[$cacheKey][$block] is
  69. // not set, but it doesn't have to check whether $this->resources[$cacheKey]
  70. // is set. If $this->resources[$cacheKey] is set, all themes for this
  71. // $cacheKey are already loaded (due to the eager population, see doc comment).
  72. if (isset($this->resources[$cacheKey])) {
  73. // As said in the previous, the caller guarantees that
  74. // $this->resources[$cacheKey][$block] is not set. Since the themes are
  75. // already loaded, it can only be a non-existing block.
  76. $this->resources[$cacheKey][$blockName] = false;
  77. return false;
  78. }
  79. // Recursively try to find the block in the themes assigned to $view,
  80. // then of its parent view, then of the parent view of the parent and so on.
  81. // When the root view is reached in this recursion, also the default
  82. // themes are taken into account.
  83. // Check each theme whether it contains the searched block
  84. if (isset($this->themes[$cacheKey])) {
  85. for ($i = count($this->themes[$cacheKey]) - 1; $i >= 0; --$i) {
  86. $this->loadResourcesFromTheme($cacheKey, $this->themes[$cacheKey][$i]);
  87. // CONTINUE LOADING (see doc comment)
  88. }
  89. }
  90. // Check the default themes once we reach the root view without success
  91. if (!$view->parent) {
  92. for ($i = count($this->defaultThemes) - 1; $i >= 0; --$i) {
  93. $this->loadResourcesFromTheme($cacheKey, $this->defaultThemes[$i]);
  94. // CONTINUE LOADING (see doc comment)
  95. }
  96. }
  97. // Proceed with the themes of the parent view
  98. if ($view->parent) {
  99. $parentCacheKey = $view->parent->vars[self::CACHE_KEY_VAR];
  100. if (!isset($this->resources[$parentCacheKey])) {
  101. $this->loadResourceForBlockName($parentCacheKey, $view->parent, $blockName);
  102. }
  103. // EAGER CACHE POPULATION (see doc comment)
  104. foreach ($this->resources[$parentCacheKey] as $nestedBlockName => $resource) {
  105. if (!isset($this->resources[$cacheKey][$nestedBlockName])) {
  106. $this->resources[$cacheKey][$nestedBlockName] = $resource;
  107. }
  108. }
  109. }
  110. // Even though we loaded the themes, it can happen that none of them
  111. // contains the searched block
  112. if (!isset($this->resources[$cacheKey][$blockName])) {
  113. // Cache that we didn't find anything to speed up further accesses
  114. $this->resources[$cacheKey][$blockName] = false;
  115. }
  116. return false !== $this->resources[$cacheKey][$blockName];
  117. }
  118. /**
  119. * Loads the resources for all blocks in a theme.
  120. *
  121. * @param string $cacheKey The cache key for storing the resource.
  122. * @param mixed $theme The theme to load the block from. This parameter
  123. * is passed by reference, because it might be necessary
  124. * to initialize the theme first. Any changes made to
  125. * this variable will be kept and be available upon
  126. * further calls to this method using the same theme.
  127. */
  128. protected function loadResourcesFromTheme($cacheKey, &$theme)
  129. {
  130. if (!$theme instanceof \Twig_Template) {
  131. /* @var \Twig_Template $theme */
  132. $theme = $this->environment->loadTemplate($theme);
  133. }
  134. if (null === $this->template) {
  135. // Store the first \Twig_Template instance that we find so that
  136. // we can call displayBlock() later on. It doesn't matter *which*
  137. // template we use for that, since we pass the used blocks manually
  138. // anyway.
  139. $this->template = $theme;
  140. }
  141. // Use a separate variable for the inheritance traversal, because
  142. // theme is a reference and we don't want to change it.
  143. $currentTheme = $theme;
  144. // The do loop takes care of template inheritance.
  145. // Add blocks from all templates in the inheritance tree, but avoid
  146. // overriding blocks already set.
  147. do {
  148. foreach ($currentTheme->getBlocks() as $block => $blockData) {
  149. if (!isset($this->resources[$cacheKey][$block])) {
  150. // The resource given back is the key to the bucket that
  151. // contains this block.
  152. $this->resources[$cacheKey][$block] = $blockData;
  153. }
  154. }
  155. } while (false !== $currentTheme = $currentTheme->getParent(array()));
  156. }
  157. }