<?php declare(strict_types=1);
namespace Shopware\Core\Framework\Script\Execution;
use Doctrine\DBAL\Connection;
use Shopware\Core\DevOps\Environment\EnvironmentHelper;
use Shopware\Core\Framework\Adapter\Cache\CacheCompressor;
use Shopware\Core\Framework\App\Lifecycle\Persister\ScriptPersister;
use Shopware\Core\Framework\DataAbstractionLayer\Doctrine\FetchModeHelper;
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Twig\Cache\FilesystemCache;
/**
* @internal only for use by the app-system
*/
class ScriptLoader implements EventSubscriberInterface
{
public const CACHE_KEY = 'shopware-app-scripts';
private Connection $connection;
private string $cacheDir;
private ScriptPersister $scriptPersister;
private bool $debug;
private TagAwareAdapterInterface $cache;
public function __construct(Connection $connection, ScriptPersister $scriptPersister, TagAwareAdapterInterface $cache, string $cacheDir, bool $debug)
{
$this->connection = $connection;
$this->cacheDir = $cacheDir . '/twig/scripts';
$this->scriptPersister = $scriptPersister;
$this->debug = $debug;
$this->cache = $cache;
}
public static function getSubscribedEvents(): array
{
return ['script.written' => 'invalidateCache'];
}
/**
* @return Script[]
*/
public function get(string $hook): array
{
$cacheItem = $this->cache->getItem(self::CACHE_KEY);
if ($cacheItem->isHit() && $cacheItem->get() && !$this->debug) {
return CacheCompressor::uncompress($cacheItem)[$hook] ?? [];
}
$scripts = $this->load();
$cacheItem = CacheCompressor::compress($cacheItem, $scripts);
$this->cache->save($cacheItem);
return $scripts[$hook] ?? [];
}
public function invalidateCache(): void
{
$this->cache->deleteItem(self::CACHE_KEY);
}
private function load(): array
{
if ($this->debug) {
$this->scriptPersister->refresh();
}
$scripts = $this->connection->fetchAllAssociative("
SELECT LOWER(HEX(`script`.`app_id`)) as `app_id`,
`script`.`name` AS scriptName,
`script`.`script` AS script,
`script`.`hook` AS hook,
IFNULL(`script`.`updated_at`, `script`.`created_at`) AS lastModified,
`app`.`name` AS appName,
LOWER(HEX(`app`.`integration_id`)) AS integrationId,
`app`.`version` AS appVersion
FROM `script`
LEFT JOIN `app` ON `script`.`app_id` = `app`.`id`
WHERE `script`.`hook` != 'include'
ORDER BY `app`.`created_at`, `app`.`id`, `script`.`name`
");
$includes = $this->connection->fetchAllAssociative("
SELECT LOWER(HEX(`script`.`app_id`)) as `app_id`,
`script`.`name` AS name,
`script`.`script` AS script,
`app`.`name` AS appName,
LOWER(HEX(`app`.`integration_id`)) AS integrationId,
IFNULL(`script`.`updated_at`, `script`.`created_at`) AS lastModified
FROM `script`
LEFT JOIN `app` ON `script`.`app_id` = `app`.`id`
WHERE `script`.`hook` = 'include'
ORDER BY `app`.`created_at`, `app`.`id`, `script`.`name`
");
$allIncludes = FetchModeHelper::group($includes);
$executableScripts = [];
/** @var array $script */
foreach ($scripts as $script) {
$appId = $script['app_id'];
$includes = $allIncludes[$appId] ?? [];
$dates = array_merge([$script['lastModified']], array_column($includes, 'lastModified'));
/** @var \DateTimeInterface $lastModified */
$lastModified = new \DateTimeImmutable(max($dates));
/** @var string $cachePrefix */
$cachePrefix = $script['appName'] ? md5($script['appName'] . $script['appVersion']) : EnvironmentHelper::getVariable('INSTANCE_ID', '');
$includes = array_map(function (array $script) use ($appId) {
$script['app_id'] = $appId;
return new Script(
$script['name'],
$script['script'],
new \DateTimeImmutable($script['lastModified']),
$this->getAppInfo($script)
);
}, $includes);
$options = [];
if (!$this->debug) {
$options['cache'] = new FilesystemCache($this->cacheDir . '/' . $cachePrefix);
} else {
$options['debug'] = true;
}
$executableScripts[$script['hook']][] = new Script(
$script['scriptName'],
$script['script'],
$lastModified,
$this->getAppInfo($script),
$options,
$includes
);
}
return $executableScripts;
}
private function getAppInfo(array $script): ?ScriptAppInformation
{
if (!$script['app_id'] || !$script['appName'] || !$script['integrationId']) {
return null;
}
return new ScriptAppInformation(
$script['app_id'],
$script['appName'],
$script['integrationId']
);
}
}