Passed
Push — master ( ed75ff...70a1a5 )
by Christian
123:05 queued 74:38
created

Kernel::loadRoutes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 10
c 1
b 0
f 0
ccs 3
cts 3
cp 1
crap 1
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core;
4
5
use Doctrine\DBAL\Configuration;
6
use Doctrine\DBAL\Connection;
7
use Doctrine\DBAL\DriverManager;
8
use Doctrine\DBAL\FetchMode;
9
use Shopware\Core\Framework\Api\Controller\FallbackController;
10
use Shopware\Core\Framework\Migration\MigrationStep;
11
use Shopware\Core\Framework\Plugin\KernelPluginLoader\KernelPluginLoader;
12
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
13
use Symfony\Component\Config\Loader\LoaderInterface;
14
use Symfony\Component\DependencyInjection\ContainerBuilder;
15
use Symfony\Component\HttpKernel\Kernel as HttpKernel;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Shopware\Core\HttpKernel. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
16
use Symfony\Component\Routing\Route;
17
use Symfony\Component\Routing\RouteCollection;
18
use Symfony\Component\Routing\RouteCollectionBuilder;
19
20
class Kernel extends HttpKernel
21
{
22
    use MicroKernelTrait;
23
24
    public const CONFIG_EXTS = '.{php,xml,yaml,yml}';
25
26
    /**
27
     * @var string Fallback version if nothing is provided via kernel constructor
28
     */
29
    public const SHOPWARE_FALLBACK_VERSION = '6.3.9999999.9999999-dev';
30
31
    /**
32
     * @var string Regex pattern for validating Shopware versions
33
     */
34
    private const VALID_VERSION_PATTERN = '#^\d\.\d+\.\d+\.(\d+|x)(-\w+)?#';
35
36
    /**
37
     * @var Connection|null
38
     */
39
    protected static $connection;
40
41
    /**
42
     * @var KernelPluginLoader|null
43
     */
44 6
    protected $pluginLoader;
45
46 6
    /**
47
     * @var string
48 6
     */
49 6
    protected $shopwareVersion;
50 6
51
    /**
52 6
     * @var string|null
53
     */
54
    protected $shopwareVersionRevision;
55 6
56
    /**
57 6
     * @var string|null
58 4
     */
59
    protected $projectDir;
60
61 6
    /**
62 6
     * @var bool
63 6
     */
64
    private $rebooting = false;
65
66 6
    /**
67
     * @var string
68 207
     */
69
    private $cacheId;
70 207
71 201
    /**
72 201
     * {@inheritdoc}
73
     */
74
    public function __construct(
75 201
        string $environment,
76
        bool $debug,
77
        KernelPluginLoader $pluginLoader,
78 6
        string $cacheId,
79 6
        ?string $version = self::SHOPWARE_FALLBACK_VERSION,
80
        ?Connection $connection = null,
81
        ?string $projectDir = null
82 6
    ) {
83
        date_default_timezone_set('UTC');
84
85
        parent::__construct($environment, $debug);
86
        self::$connection = $connection;
87
88 6
        $this->pluginLoader = $pluginLoader;
89
90 6
        $this->parseShopwareVersion($version);
91 6
        $this->cacheId = $cacheId;
92
        $this->projectDir = $projectDir;
93
    }
94
95 6
    public function registerBundles()
96
    {
97
        /** @var array $bundles */
98 6
        $bundles = require $this->getProjectDir() . '/config/bundles.php';
99
        $instanciatedBundleNames = [];
100
101 6
        /** @var class-string<\Symfony\Component\HttpKernel\Bundle\Bundle> $class */
102 6
        foreach ($bundles as $class => $envs) {
103 6
            if (isset($envs['all']) || isset($envs[$this->environment])) {
104
                $bundle = new $class();
105
                $instanciatedBundleNames[] = $bundle->getName();
106 6
107
                yield $bundle;
0 ignored issues
show
Bug Best Practice introduced by
The expression yield $bundle returns the type Generator which is incompatible with the return type mandated by Symfony\Component\HttpKe...face::registerBundles() of Symfony\Component\HttpKe...dleInterface[]|iterable.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
108 6
            }
109 6
        }
110
111 17
        yield from $this->pluginLoader->getBundles($this->getKernelParameters(), $instanciatedBundleNames);
0 ignored issues
show
Bug introduced by
The method getBundles() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

111
        yield from $this->pluginLoader->/** @scrutinizer ignore-call */ getBundles($this->getKernelParameters(), $instanciatedBundleNames);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
112
    }
113 17
114
    public function getProjectDir()
115
    {
116 6
        if ($this->projectDir === null) {
117
            $this->projectDir = parent::getProjectDir();
118 6
        }
119
120 6
        return $this->projectDir;
121 6
    }
122
123
    public function boot(): void
124 6
    {
125
        if ($this->booted === true) {
126
            if ($this->debug) {
127 6
                $this->startTime = microtime(true);
128
            }
129
130 6
            return;
131
        }
132 6
133 6
        if ($this->debug) {
134 6
            $this->startTime = microtime(true);
135 6
        }
136 6
137
        if ($this->debug && !isset($_ENV['SHELL_VERBOSITY']) && !isset($_SERVER['SHELL_VERBOSITY'])) {
138
            putenv('SHELL_VERBOSITY=3');
139
            $_ENV['SHELL_VERBOSITY'] = 3;
140 2
            $_SERVER['SHELL_VERBOSITY'] = 3;
141
        }
142 2
143
        $this->pluginLoader->initializePlugins($this->getProjectDir());
144
145 3
        // init bundles
146
        $this->initializeBundles();
147 3
148
        // init container
149
        $this->initializeContainer();
150 1
151
        foreach ($this->getBundles() as $bundle) {
152 1
            $bundle->setContainer($this->container);
153
            $bundle->boot();
154
        }
155
156 1
        $this->initializeDatabaseConnectionVariables();
157 1
158
        $this->booted = true;
159 1
    }
160 1
161
    public static function getConnection(): Connection
162 2
    {
163
        if (!self::$connection) {
164 2
            $url = $_ENV['DATABASE_URL']
165
                ?? $_SERVER['DATABASE_URL']
166 2
                ?? getenv('DATABASE_URL');
167
            $parameters = [
168 2
                'url' => $url,
169 2
                'charset' => 'utf8mb4',
170 2
            ];
171 2
172 2
            self::$connection = DriverManager::getConnection($parameters, new Configuration());
173
        }
174 1
175
        return self::$connection;
176 1
    }
177
178 1
    public function getCacheDir(): string
179 1
    {
180 1
        return sprintf(
181
            '%s/var/cache/%s_h%s',
182 1
            $this->getProjectDir(),
183 1
            $this->getEnvironment(),
184 1
            $this->getCacheHash()
185
        );
186
    }
187
188
    public function getPluginLoader(): KernelPluginLoader
189 2
    {
190
        return $this->pluginLoader;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->pluginLoader could return the type null which is incompatible with the type-hinted return Shopware\Core\Framework\...ader\KernelPluginLoader. Consider adding an additional type-check to rule them out.
Loading history...
191 2
    }
192
193 2
    public function shutdown(): void
194
    {
195 2
        if (!$this->booted) {
196 1
            return;
197 1
        }
198 1
199 1
        // keep connection when rebooting
200
        if (!$this->rebooting) {
201
            self::$connection = null;
202
        }
203 2
204 2
        parent::shutdown();
205
    }
206 2
207 2
    public function reboot($warmupDir, ?KernelPluginLoader $pluginLoader = null, ?string $cacheId = null): void
208
    {
209
        $this->rebooting = true;
210
211
        try {
212 6
            if ($pluginLoader) {
213
                $this->pluginLoader = $pluginLoader;
214 6
            }
215
            if ($cacheId) {
216 6
                $this->cacheId = $cacheId;
217 6
            }
218 6
            parent::reboot($warmupDir);
219 6
        } finally {
220 6
            $this->rebooting = false;
221
        }
222
    }
223 2
224
    /**
225 2
     * @deprecated tag:v6.4.0.0 - API Routes does not contain versions anymore
226 2
     */
227
    public function loadRoutes(LoaderInterface $loader): RouteCollection
228 2
    {
229
        $routes = new RouteCollectionBuilder($loader);
230 2
        $this->configureRoutes($routes);
231 2
232
        return $this->addApiFallbackRoutes($routes->build());
0 ignored issues
show
Deprecated Code introduced by
The function Shopware\Core\Kernel::addApiFallbackRoutes() has been deprecated: tag:v6.4.0.0 - API Routes does not contain versions anymore ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

232
        return /** @scrutinizer ignore-deprecated */ $this->addApiFallbackRoutes($routes->build());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
233 2
    }
234
235
    /**
236
     * @deprecated tag:v6.4.0.0 - API Routes does not contain versions anymore
237
     */
238
    public function addApiFallbackRoutes(RouteCollection $routes): RouteCollection
239
    {
240
        foreach ($routes->all() as $name => $route) {
241
            if (strpos($route->getPath(), '{version}') === false) {
242
                continue;
243
            }
244
245
            $fallbackRoute = clone $route;
246
            $fallbackRoute->setPath(str_replace(['v{version}/', '{version}/'], '', $fallbackRoute->getPath()));
247
            $fallbackRoute->setDefault('version', PlatformRequest::API_VERSION);
248
            $routes->add($name . '.major_fallback', $fallbackRoute);
249
        }
250
251
        return $routes;
252
    }
253
254
    protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
255
    {
256
        $container->setParameter('container.dumper.inline_class_loader', true);
257
        $container->setParameter('container.dumper.inline_factories', true);
258
259
        $confDir = $this->getProjectDir() . '/config';
260
261
        $loader->load($confDir . '/{packages}/*' . self::CONFIG_EXTS, 'glob');
262 2
        $loader->load($confDir . '/{packages}/' . $this->environment . '/**/*' . self::CONFIG_EXTS, 'glob');
263
        $loader->load($confDir . '/{services}' . self::CONFIG_EXTS, 'glob');
264 2
        $loader->load($confDir . '/{services}_' . $this->environment . self::CONFIG_EXTS, 'glob');
265
    }
266 2
267 2
    protected function configureRoutes(RouteCollectionBuilder $routes): void
268
    {
269 2
        $confDir = $this->getProjectDir() . '/config';
270
271 1
        $routes->import($confDir . '/{routes}/*' . self::CONFIG_EXTS, '/', 'glob');
272 1
        $routes->import($confDir . '/{routes}/' . $this->environment . '/**/*' . self::CONFIG_EXTS, '/', 'glob');
273 1
        $routes->import($confDir . '/{routes}' . self::CONFIG_EXTS, '/', 'glob');
274 1
275
        $this->addBundleRoutes($routes);
276 1
        $this->addApiRoutes($routes);
277 1
        $this->addBundleOverwrites($routes);
278
        $this->addFallbackRoute($routes);
279 1
    }
280 1
281
    /**
282 1
     * {@inheritdoc}
283 1
     */
284
    protected function getKernelParameters(): array
285
    {
286 1
        $parameters = parent::getKernelParameters();
287
288
        $activePluginMeta = [];
289 2
290 2
        foreach ($this->pluginLoader->getPluginInstances()->getActives() as $plugin) {
291
            $class = \get_class($plugin);
292 1
            $activePluginMeta[$class] = [
293
                'name' => $plugin->getName(),
294 1
                'path' => $plugin->getPath(),
295 1
                'class' => $class,
296
            ];
297 1
        }
298
299 1
        $pluginDir = $this->pluginLoader->getPluginDir($this->getProjectDir());
300
301
        return array_merge(
302 1
            $parameters,
303
            [
304 6
                'kernel.cache.hash' => $this->getCacheHash(),
305
                'kernel.shopware_version' => $this->shopwareVersion,
306
                'kernel.shopware_version_revision' => $this->shopwareVersionRevision,
307 6
                'kernel.plugin_dir' => $pluginDir,
308
                'kernel.active_plugins' => $activePluginMeta,
309 6
                'kernel.plugin_infos' => $this->pluginLoader->getPluginInfos(),
310
                'kernel.supported_api_versions' => [2, 3],
311
                'defaults_bool_true' => true,
312
                'defaults_bool_false' => false,
313 6
                'default_whitespace' => ' ',
314 6
            ]
315
        );
316 6
    }
317
318 6
    protected function getCacheHash()
319
    {
320
        $pluginHash = md5(implode('', array_keys($this->pluginLoader->getPluginInstances()->getActives())));
321
322
        return md5(json_encode([
323
            $this->cacheId,
324
            mb_substr($this->shopwareVersionRevision, 0, 8),
325 6
            mb_substr($pluginHash, 0, 8),
326
        ]));
327
    }
328 6
329
    protected function initializeDatabaseConnectionVariables(): void
330
    {
331
        $connection = self::getConnection();
332
333
        $nonDestructiveMigrations = $connection->executeQuery('
334
            SELECT `creation_timestamp`
335
            FROM `migration`
336
            WHERE `update` IS NOT NULL AND `update_destructive` IS NULL
337
        ')->fetchAll(FetchMode::COLUMN);
338
339
        $activeMigrations = $this->container->getParameter('migration.active');
340
341
        $activeNonDestructiveMigrations = array_intersect($activeMigrations, $nonDestructiveMigrations);
342
343
        $setSessionVariables = $_SERVER['SQL_SET_DEFAULT_SESSION_VARIABLES'] ?? true;
344
        $connectionVariables = [];
345
346
        if ($setSessionVariables) {
347
            $connectionVariables[] = 'SET @@group_concat_max_len = CAST(IF(@@group_concat_max_len > 320000, @@group_concat_max_len, 320000) AS UNSIGNED)';
348
            $connectionVariables[] = "SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''))";
349
        }
350
351
        foreach ($activeNonDestructiveMigrations as $migration) {
352
            $connectionVariables[] = sprintf(
353
                'SET %s = TRUE',
354
                sprintf(MigrationStep::MIGRATION_VARIABLE_FORMAT, $migration)
355
            );
356
        }
357
358
        if (empty($connectionVariables)) {
359
            return;
360
        }
361
        $connection->executeQuery(implode(';', $connectionVariables));
362
    }
363
364
    private function addApiRoutes(RouteCollectionBuilder $routes): void
365
    {
366
        $routes->import('.', null, 'api');
367
    }
368
369
    private function addBundleRoutes(RouteCollectionBuilder $routes): void
370
    {
371
        foreach ($this->getBundles() as $bundle) {
372
            if ($bundle instanceof Framework\Bundle) {
373
                $bundle->configureRoutes($routes, (string) $this->environment);
374
            }
375
        }
376
    }
377
378
    private function addBundleOverwrites(RouteCollectionBuilder $routes): void
379
    {
380
        foreach ($this->getBundles() as $bundle) {
381
            if ($bundle instanceof Framework\Bundle) {
382
                $bundle->configureRouteOverwrites($routes, (string) $this->environment);
383
            }
384
        }
385
    }
386
387
    private function addFallbackRoute(RouteCollectionBuilder $routes): void
388
    {
389
        // detail routes
390
        $route = new Route('/');
391
        $route->setMethods(['GET']);
392
        $route->setDefault('_controller', FallbackController::class . '::rootFallback');
393
394
        $routes->addRoute($route, 'root.fallback');
395
    }
396
397
    private function parseShopwareVersion(?string $version): void
398
    {
399
        // does not come from composer, was set manually
400
        if ($version === null || mb_strpos($version, '@') === false) {
401
            $this->shopwareVersion = self::SHOPWARE_FALLBACK_VERSION;
402
            $this->shopwareVersionRevision = str_repeat('0', 32);
403
404
            return;
405
        }
406
407
        [$version, $hash] = explode('@', $version);
408
        $version = ltrim($version, 'v');
409
        $version = (string) str_replace('+', '-', $version);
410
411
        /*
412
         * checks if the version is a valid version pattern
413
         * Shopware\Core\Framework\Test\KernelTest::testItCreatesShopwareVersion()
414
         */
415
        if (!preg_match(self::VALID_VERSION_PATTERN, $version)) {
416
            $version = self::SHOPWARE_FALLBACK_VERSION;
417
        }
418
419
        $this->shopwareVersion = $version;
420
        $this->shopwareVersionRevision = $hash;
421
    }
422
}
423