Test Failed
Push — master ( 73c405...d57cd2 )
by Konstantins
03:14
created

AbstractKernel::boot()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 0
cts 14
cp 0
rs 9.0856
c 0
b 0
f 0
cc 3
eloc 10
nc 4
nop 0
crap 12
1
<?php declare(strict_types = 1);
2
3
namespace Venta\Framework\Kernel;
4
5
use InvalidArgumentException;
6
use Psr\Log\LoggerAwareInterface;
7
use Venta\Config\ConfigFactory;
8
use Venta\Config\Parser\Json;
9
use Venta\Contracts\Config\Config;
10
use Venta\Contracts\Config\ConfigBuilder as ConfigBuilderContract;
11
use Venta\Contracts\Config\ConfigFactory as ConfigFactoryContract;
12
use Venta\Contracts\Container\Container;
13
use Venta\Contracts\Container\ContainerAware;
14
use Venta\Contracts\Http\ResponseFactoryAware;
15
use Venta\Contracts\Kernel\Kernel;
16
use Venta\Contracts\ServiceProvider\ServiceProvider;
17
use Venta\Framework\Kernel\Bootstrap\ConfigurationLoading;
18
use Venta\Framework\Kernel\Bootstrap\EnvironmentDetection;
19
use Venta\Framework\Kernel\Bootstrap\ErrorHandling;
20
use Venta\Framework\Kernel\Bootstrap\Logging;
21
use Venta\Framework\Kernel\Resolver\ServiceProviderDependencyResolver;
22
use Venta\ServiceProvider\AbstractServiceProvider;
23
24
/**
25
 * Class AbstractKernel
26
 *
27
 * @package Venta\Framework\Kernel
28
 */
