TwigRendererEngine.php 7.9 KB

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