Completed
Push — master ( cce0a8...32e652 )
by Neomerx
04:48
created

Application::getSettingsPath()   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 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php namespace Limoncello\Application\Packages\Application;
2
3
/**
4
 * Copyright 2015-2017 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use Closure;
20
use ErrorException;
21
use Limoncello\Application\Contracts\Settings\CacheSettingsProviderInterface;
22
use Limoncello\Application\CoreSettings\CoreSettings;
23
use Limoncello\Application\ExceptionHandlers\DefaultHandler;
24
use Limoncello\Application\Settings\CacheSettingsProvider;
25
use Limoncello\Application\Settings\FileSettingsProvider;
26
use Limoncello\Application\Settings\InstanceSettingsProvider;
27
use Limoncello\Contracts\Application\ApplicationSettingsInterface as A;
28
use Limoncello\Contracts\Core\SapiInterface;
29
use Limoncello\Contracts\Exceptions\ExceptionHandlerInterface;
30
use Limoncello\Contracts\Provider\ProvidesSettingsInterface;
31
use Limoncello\Contracts\Settings\SettingsProviderInterface;
32
use Limoncello\Core\Application\Sapi;
33
use Limoncello\Core\Contracts\CoreSettingsInterface;
34
use Limoncello\Core\Reflection\ClassIsTrait;
35
use Throwable;
36
use Zend\Diactoros\Response\SapiEmitter;
37
use Limoncello\Contracts\Container\ContainerInterface as LimoncelloContainerInterface;
38
use Psr\Container\ContainerInterface as PsrContainerInterface;
39
use Limoncello\Container\Container;
40
41
/**
42
 * @package Limoncello\Application
43
 *
44
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
45
 */
