Test Failed
Push — master ( eb7240...f4fe2e )
by Julien
05:06
created

Bootstrap::modules()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
cc 1
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * This file is part of the Zemit Framework.
4
 *
5
 * (c) Zemit Team <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE.txt
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Zemit;
12
13
use Dotenv\Environment\DotenvFactory;
14
use Dotenv\Loader;
15
use Phalcon\Di;
16
use Phalcon\Di\FactoryDefault;
17
use Phalcon\Http\Response;
18
use Phalcon\Http\ResponseInterface;
19
use Phalcon\Text;
20
use Phalcon\Events;
21
22
use Zemit\Bootstrap\Prepare;
23
use Zemit\Bootstrap\Config;
24
use Zemit\Bootstrap\Services;
25
use Zemit\Bootstrap\Modules;
26
use Zemit\Bootstrap\Router;
27
use Zemit\Events\EventsAwareTrait;
28
use Zemit\Mvc\Application;
29
use Zemit\Cli\Console;
30
use Zemit\Cli\Router as CliRouter;
31
32
use Dotenv\Dotenv;
33
use Docopt;
34
35
/**
36
 * Class Bootstrap
37
 * Zemit Core's Bootstrap for the MVC Application & CLI Console mode
38
 *
39
 * @author Julien Turbide <[email protected]>
40
 * @copyright Zemit Team <[email protected]>
41
 *
42
 * @since 1.0
43
 * @version 1.0
44
 *
45
 * @package Zemit
46
 */