29
abstract class AbstractKernel implements Kernel
30
{
31
    const VERSION = '0.1.0';
32
33
    /**
34
     * Service container class name.
35
     *
36
     * @var string
37
     */
38
    protected $containerClass = \Venta\Container\Container::class;
39
40
    /**
41
     * @inheritDoc
42
     */
43
    public function boot(): Container
44
    {
45
        $container = $this->initServiceContainer();
46
47
        foreach ($this->getBootstraps() as $bootstrapClass) {
48
            $this->invokeBootstrap($bootstrapClass, $container);
49
        }
50
51
        // Here we boot service providers on by one. The correct order is ensured by resolver.
52
        /** @var ServiceProviderDependencyResolver $resolver */
53
        $resolver = $container->get(ServiceProviderDependencyResolver::class);
54
        $configBuilder = $this->createConfigurationBuilder($container);
55
        foreach ($resolver($this->registerServiceProviders()) as $providerClass) {
56
            $this->bootServiceProvider($providerClass, $container, $configBuilder);
57
        }
58
59
        // When all service providers have been booted
60
        // we can be sure that all possible config changes were already made.
61
        // At this point we are creating Config class instance from Config Builder.
62
        $container->bindInstance(Config::class, $configBuilder->build());
63
64
        return $container;
65
    }
66
67
    /**
68
     * @inheritDoc
69
     */
70
    public function environment(): string
71
    {
72
        return getenv('APP_ENV') ?: 'local';
73
    }
74
75
    /**
76
     * @return string
77
     */
78
    abstract public function rootPath(): string;
79
80
    /**
81
     * @inheritDoc
82
     */
83
    public function version(): string
84
    {
85
        return self::VERSION;
86
    }
87
88
    /**
89
     * @inheritDoc
90
     */
91
    public function isCli(): bool
92
    {
93
        return php_sapi_name() === 'cli';
94
    }
95
96
    /**
97
     * Boots service provider with base config.
98
     *
99
     * @param string $providerClass
100
     * @param Container $container
101
     * @param ConfigBuilderContract $configBuilder
102
     * @throws InvalidArgumentException
103
     */
104
    protected function bootServiceProvider(string $providerClass, Container $container, ConfigBuilderContract $configBuilder)
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 125 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
105
    {
106
        $this->ensureServiceProvider($providerClass);
107
108
        /** @var ServiceProvider $provider */
109
        $provider = new $providerClass($container, $configBuilder);
110
        $provider->boot();
111
    }
112
113
    /**
114
     * Returns list of kernel bootstraps.
115
     * This is a main place to tune default kernel behavior.
116
     * Change carefully, as it may cause kernel failure.
117
     *
118
     * @return string[]
119
     */
120
    protected function getBootstraps(): array
121
    {
122
        $modules = [
123
            EnvironmentDetection::class,
124
            ConfigurationLoading::class,
125
            Logging::class,
126
            ErrorHandling::class,
127
        ];
128
129
        // Here we can add environment dependant modules.
130
        //if ($this->getEnvironment() === \Venta\Contracts\Kernel\Kernel::ENV_LOCAL) {
131
        //    $modules[] = 'KernelModule';
132
        //}
133
134
        return $modules;
135
    }
136
137
    /**
138
     * Invokes kernel bootstrap.
139
     * This is the point where specific kernel functionality defined by bootstrap is enabled.
140
     *
141
     * @param string $bootstrapClass
142
     * @param Container $container
143
     * @throws InvalidArgumentException
144
     */
145
    protected function invokeBootstrap(string $bootstrapClass, Container $container)
146
    {
147
        $this->ensureBootstrap($bootstrapClass);
148
149
        (new $bootstrapClass($container, $this))();
150
    }
151
152
    /**
153
     * Returns a list of all registered service providers.
154
     *
155
     * @return string[]
156
     */
157
    abstract protected function registerServiceProviders(): array;
158
159
    /**
160
     * Adds default service inflections.
161
     *
162
     * @param Container $container
163
     */
164
    private function addDefaultInflections(Container $container)
165
    {
166
        $container->addInflection(ContainerAware::class, 'setContainer', ['container' => $container]);
167
        $container->addInflection(LoggerAwareInterface::class, 'setLogger');
168
        $container->addInflection(ResponseFactoryAware::class, 'setResponseFactory');
169
    }
170
171
    /**
172
     * Binds default services to container.
173
     *
174
     * @param Container $container
175
     */
176
    private function bindDefaultServices(Container $container)
177
    {
178
        $container->bindInstance(Container::class, $container);
179
        $container->bindInstance(Kernel::class, $this);
180
181
        $container->bindClass(ConfigFactoryContract::class, ConfigFactory::class, true);
182
    }
183
184
    /**
185
     * Ensures bootstrap class extends abstract kernel bootstrap.
186
     *
187
     * @param string $bootstrapClass
188
     * @throws InvalidArgumentException
189
     */
190
    private function ensureBootstrap(string $bootstrapClass)
191
    {
192
        if (!is_subclass_of($bootstrapClass, AbstractKernelBootstrap::class)) {
193
            throw new InvalidArgumentException(
194
                sprintf('Class "%s" must be a subclass of "%s".', $bootstrapClass, AbstractKernelBootstrap::class)
195
            );
196
        }
197
    }
198
199
    /**
200
     * Ensures service provider implements contract.
201
     *
202
     * @param string $providerClass
203
     * @throws InvalidArgumentException
204
     */
205
    private function ensureServiceProvider(string $providerClass)
206
    {
207
        if (!is_subclass_of($providerClass, AbstractServiceProvider::class)) {
208
            throw new InvalidArgumentException(
209
                sprintf('Class "%s" must be a subclass of "%s".', $providerClass, AbstractServiceProvider::class)
210
            );
211
        }
212
    }
213
214
    /**
215
     * Initializes service container.
216
     */
217
    private function initServiceContainer(): Container
218
    {
219
        /** @var Container $container */
220
        $container = new $this->containerClass;
221
222
        $this->bindDefaultServices($container);
223
        $this->addDefaultInflections($container);
224
225
        return $container;
226
    }
227
228
    /**
229
     * Creates and returns configuration builder instance.
230
     *
231
     * @param Container $container
232
     * @return ConfigBuilderContract
233
     */
234
    private function createConfigurationBuilder(Container $container)
235
    {
236
        /** @var ConfigBuilderContract $builder */
237
        $builder = $container->get(ConfigBuilderContract::class);
238
        $builder->addFileParser($container->get(Json::class));
239
240
        return $builder;
241
    }
242
}