Completed
Push — master ( 2d0949...d58be5 )
by Neomerx
08:30
created

Application::createExceptionHandler()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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