Passed
Push — feature/migration ( e715ab...e225e7 )
by Mathieu
22:09 queued 12:08
created

Suricate::listServices()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

208
            '!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

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