AbstractKernel::initServiceContainer()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 0
cts 7
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 0
crap 2
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\ConfigProxy;
8
use Venta\Config\MutableConfig;
9
use Venta\Container\ContainerProxy;
10
use Venta\Contracts\Config\Config;
11
use Venta\Contracts\Container\Container;
12
use Venta\Contracts\Container\ContainerAware;
13
use Venta\Contracts\Container\MutableContainer;
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\ServiceProvider\AbstractServiceProvider;
22
23
/**
24
 * Class AbstractKernel
25
 *
26
 * @package Venta\Framework\Kernel
27
 */
28
abstract class AbstractKernel implements Kernel
29
{
30
    const VERSION = '0.1.0';
31
32
    /**
33
     * Service container class name.
34
     *
35
     * @var string
36
     */
37
    protected $containerClass = \Venta\Container\MutableContainer::class;
38
39
    /**
40
     * @var ServiceProvider[]
41
     */
42
    private $providers = [];
43
44
    /**
45
     * @inheritDoc
46
     */
47
    public function boot(): Container
48
    {
49
        $container = $this->initServiceContainer();
50
51
        foreach ($this->getBootstraps() as $bootstrapClass) {
52
            $this->invokeBootstrap($bootstrapClass, $container);
53
        }
54
55
        $appConfig = $container->get(Config::class)->all();
56
        $config = new MutableConfig($appConfig);
57
        $container->bind(Config::class, new ConfigProxy($config));
58
59
        // Here we initializing service providers and collect container bindings.
60
        foreach ($this->registerServiceProviders() as $providerClass) {
61
            $this->ensureServiceProvider($providerClass);
62
            /** @var AbstractServiceProvider $provider */
63
            $this->providers[] = $provider = new $providerClass($container->get(Container::class), $config);
64
            $provider->bind($container);
65
            $config->merge($appConfig);
66
        }
67
68
        // Booting service providers on by one allows
69
        foreach ($this->providers as $provider) {
70
            $provider->boot();
71
            $config->merge($appConfig);
72
        }
73
74
        return $container->get(Container::class);
75
    }
76
77
    /**
78
     * @inheritDoc
79
     */
80
    public function environment(): string
81
    {
82
        return getenv('APP_ENV') ?: 'local';
83
    }
84
85
    /**
86
     * @inheritDoc
87
     */
88
    public function isCli(): bool
89
    {
90
        return php_sapi_name() === 'cli';
91
    }
92
93
    /**
94
     * @return string
95
     */
96
    abstract public function rootPath(): string;
97
98
    /**
99
     * @inheritDoc
100
     */
101
    public function version(): string
102
    {
103
        return self::VERSION;
104
    }
105
106
    /**
107
     * Returns list of kernel bootstraps.
108
     * This is a main place to tune default kernel behavior.
109
     * Change carefully, as it may cause kernel failure.
110
     *
111
     * @return string[]
112
     */
113
    protected function getBootstraps(): array
114
    {
115
        $modules = [
116
            EnvironmentDetection::class,
117
            ConfigurationLoading::class,
118
            Logging::class,
119
            ErrorHandling::class,
120
        ];
121
122
        // Here we can add environment dependant modules.
123
        //if ($this->getEnvironment() === \Venta\Contracts\Kernel\Kernel::ENV_LOCAL) {
124
        //    $modules[] = 'KernelModule';
125
        //}
126
127
        return $modules;
128
    }
129
130
    /**
131
     * Invokes kernel bootstrap.
132
     * This is the point where specific kernel functionality defined by bootstrap is enabled.
133
     *
134
     * @param string $bootstrapClass
135
     * @param MutableContainer $container
136
     * @throws InvalidArgumentException
137
     */
138
    protected function invokeBootstrap(string $bootstrapClass, MutableContainer $container)
139
    {
140
        $this->ensureBootstrap($bootstrapClass);
141
142
        (new $bootstrapClass($container, $this))();
143
    }
144
145
    /**
146
     * Returns a list of all registered service providers.
147
     *
148
     * @return string[]
149
     */
150
    abstract protected function registerServiceProviders(): array;
151
152
    /**
153
     * Adds default service inflections.
154
     *
155
     * @param MutableContainer $container
156
     */
157
    private function addDefaultInflections(MutableContainer $container)
158
    {
159
        $container->inflect(ContainerAware::class, 'setContainer');
160
        $container->inflect(LoggerAwareInterface::class, 'setLogger');
161
        $container->inflect(ResponseFactoryAware::class, 'setResponseFactory');
162
    }
163
164
    /**
165
     * Binds default services to container.
166
     *
167
     * @param MutableContainer $container
168
     */
169
    private function bindDefaultServices(MutableContainer $container)
170
    {
171
        $container->bind(Container::class, new ContainerProxy($container));
172
        $container->bind(Kernel::class, $this);
173
    }
174
175
    /**
176
     * Ensures bootstrap class extends abstract kernel bootstrap.
177
     *
178
     * @param string $bootstrapClass
179
     * @throws InvalidArgumentException
180
     */
181
    private function ensureBootstrap(string $bootstrapClass)
182
    {
183
        if (!is_subclass_of($bootstrapClass, AbstractKernelBootstrap::class)) {
184
            throw new InvalidArgumentException(
185
                sprintf('Class "%s" must be a subclass of "%s".', $bootstrapClass, AbstractKernelBootstrap::class)
186
            );
187
        }
188
    }
189
190
    /**
191
     * Ensures service provider implements contract.
192
     *
193
     * @param string $providerClass
194
     * @throws InvalidArgumentException
195
     */
196
    private function ensureServiceProvider(string $providerClass)
197
    {
198
        if (!is_subclass_of($providerClass, AbstractServiceProvider::class)) {
199
            throw new InvalidArgumentException(
200
                sprintf('Class "%s" must be a subclass of "%s".', $providerClass, AbstractServiceProvider::class)
201
            );
202
        }
203
    }
204
205
    /**
206
     * Initializes service container.
207
     */
208
    private function initServiceContainer(): MutableContainer
209
    {
210
        /** @var MutableContainer $container */
211
        $container = new $this->containerClass;
212
213
        $this->bindDefaultServices($container);
214
        $this->addDefaultInflections($container);
215
216
        return $container;
217
    }
218
}