vendor/shopware/core/Framework/Adapter/Twig/EntityTemplateLoader.php line 58

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework\Adapter\Twig;
  3. use Doctrine\DBAL\Connection;
  4. use Shopware\Core\DevOps\Environment\EnvironmentHelper;
  5. use Shopware\Core\Framework\DependencyInjection\CompilerPass\TwigLoaderConfigCompilerPass;
  6. use Shopware\Core\Framework\Feature;
  7. use Shopware\Core\Framework\Log\Package;
  8. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  9. use Symfony\Contracts\Service\ResetInterface;
  10. use Twig\Error\LoaderError;
  11. use Twig\Loader\LoaderInterface;
  12. use Twig\Source;
  13. /**
  14. * @deprecated tag:v6.5.0 - reason:becomes-internal - EventSubscribers will become internal in v6.5.0
  15. */
  16. #[Package('core')]
  17. class EntityTemplateLoader implements LoaderInterface, EventSubscriberInterface, ResetInterface
  18. {
  19. /**
  20. * @var array<string, array<string, array{template: string, updatedAt: \DateTimeInterface|null}|null>>
  21. */
  22. private array $databaseTemplateCache = [];
  23. private Connection $connection;
  24. private string $environment;
  25. /**
  26. * @internal
  27. */
  28. public function __construct(Connection $connection, string $environment)
  29. {
  30. $this->connection = $connection;
  31. $this->environment = $environment;
  32. }
  33. public static function getSubscribedEvents(): array
  34. {
  35. return ['app_template.written' => 'reset'];
  36. }
  37. /**
  38. * @deprecated tag:v6.5.0 will be removed, use `reset()` instead
  39. */
  40. public function clearInternalCache(): void
  41. {
  42. Feature::triggerDeprecationOrThrow(
  43. 'v6.5.0.0',
  44. Feature::deprecatedMethodMessage(__CLASS__, __METHOD__, 'v6.5.0.0', 'reset()')
  45. );
  46. $this->reset();
  47. }
  48. public function reset(): void
  49. {
  50. $this->databaseTemplateCache = [];
  51. }
  52. public function getSourceContext(string $name): Source
  53. {
  54. $template = $this->findDatabaseTemplate($name);
  55. if (!$template) {
  56. throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
  57. }
  58. return new Source($template['template'], $name);
  59. }
  60. public function getCacheKey(string $name): string
  61. {
  62. return $name;
  63. }
  64. public function isFresh(string $name, int $time): bool
  65. {
  66. $template = $this->findDatabaseTemplate($name);
  67. if (!$template) {
  68. return false;
  69. }
  70. return $template['updatedAt'] === null || $template['updatedAt']->getTimestamp() < $time;
  71. }
  72. /**
  73. * @return bool
  74. */
  75. public function exists(string $name)
  76. {
  77. $template = $this->findDatabaseTemplate($name);
  78. if (!$template) {
  79. return false;
  80. }
  81. return true;
  82. }
  83. /**
  84. * @return array{template: string, updatedAt: \DateTimeInterface|null}|null
  85. */
  86. private function findDatabaseTemplate(string $name): ?array
  87. {
  88. if (EnvironmentHelper::getVariable('DISABLE_EXTENSIONS', false)) {
  89. return null;
  90. }
  91. /*
  92. * In dev env app templates are directly loaded over the filesystem
  93. * @see TwigLoaderConfigCompilerPass::addAppTemplatePaths()
  94. */
  95. if ($this->environment === 'dev') {
  96. return null;
  97. }
  98. $templateName = $this->splitTemplateName($name);
  99. $namespace = $templateName['namespace'];
  100. $path = $templateName['path'];
  101. if (empty($this->databaseTemplateCache)) {
  102. /** @var array<array{path: string, template: string, updatedAt: string|null, namespace: string}> $templates */
  103. $templates = $this->connection->fetchAllAssociative('
  104. SELECT
  105. `app_template`.`path` AS `path`,
  106. `app_template`.`template` AS `template`,
  107. `app_template`.`updated_at` AS `updatedAt`,
  108. `app`.`name` AS `namespace`
  109. FROM `app_template`
  110. INNER JOIN `app` ON `app_template`.`app_id` = `app`.`id`
  111. WHERE `app_template`.`active` = 1 AND `app`.`active` = 1
  112. ');
  113. foreach ($templates as $template) {
  114. $this->databaseTemplateCache[$template['path']][$template['namespace']] = [
  115. 'template' => $template['template'],
  116. 'updatedAt' => $template['updatedAt'] ? new \DateTimeImmutable($template['updatedAt']) : null,
  117. ];
  118. }
  119. }
  120. if (\array_key_exists($path, $this->databaseTemplateCache) && \array_key_exists($namespace, $this->databaseTemplateCache[$path])) {
  121. return $this->databaseTemplateCache[$path][$namespace];
  122. }
  123. /** @deprecated tag:v6.5.0 - only for intermediate backwards compatibility */
  124. if (
  125. \array_key_exists('../' . $path, $this->databaseTemplateCache)
  126. && \array_key_exists($namespace, $this->databaseTemplateCache['../' . $path])
  127. ) {
  128. return $this->databaseTemplateCache['../' . $path][$namespace];
  129. }
  130. // we have already loaded all DB templates
  131. // if the namespace is not included return null
  132. return $this->databaseTemplateCache[$path][$namespace] = null;
  133. }
  134. /**
  135. * @return array{namespace: string, path: string}
  136. */
  137. private function splitTemplateName(string $template): array
  138. {
  139. // remove static template inheritance prefix
  140. if (mb_strpos($template, '@') !== 0) {
  141. return ['path' => $template, 'namespace' => ''];
  142. }
  143. // remove "@"
  144. $template = mb_substr($template, 1);
  145. $template = explode('/', $template);
  146. $namespace = array_shift($template);
  147. $template = implode('/', $template);
  148. return ['path' => $template, 'namespace' => $namespace];
  149. }
  150. }