<?php declare(strict_types=1);
namespace Shopware\Core\Framework\Adapter\Twig;
use Doctrine\DBAL\Connection;
use Shopware\Core\DevOps\Environment\EnvironmentHelper;
use Shopware\Core\Framework\DependencyInjection\CompilerPass\TwigLoaderConfigCompilerPass;
use Shopware\Core\Framework\Feature;
use Shopware\Core\Framework\Log\Package;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\Service\ResetInterface;
use Twig\Error\LoaderError;
use Twig\Loader\LoaderInterface;
use Twig\Source;
/**
* @deprecated tag:v6.5.0 - reason:becomes-internal - EventSubscribers will become internal in v6.5.0
*/
#[Package('core')]
class EntityTemplateLoader implements LoaderInterface, EventSubscriberInterface, ResetInterface
{
/**
* @var array<string, array<string, array{template: string, updatedAt: \DateTimeInterface|null}|null>>
*/
private array $databaseTemplateCache = [];
private Connection $connection;
private string $environment;
/**
* @internal
*/
public function __construct(Connection $connection, string $environment)
{
$this->connection = $connection;
$this->environment = $environment;
}
public static function getSubscribedEvents(): array
{
return ['app_template.written' => 'reset'];
}
/**
* @deprecated tag:v6.5.0 will be removed, use `reset()` instead
*/
public function clearInternalCache(): void
{
Feature::triggerDeprecationOrThrow(
'v6.5.0.0',
Feature::deprecatedMethodMessage(__CLASS__, __METHOD__, 'v6.5.0.0', 'reset()')
);
$this->reset();
}
public function reset(): void
{
$this->databaseTemplateCache = [];
}
public function getSourceContext(string $name): Source
{
$template = $this->findDatabaseTemplate($name);
if (!$template) {
throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
}
return new Source($template['template'], $name);
}
public function getCacheKey(string $name): string
{
return $name;
}
public function isFresh(string $name, int $time): bool
{
$template = $this->findDatabaseTemplate($name);
if (!$template) {
return false;
}
return $template['updatedAt'] === null || $template['updatedAt']->getTimestamp() < $time;
}
/**
* @return bool
*/
public function exists(string $name)
{
$template = $this->findDatabaseTemplate($name);
if (!$template) {
return false;
}
return true;
}
/**
* @return array{template: string, updatedAt: \DateTimeInterface|null}|null
*/
private function findDatabaseTemplate(string $name): ?array
{
if (EnvironmentHelper::getVariable('DISABLE_EXTENSIONS', false)) {
return null;
}
/*
* In dev env app templates are directly loaded over the filesystem
* @see TwigLoaderConfigCompilerPass::addAppTemplatePaths()
*/
if ($this->environment === 'dev') {
return null;
}
$templateName = $this->splitTemplateName($name);
$namespace = $templateName['namespace'];
$path = $templateName['path'];
if (empty($this->databaseTemplateCache)) {
/** @var array<array{path: string, template: string, updatedAt: string|null, namespace: string}> $templates */
$templates = $this->connection->fetchAllAssociative('
SELECT
`app_template`.`path` AS `path`,
`app_template`.`template` AS `template`,
`app_template`.`updated_at` AS `updatedAt`,
`app`.`name` AS `namespace`
FROM `app_template`
INNER JOIN `app` ON `app_template`.`app_id` = `app`.`id`
WHERE `app_template`.`active` = 1 AND `app`.`active` = 1
');
foreach ($templates as $template) {
$this->databaseTemplateCache[$template['path']][$template['namespace']] = [
'template' => $template['template'],
'updatedAt' => $template['updatedAt'] ? new \DateTimeImmutable($template['updatedAt']) : null,
];
}
}
if (\array_key_exists($path, $this->databaseTemplateCache) && \array_key_exists($namespace, $this->databaseTemplateCache[$path])) {
return $this->databaseTemplateCache[$path][$namespace];
}
/** @deprecated tag:v6.5.0 - only for intermediate backwards compatibility */
if (
\array_key_exists('../' . $path, $this->databaseTemplateCache)
&& \array_key_exists($namespace, $this->databaseTemplateCache['../' . $path])
) {
return $this->databaseTemplateCache['../' . $path][$namespace];
}
// we have already loaded all DB templates
// if the namespace is not included return null
return $this->databaseTemplateCache[$path][$namespace] = null;
}
/**
* @return array{namespace: string, path: string}
*/
private function splitTemplateName(string $template): array
{
// remove static template inheritance prefix
if (mb_strpos($template, '@') !== 0) {
return ['path' => $template, 'namespace' => ''];
}
// remove "@"
$template = mb_substr($template, 1);
$template = explode('/', $template);
$namespace = array_shift($template);
$template = implode('/', $template);
return ['path' => $template, 'namespace' => $namespace];
}
}