vendor/shopware/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 TemplateFinderInterface, ResetInterface
  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, $modifiedQueue, true);
  53.             if (\is_int($index)) {
  54.                 $modifiedQueue = \array_merge(\array_slice($modifiedQueue, $index + 1), \array_slice($queue, 0, $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)', $templatePath, implode(', ', 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)', $templatePath, implode(', ', 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. }