Test Failed
Push — master ( 1c53d0...36f19e )
by Vsevolods
11:19
created

AbstractKernel::version()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
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\ConfigFactory;
8
use Venta\Contracts\Config\Config;
9
use Venta\Contracts\Config\ConfigFactory as ConfigFactoryContract;
10
use Venta\Contracts\Container\Container;
11
use Venta\Contracts\Container\ContainerAware;
12
use Venta\Contracts\Http\ResponseFactoryAware;
13
use Venta\Contracts\Kernel\Kernel;
14
use Venta\Contracts\ServiceProvider\ServiceProvider;
15
use Venta\Framework\Kernel\Bootstrap\ConfigurationLoading;
16
use Venta\Framework\Kernel\Bootstrap\EnvironmentDetection;
17
use Venta\Framework\Kernel\Bootstrap\ErrorHandling;
18
use Venta\Framework\Kernel\Bootstrap\Logging;
19
use Venta\Framework\Kernel\Resolver\ServiceProviderDependencyResolver;
20
use Venta\ServiceProvider\AbstractServiceProvider;
21
22
/**
23
 * Class AbstractKernel
24
 *
25
 * @package Venta\Framework\Kernel
26
 */
27
abstract class AbstractKernel implements Kernel
28
{
29
    const VERSION = '0.1.0';
30
31
    /**
32
     * Service container class name.
33
     *
34
     * @var string
35
     */
36
    protected $containerClass = \Venta\Container\Container::class;
37
38
    /**
39
     * @inheritDoc
40
     */
41
    public function boot(): Container
42
    {
43
        $container = $this->initServiceContainer();
44
45
        foreach ($this->getBootstraps() as $bootstrapClass) {
46
            $this->invokeBootstrap($bootstrapClass, $container);
47
        }
48
49
        /** @var Config $config */
50
        $config = $container->get(Config::class);
51
52
        // Here we boot service providers on by one. The correct order is ensured by resolver.
53
        /** @var ServiceProviderDependencyResolver $resolver */
54
        $resolver = $container->get(ServiceProviderDependencyResolver::class);
55
        foreach ($resolver($this->registerServiceProviders()) as $providerClass) {
56
            $this->bootServiceProvider($providerClass, $container, $config);
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 lock the configuration to prevent any changes during application run.
62
        $config->lock();
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 Config $baseConfig
102
     * @throws InvalidArgumentException
103
     */
104
    protected function bootServiceProvider(string $providerClass, Container $container, Config $baseConfig)
105
    {
106
        $this->ensureServiceProvider($providerClass);
107
108
        /** @var ServiceProvider $provider */
109
        $provider = new $providerClass($container, $baseConfig);
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
}