Passed
Push — master ( a5ef88...f5262c )
by Mathieu
14:16
created

Suricate::loadConfig()   B

Complexity

Conditions 9
Paths 6

Size

Total Lines 44
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 27.6489

Importance

Changes 8
Bugs 2 Features 0
Metric Value
cc 9
eloc 28
c 8
b 2
f 0
nc 6
nop 0
dl 0
loc 44
ccs 12
cts 31
cp 0.3871
crap 27.6489
rs 8.0555
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Suricate;
6
7
use InvalidArgumentException;
8
use Suricate\Cache\Apc as CacheApc;
9
use Suricate\Cache\File as CacheFile;
10
use Suricate\Cache\Memcache as CacheMemcache;
11
use Suricate\Cache\Memcached as CacheMemcached;
12
use Suricate\Cache\Redis as CacheRedis;
13
use Suricate\Event\EventDispatcher;
14
use Suricate\Session\Native as SessionNative;
15
16
/**
17
 * Suricate - Another micro PHP framework
18
 *
19
 * @author      Mathieu LESNIAK <[email protected]>
20
 * @copyright   2013-2023 Mathieu LESNIAK
21
 * @version     0.5.0
22
 * @package     Suricate
23
 *
24
 * @method static \Suricate\App                     App($newInstance = false)             Get instance of App service
25
 * @method static \Suricate\Cache                   Cache($newInstance = false)           Get instance of Cache service
26
 * @method static \Suricate\CacheMemcache           CacheMemcache($newInstance = false)   Get instance of CacheMemcache service
27
 * @method static \Suricate\CacheMemcached          CacheMemcached($newInstance = false)  Get instance of CacheMemcached service
28
 * @method static \Suricate\CacheRedis              CacheRedis($newInstance = false)      Get instance of CacheRedis service
29
 * @method static \Suricate\CacheApc                CacheApc($newInstance = false)        Get instance of CacheApc service
30
 * @method static \Suricate\CacheFile               CacheFile($newInstance = false)       Get instance of CacheFile service
31
 * @method static \Suricate\Curl                    Curl($newInstance = false)            Get instance of Curl service
32
 * @method static \Suricate\Database                Database($newInstance = false)        Get instance of Database service
33
 * @method static \Suricate\Error                   Error($newInstance = false)           Get instance of Error service
34
 * @method static \Suricate\Event\EventDispatcher   EventDispatcher($newInstance = false) Get instance of EventDispatcher service
35
 * @method static \Suricate\I18n                    I18n($newInstance = false)            Get instance of I18n service
36
 * @method static \Suricate\Logger                  Logger($newInstance = false)          Get instance of Logger service
37
 * @method static \Suricate\Request                 Request($newInstance = false)         Get instance of Request service
38
 * @method static \Suricate\Request                 Response($newInstance = false)        Get instance of Request/Response service
39
 * @method static \Suricate\Router                  Router($newInstance = false)          Get instance of Router service
40
 * @method static \Suricate\Session                 Session($newInstance = false)         Get instance of Session service
41
 * @method static \Suricate\SessionNative           SessionNative($newInstance = false)   Get instance of Session service
42
 * @method static \Suricate\SessionCookie           SessionCookie($newInstance = false)   Get instance of Session service
43
 * @method static \Suricate\SessionMemcache         SessionMemcache($newInstance = false) Get instance of Session service
44
 */
