Passed
Push — trunk ( fa6f0e...49986f )
by Christian
13:12 queued 15s
created

HttpKernel   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 196
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 87
dl 0
loc 196
rs 10
c 0
b 0
f 0
wmc 29

10 Methods

Rating   Name   Duplication   Size   Complexity  
A terminate() 0 7 2
A doHandle() 0 38 5
A getProjectDir() 0 26 6
A __construct() 0 5 1
A getKernel() 0 3 1
A getConnection() 0 9 2
A handle() 0 11 2
A setPluginLoader() 0 3 1
A createPluginLoader() 0 13 3
B createKernel() 0 34 6
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core;
4
5
use Composer\Autoload\ClassLoader;
6
use Composer\InstalledVersions;
7
use Doctrine\DBAL\Connection;
8
use Doctrine\DBAL\Driver\Middleware;
9
use Doctrine\DBAL\DriverManager;
10
use Doctrine\DBAL\Exception;
11
use Shopware\Core\Framework\Adapter\Cache\CacheIdLoader;
12
use Shopware\Core\Framework\Adapter\Database\MySQLFactory;
13
use Shopware\Core\Framework\Adapter\Storage\MySQLKeyValueStorage;
14
use Shopware\Core\Framework\Event\BeforeSendRedirectResponseEvent;
15
use Shopware\Core\Framework\Event\BeforeSendResponseEvent;
16
use Shopware\Core\Framework\Log\Package;
17
use Shopware\Core\Framework\Plugin\KernelPluginLoader\DbalKernelPluginLoader;
18
use Shopware\Core\Framework\Plugin\KernelPluginLoader\KernelPluginLoader;
19
use Shopware\Core\Framework\Routing\CanonicalRedirectService;
20
use Shopware\Core\Framework\Routing\RequestTransformerInterface;
21
use Shopware\Core\Profiling\Doctrine\ProfilingMiddleware;
22
use Shopware\Storefront\Framework\Cache\CacheStore;
23
use Symfony\Component\HttpFoundation\RedirectResponse;
24
use Symfony\Component\HttpFoundation\Request;
25
use Symfony\Component\HttpFoundation\Response;
26
use Symfony\Component\HttpKernel\HttpCache\HttpCache;
27
use Symfony\Component\HttpKernel\HttpKernelInterface;
28
use Symfony\Component\HttpKernel\KernelInterface;
29
use Symfony\Component\HttpKernel\TerminableInterface;
30
31
/**
32
 * @psalm-import-type Params from DriverManager
33
 */
34
#[Package('core')]
35
class HttpKernel
36
{
37
    protected static ?Connection $connection = null;
38
39
    /**
40
     * @var class-string<Kernel>
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Kernel> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Kernel>.
Loading history...
41
     */
42
    protected static string $kernelClass = Kernel::class;
43
44
    /**
45
     * @var class-string<HttpCache>
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<HttpCache> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<HttpCache>.
Loading history...
46
     */
47
    protected static string $httpCacheClass = HttpCache::class;
48
49
    protected ?string $projectDir = null;
50
51
    protected ?KernelPluginLoader $pluginLoader = null;
52
53
    protected ?KernelInterface $kernel = null;
54
55
    public function __construct(
56
        protected string $environment,
57
        protected bool $debug,
58
        protected ?ClassLoader $classLoader = null
59
    ) {
60
    }
61
62
    public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): HttpKernelResult
63
    {
64
        try {
65
            return $this->doHandle($request, $type, $catch);
66
        } catch (Exception $e) {
67
            /** @var Params|array{url?: string} $connectionParams */
68
            $connectionParams = self::getConnection()->getParams();
69
70
            $message = str_replace([$connectionParams['url'] ?? null, $connectionParams['password'] ?? null, $connectionParams['user'] ?? null], '******', $e->getMessage());
71
72
            throw new \RuntimeException(sprintf('Could not connect to database. Message from SQL Server: %s', $message));
73
        }
74
    }
75
76
    public function getKernel(): KernelInterface
77
    {
78
        return $this->createKernel();
79
    }
80
81
    /**
82
     * Allows to switch the plugin loading.
83
     */
84
    public function setPluginLoader(KernelPluginLoader $pluginLoader): void
85
    {
86
        $this->pluginLoader = $pluginLoader;
87
    }
88
89
    /**
90
     * @param array<Middleware> $middlewares
91
     */
92
    public static function getConnection(array $middlewares = []): Connection
93
    {
94
        if (self::$connection) {
95
            return self::$connection;
96
        }
97
98
        self::$connection = MySQLFactory::create($middlewares);
99
100
        return self::$connection;
101
    }
102
103
    public function terminate(Request $request, Response $response): void