46
class Application extends \Limoncello\Core\Application\Application
47
{
48
    use ClassIsTrait;
49
50
    /**
51
     * @var string
52
     */
53
    private $settingsPath;
54
55
    /**
56
     * @var callable|string|null
57
     */
58
    private $settingCacheMethod;
59
60
    /**
61
     * @param string                     $settingsPath
62
     * @param string|array|callable|null $settingCacheMethod
63
     * @param SapiInterface|null         $sapi
64
     */
65 3
    public function __construct(string $settingsPath, $settingCacheMethod = null, SapiInterface $sapi = null)
66
    {
67
        // The reason we do not use `callable` for the input parameter is that at the moment
68
        // of calling the callable might not exist. Therefore when created it will pass
69
        // `is_callable` check and will be used for getting the cached data.
70 3
        assert(is_null($settingCacheMethod) || is_string($settingCacheMethod) || is_array($settingCacheMethod));
71
72 3
        $this->settingsPath       = $settingsPath;
73 3
        $this->settingCacheMethod = $settingCacheMethod;
74
75 3
        $this->setSapi($sapi ?? new Sapi(new SapiEmitter()));
76
    }
77
78
    /**
79
     * Get container from application. If `method` and `path` are specified the container will be configured
80
     * not only with global container configurators but with route's one as well.
81
     *
82
     * @param string|null $method
83
     * @param string|null $path
84
     *
85
     * @return LimoncelloContainerInterface
86
     *
87
     * @SuppressWarnings(PHPMD.StaticAccess)
88
     */
89 1
    public function createContainer(string $method = null, string $path = null): LimoncelloContainerInterface
90
    {
91 1
        $container = $this->createContainerInstance();
92
93 1
        $settingsProvider = $this->createSettingsProvider();
94 1
        $container->offsetSet(SettingsProviderInterface::class, $settingsProvider);
95 1
        $container->offsetSet(CacheSettingsProviderInterface::class, $settingsProvider);
96
97 1
        $coreSettings = $settingsProvider->get(CoreSettingsInterface::class);
98
99 1
        $routeConfigurators = [];
100 1
        if (empty($method) === false && empty($path) === false) {
101 1
            list(, , , , , $routeConfigurators) = $this->initRouter($coreSettings)->match($method, $path);
102
        }
103
104
        // configure container
105 1
        $globalConfigurators = CoreSettings::getGlobalConfiguratorsFromData($coreSettings);
106 1
        $this->configureContainer($container, $globalConfigurators, $routeConfigurators);
107
108 1
        return $container;
109
    }
110
111
    /**
112
     * @return SettingsProviderInterface
113
     *
114
     * @SuppressWarnings(PHPMD.ElseExpression)
115
     */
116 1
    protected function createSettingsProvider(): SettingsProviderInterface
117
    {
118 1
        $provider = new CacheSettingsProvider();
119 1
        if (is_callable($method = $this->getSettingCacheMethod()) === true) {
120 1
            $data = call_user_func($method);
121 1
            $provider->unserialize($data);
122
        } else {
123
            $provider->setInstanceSettings($this->createFileSettingsProvider());
124
        }
125
126 1
        return $provider;
127
    }
128
129
    /**
130
     * @return InstanceSettingsProvider
131
     */
132
    protected function createFileSettingsProvider(): InstanceSettingsProvider
133
    {
134
        // Load all settings from path specified
135
        $provider = (new FileSettingsProvider())->load($this->getSettingsPath());
136
137
        // Application settings have a list of providers which might have additional settings to load
138
        $appSettings     = $provider->get(A::class);
139
        $providerClasses = $appSettings[A::KEY_PROVIDER_CLASSES];
140
        foreach ($this->selectClassImplements($providerClasses, ProvidesSettingsInterface::class) as $providerClass) {
141
            /** @var ProvidesSettingsInterface $providerClass */
142
            foreach ($providerClass::getSettings() as $setting) {
143
                $provider->register($setting);
144
            }
145
        }
146
147
        // App settings (paths, lists) --> core settings (container configurators, routes, middleware and etc).
148
        $routesPath     = $appSettings[A::KEY_ROUTES_PATH];
149
        $containersPath = $appSettings[A::KEY_CONTAINER_CONFIGURATORS_PATH];
150
        $coreSettings   = new CoreSettings($routesPath, $containersPath, $providerClasses);
151
152
        $provider->register($coreSettings);
153
154
        return $provider;
155
    }
156
157
    /**
158
     * @return LimoncelloContainerInterface
159
     */
160 1
    protected function createContainerInstance(): LimoncelloContainerInterface
161
    {
162 1
        return new Container();
163
    }
164
165
    /**
166
     * @inheritdoc
167
     */
168 1
    protected function setUpExceptionHandler(SapiInterface $sapi, PsrContainerInterface $container): void
169
    {
170 1
        error_reporting(E_ALL);
171
172 1
        set_exception_handler($this->createThrowableHandler($sapi, $container));
173 1
        set_error_handler($this->createErrorHandler($sapi, $container));
174 1
        register_shutdown_function($this->createFatalErrorHandler($container));
175
    }
176
177
    /**
178
     * @return string
179
     */
180
    protected function getSettingsPath(): string
181
    {
182
        return $this->settingsPath;
183
    }
184
185
    /**
186
     * @return callable|string|null
187
     */
188 1
    protected function getSettingCacheMethod()
189
    {
190 1
        return $this->settingCacheMethod;
191
    }
192
193
    /**
194
     * @param PsrContainerInterface $container
195
     *
196
     * @return ExceptionHandlerInterface
197
     */
198 1
    protected function createExceptionHandler(PsrContainerInterface $container): ExceptionHandlerInterface
199
    {
200 1
        $has     = $container->has(ExceptionHandlerInterface::class);
201 1
        $handler = $has === true ? $container->get(ExceptionHandlerInterface::class) : new DefaultHandler();
202
203 1
        return $handler;
204
    }
205
206
    /**
207
     * @param SapiInterface         $sapi
208
     * @param PsrContainerInterface $container
209
     *
210
     * @return Closure
211
     */
212
    protected function createThrowableHandler(SapiInterface $sapi, PsrContainerInterface $container): Closure
213
    {
214 1
        return function (Throwable $throwable) use ($sapi, $container) {
215 1
            $handler = $this->createExceptionHandler($container);
216 1
            $handler->handleThrowable($throwable, $sapi, $container);
217 1
        };
218
    }
219
220
    /**
221
     * @param SapiInterface         $sapi
222
     * @param PsrContainerInterface $container
223
     *
224
     * @return Closure
225
     */
226
    protected function createErrorHandler(SapiInterface $sapi, PsrContainerInterface $container): Closure
227
    {
228 1
        return function ($severity, $message, $fileName, $lineNumber) use ($sapi, $container) {
229 1
            $errorException = new ErrorException($message, 0, $severity, $fileName, $lineNumber);
230 1
            $handler = $this->createThrowableHandler($sapi, $container);
231 1
            $handler($errorException);
232 1
            throw $errorException;
233 1
        };
234
    }
235
236
    /**
237
     * @param PsrContainerInterface $container
238
     *
239
     * @return Closure
240
     */
241
    protected function createFatalErrorHandler(PsrContainerInterface $container): Closure
242
    {
243 1
        return function () use ($container) {
244 1
            $error = $this->getLastError();
245 1
            if ($error !== null && ((int)$error['type'] & (E_ERROR | E_COMPILE_ERROR))) {
246 1
                $handler = $this->createExceptionHandler($container);
247 1
                $handler->handleFatal($error, $container);
248
            }
249 1
        };
250
    }
251
252
    /**
253
     * It is needed for mocking while testing.
254
     *
255
     * @return array|null
256
     */
257 1
    protected function getLastError(): ?array
258
    {
259 1
        return error_get_last();
260
    }
261
}
262