45
46
class Suricate
47
{
48
    const VERSION = '0.5.0';
49
50
    const CONF_DIR = '/conf/';
51
52
    private $config = [];
53
    private $configFile = [];
54
55
    private $useAutoloader = false;
56
57
    private static $servicesContainer;
58
    private static $servicesRepository;
59
60
    private $servicesList = [
61
        'App' => App::class,
62
        'Cache' => Cache::class,
63
        'CacheMemcache' => CacheMemcache::class,
64
        'CacheMemcached' => CacheMemcached::class,
65
        'CacheRedis' => CacheRedis::class,
66
        'CacheApc' => CacheApc::class,
67
        'CacheFile' => CacheFile::class,
68
        'Curl' => Curl::class,
69
        'Database' => Database::class,
70
        'Error' => Error::class,
71
        'EventDispatcher' => EventDispatcher::class,
72
        'I18n' => I18n::class,
73
        'Logger' => Logger::class,
74
        'Request' => Request::class,
75
        'Response' => Request::class,
76
        'Router' => Router::class,
77
        'Session' => Session::class,
78
        'SessionNative' => SessionNative::class,
79
        'SessionCookie' => '\Suricate\Session\Cookie',
80
        'SessionMemcache' => '\Suricate\Session\Memcache'
81
    ];
82 8
83
    /**
84 8
     * Suricate contructor
85 6
     *
86
     * @param array $paths Application paths
87
     * @param string|array|null $configFile path of configuration file(s)
88
     *
89 8
     * @SuppressWarnings(PHPMD.StaticAccess)
90
     */
91 8
    public function __construct($paths = [], $configFile = null)
92 8
    {
93
        if ($configFile !== null) {
94 8
            $this->setConfigFile($configFile);
95
        }
96
97
        // Load helpers
98
        require_once __DIR__ . DIRECTORY_SEPARATOR . 'Helper.php';
99
100
        $this->loadConfig();
101 8
        $this->setAppPaths($paths);
102 8
103 8
        if ($this->useAutoloader) {
104
            // Configure autoloader
105 8
            require_once __DIR__ . DIRECTORY_SEPARATOR . 'AutoLoader.php';
106
            AutoLoader::register();
107 8
        }
108 8
109
        // Define error handler
110
        set_exception_handler(['\Suricate\Error', 'handleException']);
111
        set_error_handler(['\Suricate\Error', 'handleError']);
112
        register_shutdown_function(['\Suricate\Error', 'handleShutdownError']);
113
114
        self::$servicesRepository = new Container();
115 1
116
        $this->initServices();
117 1
    }
118
119
    /**
120 8
     * Get app configuration
121
     *
122 8
     * @return array
123
     */
124
    public function getConfig(): array
125
    {
126 8
        return $this->config;
127
    }
128
129
    private function setAppPaths($paths = [])
130
    {
131
        foreach ($paths as $key => $value) {
132 8
            $this->config['App']['path.' . $key] = realpath($value);
133
        }
134 8
135
        return $this;
136 8
    }
137 8
    /**
138
     * Initialize Framework services
139
     * @return void
140
     */
141
    private function initServices()
142
    {
143 8
        self::$servicesRepository->setWarehouse($this->servicesList);
144
145 8
        self::$servicesRepository['Request']->parse();
146 8
        if (isset($this->config['App']['locale'])) {
147 8
            $this->config['I18n'] = [
148 8
                'locale' => $this->config['App']['locale']
149
            ];
150
        }
151
152
        // Define constants
153
        if (isset($this->config['Constants'])) {
154 8
            foreach (
155
                $this->config['Constants']
156
                as $constantName => $constantValue
157
            ) {
158 8
                $constantName = strtoupper($constantName);
159
                define($constantName, $constantValue);
160 1
            }
161
        }
162
163 1
        // first sync, && init, dependency to Suricate::request
164 1
        self::$servicesContainer = clone self::$servicesRepository;
165
166
        foreach (array_keys($this->servicesList) as $serviceName) {
167
            if (isset($this->config[$serviceName])) {
168
                self::$servicesRepository[$serviceName]->configure(
169 8
                    $this->config[$serviceName]
170 8
                );
171
172 1
                /**
173
                 TODO : remove sync in service creation
174 1
                 */
175
                self::$servicesContainer = clone self::$servicesRepository;
176
            }
177 6
        }
178
179 6
        // final sync, repository is complete
180 6
        self::$servicesContainer = clone self::$servicesRepository;
181 6
    }
182
183
    public function hasService(string $serviceName): bool
184
    {
185 6
        return isset(self::$servicesContainer[$serviceName]);
186
    }
187
188
    private function setConfigFile($configFile)
189
    {
190
        foreach ((array) $configFile as $file) {
191 8
            if (is_file($file)) {
192
                $this->configFile[] = $file;
193 8
            }
194 8
        }
195 6
196 6
        return $this;
197 6
    }
198 6
199 6
    private function parseYamlConfig($filename) {
200
        return yaml_parse_file($filename, 0, $ndocs,
201
        [
202
            '!include' => function($value, $tag, $flags) use($filename)
0 ignored issues
show
Unused Code introduced by
The parameter $flags is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

202
            '!include' => function($value, $tag, /** @scrutinizer ignore-unused */ $flags) use($filename)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $tag is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

202
            '!include' => function($value, /** @scrutinizer ignore-unused */ $tag, $flags) use($filename)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
203
            {
204 6
                $directory = dirname($filename);
205 5
                return $this->parseYamlConfig("$directory/$value");
206 5
            }
207
        ]);
208
    }
209
    /**
210
     * Load framework configuration from ini file
211
     * @return void
212
     */
213
    private function loadConfig()
214
    {
215
        $userConfig = [];
216
        if (count($this->configFile)) {
217 8
            $userConfig = [];
218 8
            foreach ($this->configFile as $configFile) {
219
                if (stripos($configFile, 'yml') !== false) {
220
                    $userConfig = array_merge_recursive($userConfig, $this->parseYamlConfig($configFile));
221
                } else {
222
                    $userConfig = array_merge_recursive(
223
                        $userConfig,
224
                        (array) parse_ini_file($configFile, true, INI_SCANNER_TYPED)
225 8
                    );
226
                }
227
            }
228
229 8
            // Advanced ini parsing, split key with '.' into subarrays
230
            foreach ($userConfig as $section => $configData) {
231 8
                foreach ($configData as $name => $value) {
232 8
                    if (stripos($name, '.') !== false) {
233
                        $subkeys = explode('.', $name);
234 8
                        unset($userConfig[$section][$name]);
235
                        $str =
236 8
                            "['" . implode("']['", $subkeys) . "'] = \$value;";
237 8
                        eval("\$userConfig[\$section]" . $str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
238 8
                    }
239 8
                }
240
            }
241 8
        }
242
243
        foreach ($this->getDefaultConfig() as $context => $directives) {
244
            if (isset($userConfig[$context])) {
245
                $this->config[$context] = array_merge(
246
                    $directives,
247
                    $userConfig[$context]
248
                );
249
                unset($userConfig[$context]);
250
            } else {
251
                $this->config[$context] = $directives;
252
            }
253
        }
254
255
        $this->config = array_merge($this->config, $userConfig);
256
        $this->configureAppMode();
257
    }
258
259
    private function configureAppMode()
260
    {
261
        $errorReporting = true;
262
        $errorDumpContext = true;
263
        $logLevel = Logger::LOGLEVEL_WARN;
264
        $logFile = 'php://stdout';
265
266
        if (isset($this->config['App']['mode'])) {
267
            switch ($this->config['App']['mode']) {
268
                case App::DEVELOPMENT_MODE:
269 8
                    $errorReporting = true;
270
                    $errorDumpContext = true;
271
                    $logLevel = Logger::LOGLEVEL_INFO;
272 8
                    $logFile = 'php://stdout';
273
                    break;
274
                case App::DEBUG_MODE:
275 8
                    $errorReporting = true;
276
                    $errorDumpContext = true;
277
                    $logLevel = Logger::LOGLEVEL_DEBUG;
278 8
                    $logFile = 'php://stdout';
279
                    break;
280
                case App::PRELIVE_MODE:
281
                    $errorReporting = true;
282 8
                    $errorDumpContext = false;
283 8
                    $logLevel = Logger::LOGLEVEL_WARN;
284 8
                    $logFile = 'php://stderr';
285 8
                    break;
286 8
                case App::PRODUCTION_MODE:
287
                    $errorReporting = false;
288
                    $errorDumpContext = false;
289
                    $logLevel = Logger::LOGLEVEL_WARN;
290
                    $logFile = 'php://stderr';
291 8
                    break;
292
            }
293
        }
294 8
        if (isset($this->config['Logger']['level'])) {
295
            $logLevel = $this->config['Logger']['level'];
296
        }
297
        if (isset($this->config['Logger']['logfile'])) {
298
            $logFile = $this->config['Logger']['logfile'];
299
        }
300
        if (isset($this->config['Error']['report'])) {
301
            $errorReporting = $this->config['Error']['report'];
302
        }
303
        if (isset($this->config['Error']['dumpContext'])) {
304
            $errorDumpContext = $this->config['Error']['dumpContext'];
305
        }
306
307 19
        $this->config['Logger']['level'] = $logLevel;
308
        $this->config['Logger']['logfile'] = $logFile;
309 19
        $this->config['Error']['report'] = $errorReporting;
310 1
        $this->config['Error']['dumpContext'] = $errorDumpContext;
311
    }
312
    /**
313 19
     * Default setup template
314
     * @return array setup
315
     */
316
    private function getDefaultConfig()
317
    {
318
        return [
319
            'Router' => [],
320
            'Logger' => [
321
                'enabled' => true
322
            ],
323
            'App' => ['base_uri' => '/']
324
        ];
325
    }
326
327
    public function run()
328
    {
329
        self::$servicesContainer['Router']->doRouting();
330
    }
331
332
    public static function __callStatic($name, $arguments)
333
    {
334
        if (isset($arguments[0]) && $arguments[0] === true) {
335
            return clone self::$servicesRepository[$name];
336
        }
337
338
        return self::$servicesContainer[$name];
339
    }
340
341
    public function registerService(string $serviceName, string $serviceClass): self
342
    {
343
        if (isset(self::$servicesContainer[$serviceName])) {
344
            throw new InvalidArgumentException('Service ' . $serviceName . ' already registered');
345
        }
346
347
        self::$servicesContainer->addToWarehouse($serviceName, $serviceClass);
348
        self::$servicesRepository->addToWarehouse($serviceName, $serviceClass);
349
        if (isset($this->config[$serviceName])) {
350
            self::$servicesContainer[$serviceName]->configure(
351
                $this->config[$serviceName]
352
            );
353
        }
354
        return $this;
355
    }
356
}
357