104
    {
105
        if (!$this->kernel instanceof TerminableInterface) {
106
            return;
107
        }
108
109
        $this->kernel->terminate($request, $response);
110
    }
111
112
    private function doHandle(Request $request, int $type, bool $catch): HttpKernelResult
113
    {
114
        // create core kernel which contains bootstrapping for plugins etc.
115
        $kernel = $this->createKernel();
116
        $kernel->boot();
117
118
        $container = $kernel->getContainer();
119
120
        // transform request to resolve seo urls and detect sales channel
121
        $transformed = $container
122
            ->get(RequestTransformerInterface::class)
123
            ->transform($request);
124
125
        $redirect = $container
126
            ->get(CanonicalRedirectService::class)
127
            ->getRedirect($transformed);
128
129
        if ($redirect instanceof RedirectResponse) {
130
            $event = new BeforeSendRedirectResponseEvent($transformed, $redirect);
131
            $container->get('event_dispatcher')->dispatch($event);
132
133
            return new HttpKernelResult($transformed, $event->getResponse());
134
        }
135
136
        // check for http caching
137
        $enabled = $container->hasParameter('shopware.http.cache.enabled')
138
            && $container->getParameter('shopware.http.cache.enabled');
139
        if ($enabled && $container->has(CacheStore::class)) {
140
            $kernel = new static::$httpCacheClass($kernel, $container->get(CacheStore::class), null, ['debug' => $this->debug]);
141
        }
142
143
        $response = $kernel->handle($transformed, $type, $catch);
144
145
        // fire event to trigger runtime events like seo url headers
146
        $event = new BeforeSendResponseEvent($transformed, $response);
147
        $container->get('event_dispatcher')->dispatch($event);
148
149
        return new HttpKernelResult($transformed, $event->getResponse());
150
    }
151
152
    private function createKernel(): KernelInterface
153
    {
154
        if ($this->kernel !== null) {
155
            return $this->kernel;
156
        }
157
158
        if (InstalledVersions::isInstalled('shopware/platform')) {
159
            $shopwareVersion = InstalledVersions::getVersion('shopware/platform')
160
                . '@' . InstalledVersions::getReference('shopware/platform');
161
        } else {
162
            $shopwareVersion = InstalledVersions::getVersion('shopware/core')
163
                . '@' . InstalledVersions::getReference('shopware/core');
164
        }
165
166
        $middlewares = [];
167
        if (\PHP_SAPI !== 'cli' && $this->environment !== 'prod' && InstalledVersions::isInstalled('symfony/doctrine-bridge')) {
168
            $middlewares = [new ProfilingMiddleware()];
169
        }
170
171
        $connection = self::getConnection($middlewares);
172
173
        $pluginLoader = $this->createPluginLoader($connection);
174
175
        $storage = new MySQLKeyValueStorage($connection);
176
        $cacheId = (new CacheIdLoader($storage))->load();
177
178
        return $this->kernel = new static::$kernelClass(
179
            $this->environment,
180
            $this->debug,
181
            $pluginLoader,
182
            $cacheId,
183
            $shopwareVersion,
184
            $connection,
185
            $this->getProjectDir()
186
        );
187
    }
188
189
    private function getProjectDir(): string
190
    {
191
        if ($this->projectDir === null) {
192
            if ($dir = $_ENV['PROJECT_ROOT'] ?? $_SERVER['PROJECT_ROOT'] ?? false) {
193
                return $this->projectDir = $dir;
194
            }
195
196
            $r = new \ReflectionObject($this);
197
198
            /** @var string $dir */
199
            $dir = $r->getFileName();
200
            if (!file_exists($dir)) {
201
                throw new \LogicException(sprintf('Cannot auto-detect project dir for kernel of class "%s".', $r->name));
202
            }
203
204
            $dir = $rootDir = \dirname($dir);
205
            while (!file_exists($dir . '/vendor')) {
206
                if ($dir === \dirname($dir)) {
207
                    return $this->projectDir = $rootDir;
208
                }
209
                $dir = \dirname($dir);
210
            }
211
            $this->projectDir = $dir;
212
        }
213
214
        return $this->projectDir;
215
    }
216
217
    private function createPluginLoader(Connection $connection): KernelPluginLoader
218
    {
219
        if ($this->pluginLoader) {
220
            return $this->pluginLoader;
221
        }
222
223
        if (!$this->classLoader) {
224
            throw new \RuntimeException('No plugin loader and no class loader provided');
225
        }
226
227
        $this->pluginLoader = new DbalKernelPluginLoader($this->classLoader, null, $connection);
228
229
        return $this->pluginLoader;
230
    }
231
}
232