src/Core/Kernel.php line 173

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core;
  3. use Doctrine\DBAL\Connection;
  4. use Shopware\Core\DevOps\Environment\EnvironmentHelper;
  5. use Shopware\Core\Framework\Adapter\Database\MySQLFactory;
  6. use Shopware\Core\Framework\Api\Controller\FallbackController;
  7. use Shopware\Core\Framework\Log\Package;
  8. use Shopware\Core\Framework\Plugin\KernelPluginLoader\KernelPluginLoader;
  9. use Shopware\Core\Framework\Util\VersionParser;
  10. use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
  11. use Symfony\Component\Config\ConfigCache;
  12. use Symfony\Component\Config\Loader\LoaderInterface;
  13. use Symfony\Component\DependencyInjection\ContainerBuilder;
  14. use Symfony\Component\HttpKernel\Bundle\Bundle;
  15. use Symfony\Component\HttpKernel\Bundle\BundleInterface;
  16. use Symfony\Component\HttpKernel\Kernel as HttpKernel;
  17. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
  18. use Symfony\Component\Routing\Route;
  19. #[Package('core')]
  20. class Kernel extends HttpKernel
  21. {
  22.     use MicroKernelTrait;
  23.     final public const CONFIG_EXTS '.{php,xml,yaml,yml}';
  24.     /**
  25.      * @var string Fallback version if nothing is provided via kernel constructor
  26.      */
  27.     final public const SHOPWARE_FALLBACK_VERSION '6.5.9999999.9999999-dev';
  28.     /**
  29.      * @var Connection|null
  30.      */
  31.     protected static $connection;
  32.     /**
  33.      * @var KernelPluginLoader
  34.      */
  35.     protected $pluginLoader;
  36.     /**
  37.      * @var string
  38.      */
  39.     protected $shopwareVersion;
  40.     /**
  41.      * @var string|null
  42.      */
  43.     protected $shopwareVersionRevision;
  44.     /**
  45.      * @var string|null
  46.      */
  47.     protected $projectDir;
  48.     private bool $rebooting false;
  49.     /**
  50.      * {@inheritdoc}
  51.      */
  52.     public function __construct(
  53.         string $environment,
  54.         bool $debug,
  55.         KernelPluginLoader $pluginLoader,
  56.         private string $cacheId,
  57.         ?string $version self::SHOPWARE_FALLBACK_VERSION,
  58.         ?Connection $connection null,
  59.         ?string $projectDir null
  60.     ) {
  61.         date_default_timezone_set('UTC');
  62.         parent::__construct($environment$debug);
  63.         self::$connection $connection;
  64.         $this->pluginLoader $pluginLoader;
  65.         $version VersionParser::parseShopwareVersion($version);
  66.         $this->shopwareVersion $version['version'];
  67.         $this->shopwareVersionRevision $version['revision'];
  68.         $this->projectDir $projectDir;
  69.     }
  70.     /**
  71.      * @return iterable<BundleInterface>
  72.      */
  73.     public function registerBundles(): iterable
  74.     {
  75.         /** @var array<class-string<Bundle>, array<string, bool>> $bundles */
  76.         $bundles = require $this->getProjectDir() . '/config/bundles.php';
  77.         $instanciatedBundleNames = [];
  78.         foreach ($bundles as $class => $envs) {
  79.             if (isset($envs['all']) || isset($envs[$this->environment])) {
  80.                 $bundle = new $class();
  81.                 $instanciatedBundleNames[] = $bundle->getName();
  82.                 yield $bundle;
  83.             }
  84.         }
  85.         yield from $this->pluginLoader->getBundles($this->getKernelParameters(), $instanciatedBundleNames);
  86.     }
  87.     public function getProjectDir(): string
  88.     {
  89.         if ($this->projectDir === null) {
  90.             if ($dir $_ENV['PROJECT_ROOT'] ?? $_SERVER['PROJECT_ROOT'] ?? false) {
  91.                 return $this->projectDir $dir;
  92.             }
  93.             $r = new \ReflectionObject($this);
  94.             $dir = (string) $r->getFileName();
  95.             if (!file_exists($dir)) {
  96.                 throw new \LogicException(sprintf('Cannot auto-detect project dir for kernel of class "%s".'$r->name));
  97.             }
  98.             $dir $rootDir \dirname($dir);
  99.             while (!file_exists($dir '/vendor')) {
  100.                 if ($dir === \dirname($dir)) {
  101.                     return $this->projectDir $rootDir;
  102.                 }
  103.                 $dir \dirname($dir);
  104.             }
  105.             $this->projectDir $dir;
  106.         }
  107.         return $this->projectDir;
  108.     }
  109.     public function boot(): void
  110.     {
  111.         if ($this->booted === true) {
  112.             if ($this->debug) {
  113.                 $this->startTime microtime(true);
  114.             }
  115.             return;
  116.         }
  117.         if ($this->debug) {
  118.             $this->startTime microtime(true);
  119.         }
  120.         if ($this->debug && !EnvironmentHelper::hasVariable('SHELL_VERBOSITY')) {
  121.             putenv('SHELL_VERBOSITY=3');
  122.             $_ENV['SHELL_VERBOSITY'] = 3;
  123.             $_SERVER['SHELL_VERBOSITY'] = 3;
  124.         }
  125.         try {
  126.             $this->pluginLoader->initializePlugins($this->getProjectDir());
  127.         } catch (\Throwable $e) {
  128.             if (\defined('\STDERR')) {
  129.                 fwrite(\STDERR'Warning: Failed to load plugins. Message: ' $e->getMessage() . \PHP_EOL);
  130.             }
  131.         }
  132.         // init bundles
  133.         $this->initializeBundles();
  134.         // init container
  135.         $this->initializeContainer();
  136.         foreach ($this->getBundles() as $bundle) {
  137.             $bundle->setContainer($this->container);
  138.             $bundle->boot();
  139.         }
  140.         $this->initializeDatabaseConnectionVariables();
  141.         $this->booted true;
  142.     }
  143.     public static function getConnection(): Connection
  144.     {
  145.         if (self::$connection) {
  146.             return self::$connection;
  147.         }
  148.         self::$connection MySQLFactory::create();
  149.         return self::$connection;
  150.     }
  151.     public function getCacheDir(): string
  152.     {
  153.         return sprintf(
  154.             '%s/var/cache/%s_h%s%s',
  155.             $this->getProjectDir(),
  156.             $this->getEnvironment(),
  157.             $this->getCacheHash(),
  158.             EnvironmentHelper::getVariable('TEST_TOKEN') ?? ''
  159.         );
  160.     }
  161.     public function getPluginLoader(): KernelPluginLoader
  162.     {
  163.         return $this->pluginLoader;
  164.     }
  165.     public function shutdown(): void
  166.     {
  167.         if (!$this->booted) {
  168.             return;
  169.         }
  170.         // keep connection when rebooting
  171.         if (!$this->rebooting) {
  172.             self::$connection null;
  173.         }
  174.         parent::shutdown();
  175.     }
  176.     public function reboot($warmupDir, ?KernelPluginLoader $pluginLoader null, ?string $cacheId null): void
  177.     {
  178.         $this->rebooting true;
  179.         try {
  180.             if ($pluginLoader) {
  181.                 $this->pluginLoader $pluginLoader;
  182.             }
  183.             if ($cacheId) {
  184.                 $this->cacheId $cacheId;
  185.             }
  186.             parent::reboot($warmupDir);
  187.         } finally {
  188.             $this->rebooting false;
  189.         }
  190.     }
  191.     protected function configureContainer(ContainerBuilder $containerLoaderInterface $loader): void
  192.     {
  193.         $container->setParameter('container.dumper.inline_class_loader'true);
  194.         $container->setParameter('container.dumper.inline_factories'true);
  195.         $confDir $this->getProjectDir() . '/config';
  196.         $loader->load($confDir '/{packages}/*' self::CONFIG_EXTS'glob');
  197.         $loader->load($confDir '/{packages}/' $this->environment '/**/*' self::CONFIG_EXTS'glob');
  198.         $loader->load($confDir '/{services}' self::CONFIG_EXTS'glob');
  199.         $loader->load($confDir '/{services}_' $this->environment self::CONFIG_EXTS'glob');
  200.     }
  201.     protected function configureRoutes(RoutingConfigurator $routes): void
  202.     {
  203.         $confDir $this->getProjectDir() . '/config';
  204.         $routes->import($confDir '/{routes}/*' self::CONFIG_EXTS'glob');
  205.         $routes->import($confDir '/{routes}/' $this->environment '/**/*' self::CONFIG_EXTS'glob');
  206.         $routes->import($confDir '/{routes}' self::CONFIG_EXTS'glob');
  207.         $this->addBundleRoutes($routes);
  208.         $this->addApiRoutes($routes);
  209.         $this->addBundleOverwrites($routes);
  210.         $this->addFallbackRoute($routes);
  211.     }
  212.     /**
  213.      * {@inheritdoc}
  214.      *
  215.      * @return array<string, mixed>
  216.      */
  217.     protected function getKernelParameters(): array
  218.     {
  219.         $parameters parent::getKernelParameters();
  220.         $activePluginMeta = [];
  221.         foreach ($this->pluginLoader->getPluginInstances()->getActives() as $plugin) {
  222.             $class $plugin::class;
  223.             $activePluginMeta[$class] = [
  224.                 'name' => $plugin->getName(),
  225.                 'path' => $plugin->getPath(),
  226.                 'class' => $class,
  227.             ];
  228.         }
  229.         $pluginDir $this->pluginLoader->getPluginDir($this->getProjectDir());
  230.         $coreDir \dirname((string) (new \ReflectionClass(self::class))->getFileName());
  231.         return array_merge(
  232.             $parameters,
  233.             [
  234.                 'kernel.cache.hash' => $this->getCacheHash(),
  235.                 'kernel.shopware_version' => $this->shopwareVersion,
  236.                 'kernel.shopware_version_revision' => $this->shopwareVersionRevision,
  237.                 'kernel.shopware_core_dir' => $coreDir,
  238.                 'kernel.plugin_dir' => $pluginDir,
  239.                 'kernel.app_dir' => rtrim($this->getProjectDir(), '/') . '/custom/apps',
  240.                 'kernel.active_plugins' => $activePluginMeta,
  241.                 'kernel.plugin_infos' => $this->pluginLoader->getPluginInfos(),
  242.                 'kernel.supported_api_versions' => [234],
  243.                 'defaults_bool_true' => true,
  244.                 'defaults_bool_false' => false,
  245.                 'default_whitespace' => ' ',
  246.             ]
  247.         );
  248.     }
  249.     protected function getCacheHash(): string
  250.     {
  251.         $plugins = [];
  252.         foreach ($this->pluginLoader->getPluginInfos() as $plugin) {
  253.             if ($plugin['active'] === false) {
  254.                 continue;
  255.             }
  256.             $plugins[$plugin['name']] = $plugin['version'];
  257.         }
  258.         $pluginHash md5((string) json_encode($plugins\JSON_THROW_ON_ERROR));
  259.         return md5((string) \json_encode([
  260.             $this->cacheId,
  261.             substr((string) $this->shopwareVersionRevision08),
  262.             substr($pluginHash08),
  263.             EnvironmentHelper::getVariable('DATABASE_URL'''),
  264.         ], \JSON_THROW_ON_ERROR));
  265.     }
  266.     protected function initializeDatabaseConnectionVariables(): void
  267.     {
  268.         $shopwareSkipConnectionVariables EnvironmentHelper::getVariable('SHOPWARE_SKIP_CONNECTION_VARIABLES'false);
  269.         if ($shopwareSkipConnectionVariables) {
  270.             return;
  271.         }
  272.         $connection self::getConnection();
  273.         try {
  274.             $setSessionVariables = (bool) EnvironmentHelper::getVariable('SQL_SET_DEFAULT_SESSION_VARIABLES'true);
  275.             $connectionVariables = [];
  276.             $timeZoneSupportEnabled = (bool) EnvironmentHelper::getVariable('SHOPWARE_DBAL_TIMEZONE_SUPPORT_ENABLED'false);
  277.             if ($timeZoneSupportEnabled) {
  278.                 $connectionVariables[] = 'SET @@session.time_zone = "UTC"';
  279.             }
  280.             if ($setSessionVariables) {
  281.                 $connectionVariables[] = 'SET @@group_concat_max_len = CAST(IF(@@group_concat_max_len > 320000, @@group_concat_max_len, 320000) AS UNSIGNED)';
  282.                 $connectionVariables[] = 'SET sql_mode=(SELECT REPLACE(@@sql_mode,\'ONLY_FULL_GROUP_BY\',\'\'))';
  283.             }
  284.             if (empty($connectionVariables)) {
  285.                 return;
  286.             }
  287.             $connection->executeQuery(implode(';'$connectionVariables));
  288.         } catch (\Throwable) {
  289.         }
  290.     }
  291.     /**
  292.      * Dumps the preload file to an always known location outside the generated cache folder name
  293.      */
  294.     protected function dumpContainer(ConfigCache $cacheContainerBuilder $containerstring $classstring $baseClass): void
  295.     {
  296.         parent::dumpContainer($cache$container$class$baseClass);
  297.         $cacheDir $this->getCacheDir();
  298.         $cacheName basename($cacheDir);
  299.         $fileName substr(basename($cache->getPath()), 0, -3) . 'preload.php';
  300.         $preloadFile \dirname($cacheDir) . '/opcache-preload.php';
  301.         $loader = <<<PHP
  302. <?php
  303. require_once __DIR__ . '/#CACHE_PATH#';
  304. PHP;
  305.         file_put_contents($preloadFilestr_replace(
  306.             ['#CACHE_PATH#'],
  307.             [$cacheName '/' $fileName],
  308.             $loader
  309.         ));
  310.     }
  311.     private function addApiRoutes(RoutingConfigurator $routes): void
  312.     {
  313.         $routes->import('.''api');
  314.     }
  315.     private function addBundleRoutes(RoutingConfigurator $routes): void
  316.     {
  317.         foreach ($this->getBundles() as $bundle) {
  318.             if ($bundle instanceof Framework\Bundle) {
  319.                 $bundle->configureRoutes($routes$this->environment);
  320.             }
  321.         }
  322.     }
  323.     private function addBundleOverwrites(RoutingConfigurator $routes): void
  324.     {
  325.         foreach ($this->getBundles() as $bundle) {
  326.             if ($bundle instanceof Framework\Bundle) {
  327.                 $bundle->configureRouteOverwrites($routes$this->environment);
  328.             }
  329.         }
  330.     }
  331.     private function addFallbackRoute(RoutingConfigurator $routes): void
  332.     {
  333.         // detail routes
  334.         $route = new Route('/');
  335.         $route->setMethods(['GET']);
  336.         $route->setDefault('_controller'FallbackController::class . '::rootFallback');
  337.         $route->setDefault(PlatformRequest::ATTRIBUTE_ROUTE_SCOPE, ['storefront']);
  338.         $routes->add('root.fallback'$route->getPath());
  339.     }
  340. }