src/Core/Framework/Adapter/Twig/TemplateFinder.php line 135

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework\Adapter\Twig;
  3. use Shopware\Core\Framework\Adapter\Twig\NamespaceHierarchy\NamespaceHierarchyBuilder;
  4. use Shopware\Core\Framework\Log\Package;
  5. use Symfony\Contracts\Service\ResetInterface;
  6. use Twig\Cache\FilesystemCache;
  7. use Twig\Environment;
  8. use Twig\Error\LoaderError;
  9. use Twig\Loader\LoaderInterface;
  10. #[Package('core')]
  11. class TemplateFinder implements TemplateFinderInterfaceResetInterface
  12. {
  13.     /**
  14.      * @var string[]
  15.      */
  16.     private array $namespaceHierarchy = [];
  17.     /**
  18.      * @internal
  19.      */
  20.     public function __construct(
  21.         private readonly Environment $twig,
  22.         private readonly LoaderInterface $loader,
  23.         private readonly string $cacheDir,
  24.         private readonly NamespaceHierarchyBuilder $namespaceHierarchyBuilder
  25.     ) {
  26.     }
  27.     public function getTemplateName(string $template): string
  28.     {
  29.         //remove static template inheritance prefix
  30.         if (mb_strpos($template'@') !== 0) {
  31.             return $template;
  32.         }
  33.         $template explode('/'$template);
  34.         array_shift($template);
  35.         $template implode('/'$template);
  36.         return $template;
  37.     }
  38.     /**
  39.      * {@inheritdoc}
  40.      */
  41.     public function find(string $template$ignoreMissing false, ?string $source null): string
  42.     {
  43.         $templatePath $this->getTemplateName($template);
  44.         $sourcePath $source $this->getTemplateName($source) : null;
  45.         $sourceBundleName $source $this->getSourceBundleName($source) : null;
  46.         $originalTemplate $source null $template;
  47.         $queue $this->getNamespaceHierarchy();
  48.         $modifiedQueue $queue;
  49.         // If we are trying to load the same file as the template, we do are not allowed to search the hierarchy
  50.         // up to the source file as that has already been searched and that would lead to an endless template inheritance.
  51.         if ($sourceBundleName !== null && $sourcePath === $templatePath) {
  52.             $index \array_search($sourceBundleName$modifiedQueuetrue);
  53.             if (\is_int($index)) {
  54.                 $modifiedQueue \array_merge(\array_slice($modifiedQueue$index 1), \array_slice($queue0$index));
  55.             }
  56.         }
  57.         // iterate over all bundles but exclude the originally requested bundle
  58.         // example: if @Storefront/storefront/index.html.twig is requested, all bundles except Storefront will be checked first
  59.         foreach ($modifiedQueue as $prefix) {
  60.             $name '@' $prefix '/' $templatePath;
  61.             // original template is loaded last
  62.             if ($name === $originalTemplate) {
  63.                 continue;
  64.             }
  65.             if (!$this->loader->exists($name)) {
  66.                 continue;
  67.             }
  68.             return $name;
  69.         }
  70.         // Throw a useful error when the template cannot be found
  71.         if ($originalTemplate === null) {
  72.             if ($ignoreMissing === true) {
  73.                 return $templatePath;
  74.             }
  75.             throw new LoaderError(sprintf('Unable to load template "%s". (Looked into: %s)'$templatePathimplode(', 'array_values($modifiedQueue))));
  76.         }
  77.         // if no other bundle extends the requested template, load the original template
  78.         if ($this->loader->exists($originalTemplate)) {
  79.             return $originalTemplate;
  80.         }
  81.         if ($ignoreMissing === true) {
  82.             return $templatePath;
  83.         }
  84.         throw new LoaderError(sprintf('Unable to load template "%s". (Looked into: %s)'$templatePathimplode(', 'array_values($modifiedQueue))));
  85.     }
  86.     public function reset(): void
  87.     {
  88.         $this->namespaceHierarchy = [];
  89.     }
  90.     private function getSourceBundleName(string $source): ?string
  91.     {
  92.         if (mb_strpos($source'@') !== 0) {
  93.             return null;
  94.         }
  95.         $source explode('/'$source);
  96.         $source array_shift($source);
  97.         $source $source ltrim($source'@') : null;
  98.         return $source ?: null;
  99.     }
  100.     /**
  101.      * @return string[]
  102.      */
  103.     private function getNamespaceHierarchy(): array
  104.     {
  105.         if ($this->namespaceHierarchy) {
  106.             return $this->namespaceHierarchy;
  107.         }
  108.         $namespaceHierarchy $this->namespaceHierarchyBuilder->buildHierarchy();
  109.         $this->defineCache($namespaceHierarchy);
  110.         return $this->namespaceHierarchy array_keys($namespaceHierarchy);
  111.     }
  112.     /**
  113.      * @param string[] $queue
  114.      */
  115.     private function defineCache(array $queue): void
  116.     {
  117.         if ($this->twig->getCache(false) instanceof FilesystemCache) {
  118.             $configHash md5((string) json_encode($queue\JSON_THROW_ON_ERROR));
  119.             $fileSystemCache = new ConfigurableFilesystemCache($this->cacheDir);
  120.             $fileSystemCache->setConfigHash($configHash);
  121.             // Set individual twig cache for different configurations
  122.             $this->twig->setCache($fileSystemCache);
  123.         }
  124.     }
  125. }