TwigRendererEngine.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  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
  19. {
  20. /**
  21. * @var Environment
  22. */
  23. private $environment;
  24. /**
  25. * @var Template
  26. */
  27. private $template;
  28. public function __construct(array $defaultThemes, Environment $environment)
  29. {
  30. parent::__construct($defaultThemes);
  31. $this->environment = $environment;
  32. }
  33. /**
  34. * {@inheritdoc}
  35. */
  36. public function renderBlock(FormView $view, $resource, string $blockName, array $variables = [])
  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. * @return bool True if the resource could be loaded, false otherwise
  61. */
  62. protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName)
  63. {
  64. // The caller guarantees that $this->resources[$cacheKey][$block] is
  65. // not set, but it doesn't have to check whether $this->resources[$cacheKey]
  66. // is set. If $this->resources[$cacheKey] is set, all themes for this
  67. // $cacheKey are already loaded (due to the eager population, see doc comment).
  68. if (isset($this->resources[$cacheKey])) {
  69. // As said in the previous, the caller guarantees that
  70. // $this->resources[$cacheKey][$block] is not set. Since the themes are
  71. // already loaded, it can only be a non-existing block.
  72. $this->resources[$cacheKey][$blockName] = false;
  73. return false;
  74. }
  75. // Recursively try to find the block in the themes assigned to $view,
  76. // then of its parent view, then of the parent view of the parent and so on.
  77. // When the root view is reached in this recursion, also the default
  78. // themes are taken into account.
  79. // Check each theme whether it contains the searched block
  80. if (isset($this->themes[$cacheKey])) {
  81. for ($i = \count($this->themes[$cacheKey]) - 1; $i >= 0; --$i) {
  82. $this->loadResourcesFromTheme($cacheKey, $this->themes[$cacheKey][$i]);
  83. // CONTINUE LOADING (see doc comment)
  84. }
  85. }
  86. // Check the default themes once we reach the root view without success
  87. if (!$view->parent) {
  88. if (!isset($this->useDefaultThemes[$cacheKey]) || $this->useDefaultThemes[$cacheKey]) {
  89. for ($i = \count($this->defaultThemes) - 1; $i >= 0; --$i) {
  90. $this->loadResourcesFromTheme($cacheKey, $this->defaultThemes[$i]);
  91. // CONTINUE LOADING (see doc comment)
  92. }
  93. }
  94. }
  95. // Proceed with the themes of the parent view
  96. if ($view->parent) {
  97. $parentCacheKey = $view->parent->vars[self::CACHE_KEY_VAR];
  98. if (!isset($this->resources[$parentCacheKey])) {
  99. $this->loadResourceForBlockName($parentCacheKey, $view->parent, $blockName);
  100. }
  101. // EAGER CACHE POPULATION (see doc comment)
  102. foreach ($this->resources[$parentCacheKey] as $nestedBlockName => $resource) {
  103. if (!isset($this->resources[$cacheKey][$nestedBlockName])) {
  104. $this->resources[$cacheKey][$nestedBlockName] = $resource;
  105. }
  106. }
  107. }
  108. // Even though we loaded the themes, it can happen that none of them
  109. // contains the searched block
  110. if (!isset($this->resources[$cacheKey][$blockName])) {
  111. // Cache that we didn't find anything to speed up further accesses
  112. $this->resources[$cacheKey][$blockName] = false;
  113. }
  114. return false !== $this->resources[$cacheKey][$blockName];
  115. }
  116. /**
  117. * Loads the resources for all blocks in a theme.
  118. *
  119. * @param mixed $theme The theme to load the block from. This parameter
  120. * is passed by reference, because it might be necessary
  121. * to initialize the theme first. Any changes made to
  122. * this variable will be kept and be available upon
  123. * further calls to this method using the same theme.
  124. */
  125. protected function loadResourcesFromTheme(string $cacheKey, &$theme)
  126. {
  127. if (!$theme instanceof Template) {
  128. /* @var Template $theme */
  129. $theme = $this->environment->load($theme)->unwrap();
  130. }
  131. if (null === $this->template) {
  132. // Store the first Template instance that we find so that
  133. // we can call displayBlock() later on. It doesn't matter *which*
  134. // template we use for that, since we pass the used blocks manually
  135. // anyway.
  136. $this->template = $theme;
  137. }
  138. // Use a separate variable for the inheritance traversal, because
  139. // theme is a reference and we don't want to change it.
  140. $currentTheme = $theme;
  141. $context = $this->environment->mergeGlobals([]);
  142. // The do loop takes care of template inheritance.
  143. // Add blocks from all templates in the inheritance tree, but avoid
  144. // overriding blocks already set.
  145. do {
  146. foreach ($currentTheme->getBlocks() as $block => $blockData) {
  147. if (!isset($this->resources[$cacheKey][$block])) {
  148. // The resource given back is the key to the bucket that
  149. // contains this block.
  150. $this->resources[$cacheKey][$block] = $blockData;
  151. }
  152. }
  153. } while (false !== $currentTheme = $currentTheme->getParent($context));
  154. }
  155. }