Issues (31)

src/Http/Kernel.php (4 issues)

1
<?php
2
/**
3
 * Copyright (c) 2020.
4
 * @author Paweł Antosiak <[email protected]>
5
 */
6
7
declare(strict_types=1);
8
9
namespace Gorynych\Http;
10
11
use Gorynych\Exception\KernelNotBootedException;
12
use Gorynych\Http\Dto\ProblemDetails;
13
use Gorynych\Http\Exception\HttpException;
14
use Gorynych\Http\Exception\NotAcceptableHttpException;
15
use Gorynych\Http\Formatter\FormatterFactory;
16
use Gorynych\Http\Routing\Router;
17
use Gorynych\Resource\ResourceLoader;
18
use Symfony\Component\Config\FileLocator;
19
use Symfony\Component\Config\FileLocatorInterface;
20
use Symfony\Component\DependencyInjection\ContainerBuilder;
21
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
22
use Symfony\Component\HttpFoundation\Request;
23
use Symfony\Component\HttpFoundation\Response;
24
25
abstract class Kernel
26
{
27
    protected ?ContainerBuilder $container;
28
    protected string $env;
29
    protected bool $booted = false;
30
31 4
    public function boot(string $env = 'dev'): self
32
    {
33 4
        $this->env = $env;
34
35 4
        $this->initializeContainer();
36 4
        $this->booted = true;
37
38 4
        return $this;
39
    }
40
41
    public function reboot(): self
42
    {
43
        $this->shutdown()->boot($this->env ?? 'dev');
44
45
        return $this;
46
    }
47
48
    public function shutdown(): self
49
    {
50
        $this->container = null;
51
        $this->booted = false;
52
53
        return $this;
54
    }
55
56
    /**
57
     * @throws KernelNotBootedException
58
     */
59
    public function getContainer(): ContainerBuilder
60
    {
61
        $this->ensureIsBooted();
62
63
        return $this->container;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->container could return the type null which is incompatible with the type-hinted return Symfony\Component\Depend...ection\ContainerBuilder. Consider adding an additional type-check to rule them out.
Loading history...
64
    }
65
66
    /**
67
     * @throws KernelNotBootedException
68
     */
69
    public function getRouter(): Router
70
    {
71
        $this->ensureIsBooted();
72
73
        return $this->container->get('http.router');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->container->get('http.router') could return the type null which is incompatible with the type-hinted return Gorynych\Http\Routing\Router. Consider adding an additional type-check to rule them out.
Loading history...
The method get() 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

73
        return $this->container->/** @scrutinizer ignore-call */ get('http.router');

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...
74
    }
75
76
    /**
77
     * @throws KernelNotBootedException
78
     */
79
    public function getFormatterFactory(): FormatterFactory
80
    {
81
        $this->ensureIsBooted();
82
83
        return $this->container->get('http.formatter_factory');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->container-...ttp.formatter_factory') could return the type null which is incompatible with the type-hinted return Gorynych\Http\Formatter\FormatterFactory. Consider adding an additional type-check to rule them out.
Loading history...
84
    }
85
86
    /**
87
     * @throws KernelNotBootedException
88
     */
89 4
    public function handleRequest(Request $request): Response
90
    {
91 4
        $this->ensureIsBooted();
92
93
        try {
94 4
            $formatter = $this->getFormatterFactory()->create(...$request->getAcceptableContentTypes());
95 1
        } catch (NotAcceptableHttpException $e) {
96 1
            return new Response($e->getMessage(), $e->getStatusCode());
97
        }
98
99
        try {
100 3
            $operation = $this->getRouter()->findOperation($request);
101 3
            $output = $operation->handle($request);
102 2
        } catch (\Throwable $throwable) {
103 2
            if ('prod' === $this->env || ('test' === $this->env && $throwable instanceof HttpException)) {
104 1
                return $formatter->format(
105 1
                    $problemDetails = ProblemDetails::fromThrowable($throwable),
106 1
                    $problemDetails->status,
107
                );
108
            }
109
110 1
            throw $throwable;
111
        }
112
113 1
        return $formatter->format($output, $operation->getResponseStatus());
114
    }
115
116
    protected function initializeContainer(): void
117
    {
118
        $this->container = new ContainerBuilder();
119
120
        $loader = new YamlFileLoader($this->container, new FileLocator(dirname(__DIR__, 2) . '/config'));
121
        $loader->load('services.yaml');
122
123
        $this->container->set('kernel.config_locator', $this->getConfigLocator());
124
        $this->container->set('http.router', new Router(new ResourceLoader($this->container, $this->getConfigLocator())));
125
        $this->container->set('http.formatter_factory', new FormatterFactory($this->getConfigLocator()));
126
127
        $this->loadConfiguration();
128
        $this->container->compile();
129
    }
130
131
    /**
132
     * @throws KernelNotBootedException
133
     */
134 4
    private function ensureIsBooted(): void
135
    {
136 4
        if (false === $this->booted) {
137
            throw new KernelNotBootedException();
138
        }
139 4
    }
140
141
    /**
142
     * Returns FileLocator for project config directory
143
     */
144
    abstract public function getConfigLocator(): FileLocatorInterface;
145
146
    /**
147
     * Loads project configuration files
148
     */
149
    abstract protected function loadConfiguration(): void;
150
}
151