47
class Bootstrap
48
{
49
    use EventsAwareTrait;
50
51
    /**
52
     * Bootstrap modes
53
     */
54
    const MODE_CLI = 'console';
55
    const MODE_DEFAULT = 'default';
56
    const MODE_CONSOLE = self::MODE_CLI;
57
58
    /**
59
     * Ideally, only the config service provider should be added here, then it will load other service from itself
60
     * You can also add new Service Providers here if it's absolutely required to be loaded earlier before
61
     * @var array abstract => concrete
62
     */
63
    public $providers = [
64
        Provider\Config\ServiceProvider::class => Provider\Config\ServiceProvider::class,
65
    ];
66
67
    /**
68
     * Bootstrap mode
69
     * @var string
70
     */
71
    public $mode;
72
73
    /**
74
     * Bootstrap console args
75
     * @var array
76
     */
77
    public $args;
78
79
    /**
80
     * Dependencies
81
     * @var FactoryDefault|FactoryDefault\Cli
82
     */
83
    public $di;
84
85
    /**
86
     * @var Dotenv
87
     */
88
    public $dotenv;
89
90
    /**
91
     * @var Prepare
92
     */
93
    public $prepare;
94
95
    /**
96
     * @var Config
97
     */
98
    public $config;
99
100
    /**
101
     * @var Services
102
     */
103
    public $services;
104
105
    /**
106
     * @var Application|\Phalcon\Cli\Console
107
     */
108
    public $application;
109
110
    /**
111
     * @var Modules
112
     */
113
    public $modules;
114
115
    /**
116
     * @var Router
117
     */
118
    public $router;
119
120
    /**
121
     * @var Debug
122
     */
123
    public $debug;
124
125
    /**
126
     * @var bool|ResponseInterface
127
     */
128
    public $response;
129
130
    /**
131
     * @var Docopt
132
     */
133
    public $docopt;
134
135
    /**
136
     * @var string
137
     */
138
    public $consoleDoc = <<<DOC
139
Zemit Console
140
141
Usage:
142
  zemit <module> <task> [<action>] [--help | --quiet | --verbose] [--debug] [--format=<format>] [<args>...] [-c <fds>=<value>]
143
  zemit (-h | --help)
144
  zemit (-v | --version)
145
  zemit (-i | --info)
146
147
Options:
148
  -c <fds>=<value>       test
149
  -h --help               show this help message
150
  -v --version            print version number
151
  -i --info               print information
152
  -q --quiet              suppress output
153
  -V --verbose            increase verbosity
154
  -d --debug              enable debug mode
155
  --format=<format>       change output returned value format (json, xml, serialized, raw, dump)
156
157
The most commonly used zemit commands are:
158
   deployment        Populate the database
159
DOC;
160
161
    /**
162
     * Bootstrap constructor.
163
     * Setup the di, env, app, config, services, applications, modules and then the router
164
     *
165
     * @param string $mode Mode for the application 'default' 'console'
166
     *
167
     * @throws Exception
168
     */
169
    public function __construct($mode = self::MODE_DEFAULT)
170
    {
171
        $this->mode = $mode;
172
        $this->setEventsManager(new Events\Manager());
173
        $this->initialize();
174
        $this->dotenv();
175
        $this->docopt();
176
        $this->di();
177
        $this->prepare();
178
        $this->register();
179
        $this->config();
180
        $this->debug();
181
        $this->services();
182
        $this->application();
183
        $this->modules();
184
//        $this->router(); // using serviceProvider now
185
    }
186
187
    /**
188
     * Initialisation
189
     */
190
    public function initialize()
191
    {
192
    }
193
194
    /**
195
     * Prepare the DI including itself (Bootstrap) and setup as default DI
196
     * Also use the cli factory default for console mode
197
     * @return FactoryDefault|FactoryDefault\Cli Return a factory default DI
198
     * @throws Exception
199
     */
200
    public function di()
201
    {
202
        // Use the phalcon cli factory default for console mode
203
        $this->fireSet(
204
            $this->di,
205
            $this->isConsole() ?
206
            FactoryDefault\Cli::class :
207
            FactoryDefault::class,
208
            [],
209
            function (Bootstrap $bootstrap) {
210
                // Register bootstrap itself
211
                $this->di->setShared('bootstrap', $bootstrap);
212
213
                // Set as the default DI
214
                Di::setDefault($this->di);
215
            }
216
        );
217
218
        return $this->di;
219
    }
220
221
    /**
222
     * Reading .env file
223
     * @return Dotenv
224
     * @throws Exception
225
     */
226
    public function dotenv()
227
    {
228
        try {
229
            $loader = new Loader([dirname(APP_PATH)], new DotenvFactory(), true);
230
            $this->fireSet($this->dotenv, Dotenv::class, [$loader], function (Bootstrap $bootstrap) {
231
                $bootstrap->dotenv->load();
232
            });
233
        } catch (\Dotenv\Exception\InvalidPathException|\Dotenv\Exception\InvalidFileException $e) {
234
            // just ignore and run the application anyway
235
        }
236
237
        return $this->dotenv;
238
    }
239
240
    /**
241
     * Get arguments from command line interface (cli / console)
242
     * @return Docopt
243
     * @throws Exception
244
     */
245
    public function docopt()
246
    {
247
        return $this->fireSet($this->docopt, Docopt::class, [], function (Bootstrap $bootstrap) {
248
            if ($this->isConsole()) {
249
                $docoptResponse = $bootstrap->docopt->handle($bootstrap->consoleDoc, [
250
                    'argv' => array_slice($_SERVER['argv'], 1),
251
                    'optionsFirst' => true,
252
                ]);
253
                $bootstrap->args = array_merge($bootstrap->args ?? [], $docoptResponse->args);
254
            }
255
        });
256
    }
257
258
    /**
259
     * Preparing some native PHP related stuff
260
     * @return Prepare
261
     * @throws Exception
262
     */
263
    public function prepare()
264
    {
265
        return $this->fireSet($this->prepare, Prepare::class);
266
    }
267
268
    /**
269
     * Registering bootstrap providers
270
     */
271
    public function register(array &$providers = null)
272
    {
273
        $providers ??= $this->providers;
274
275
        foreach ($providers as $key => $provider) {
276
            if (is_string($provider) && class_exists($provider)) {
277
                $provider = new $provider($this->di);
278
                $this->di->register($provider);
279
                if ($this->di->has($provider->getName())) {
280
                    $this->providers[$key] = $this->di->get($provider->getName());
281
                }
282
            }
283
        }
284
285
        return $this->providers;
286
    }
287
288
    /**
289
     * Prepare the config service
290
     * - Fire events (before & after)
291
     * - Apply current bootstrap mode ('default', 'console')
292
     * - Merge with current environment config
293
     * @return Config
294
     * @throws Exception
295
     */
296
    public function config()
297
    {
298
        if ($this->di->has('config')) {
299
            $this->config = $this->di->get('config');
300
        }
301
        return $this->fireSet($this->config, Config::class);
302
    }
303
304
    /**
305
     * @return Debug
306
     * @throws Exception
307
     */
308
    public function debug()
309
    {
310
        return $this->fireSet($this->debug, Debug::class, [], function (Bootstrap $bootstrap) {
311
            $config = $bootstrap->config->debug->toArray();
312
            $bootstrap->prepare->debug($bootstrap->config);
313
314
            // @todo review on phalcon5 php8+ because phalcon4 php8+ debug is doing cyclic error
315
            $cyclicError =
316
                version_compare(PHP_VERSION, '8.0.0', '>=') &&
317
                version_compare(\Phalcon\Version::get(), '5.0.0', '<');
318
319
            if (!$this->isConsole() && !$cyclicError) {
320
                if ($bootstrap->config->app->get('debug') || $config['enable']) {
321
                    $bootstrap->debug->listen($config['exception'] ?? true, $config['lowSeverity'] ?? false);
322
                    $bootstrap->debug->setBlacklist($config['blacklist'] ?? []);
323
                    $bootstrap->debug->setShowFiles($config['showFiles'] ?? true);
324
                    $bootstrap->debug->setShowBackTrace($config['showBackTrace'] ?? true);
325
                    $bootstrap->debug->setShowFileFragment($config['showFileFragment'] ?? true);
326
                    if (is_string($config['uri'])) {
327
                        $bootstrap->debug->setUri($config['uri']);
328
                    }
329
                }
330
            }
331
        });
332
    }
333
334
    /**
335
     * Prepare procedural services way
336
     * - Fire events (before & after)
337
     * - Pass the current Di object as well as the current Config object
338
     * @return Services
339
     * @throws Exception
340
     */
341
    public function services()
342
    {
343
        return $this->fireSet($this->services, Services::class, [$this->di, $this->config]);
344
    }
345
346
    /**
347
     * Prepare the application
348
     * - Fire events (before & after)
349
     * - Pass the current Di object
350
     * - Depends on the current bootstrap mode ('default', 'console')
351
     * @return Console|Application
352
     * @throws Exception
353
     */
354
    public function application()
355
    {
356
        return $this->fireSet($this->application, $this->isConsole() ? Console::class : Application::class, [$this->di]);
357
    }
358
359
    /**
360
     * Prepare the application for modules
361
     * - Fire events (before & after)
362
     * - Pass the current Application object
363
     * @return Modules
364
     * @throws Exception
365
     */
366
    public function modules()
367
    {
368
        return $this->fireSet($this->modules, Modules::class, [$this->application]);
369
    }
370
371
    /**
372
     * Prepare the router
373
     * @return Router
374
     * @throws Exception
375
     */
376
    public function router()
377
    {
378
        if ($this->di->has('router')) {
379
            $this->config = $this->di->get('router');
380
        }
381
        return $this->fireSet($this->router, $this->isConsole() ? CliRouter::class : Router::class, [true, $this->application]);
382
    }
383
    
384
    /**
385
     * Run the application
386
     * - Fire events (before run & after run)
387
     * - Handle both console and default application
388
     * - Return response string
389
     * @return false|string|null
390
     * @throws \Exception
391
     */
392
    public function run()
393
    {
394
        $this->fire('beforeRun');
395
        
396
        if (isset($this->application)) {
397
            
398
            if ($this->application instanceof Console) {
399
                
400
                // run console cli
401
                $content = $this->handleConsole($this->application);
402
            }
403
            
404
            else if ($this->application instanceof Application) {
405
                
406
                // run mvc http
407
                $content = $this->handleApplication($this->application);
408
            }
409
            else {
410
                throw new \Exception('Application \'' . get_class($this->application) . '\' not supported', 400);
411
            }
412
        }
413
        else {
414
            throw new \Exception('Application not found', 404);
415
        }
416
        
417
        $this->fire('afterRun', $content);
0 ignored issues
show
Bug introduced by
It seems like $content can also be of type string; however, parameter $data of Zemit\Bootstrap::fire() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

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

417
        $this->fire('afterRun', /** @scrutinizer ignore-type */ $content);
Loading history...
418
        return $content;
419
    }
420
    
421
    /**
422
     * @param Console $console
423
     * @return false|string|null
424
     */
425
    public function handleConsole(Console $console)
426
    {
427
        $response = null;
428
        
429
        try {
430
            ob_start();
431
            $console->handle($this->getArguments());
432
            $response = ob_get_clean();
433
        }
434
        catch (\Zemit\Exception $e) {
435
            new Cli\ExceptionHandler($e);
436
        }
437
        catch (\Exception $exception) {
438
            new Cli\ExceptionHandler($exception);
439
        }
440
        catch (\Throwable $throwable) {
441
            new Cli\ExceptionHandler($throwable);
442
        }
443
        
444
        return $response;
445
    }
446
    
447
    /**
448
     * @param Application $application
449
     * @return string
450
     * @throws \Exception
451
     */
452
    public function handleApplication(Application $application): string
453
    {
454
        $this->response = $application->handle($_SERVER['REQUEST_URI'] ?? '/');
455
        return $this->response ? $this->response->getContent() : '';
456
    }
457
458
    /**
459
     * Get & format arguments from the $this->args property
460
     * @return array Key value pair, human-readable
461
     */
462
    public function getArguments() : array
463
    {
464
        $arguments = [];
465
        if (!empty($this->args)) {
466
            foreach ($this->args as $key => $value) {
467
                if (preg_match('/(<(.*?)>|\-\-(.*))/', $key, $match)) {
468
                    $key = lcfirst(Text::camelize(Text::uncamelize(array_pop($match))));
469
                    $arguments[$key] = $value;
470
                }
471
            }
472
        }
473
        return $arguments;
474
    }
475
476
    /**
477
     * Return true if the bootstrap mode is set to 'console'
478
     * @return bool Console mode
479
     */
480
    public function isConsole() : bool
481
    {
482
        return $this->getMode() === self::MODE_CONSOLE;
483
    }
484
485
    /**
486
     * Return the raw bootstrap mode, should be either 'console' or 'default'
487
     * @return string Bootstrap mode
488
     */
489
    public function getMode() : string
490
    {
491
        return $this->mode;
492
    }
493
}
494