Passed
Push — master ( e5b7b4...ec2be8 )
by Julien
14:08
created

Bootstrap::di()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2.0017

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 9
c 3
b 1
f 0
dl 0
loc 19
ccs 12
cts 13
cp 0.9231
rs 9.9666
cc 2
nc 1
nop 0
crap 2.0017
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;
0 ignored issues
show
Bug introduced by
The type Dotenv\Environment\DotenvFactory was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
use Zemit\Utils\Env;
35
36
/**
37
 * Class Bootstrap
38
 * Zemit Core's Bootstrap for the MVC Application & CLI Console mode
39
 *
40
 * @author Julien Turbide <[email protected]>
41
 * @copyright Zemit Team <[email protected]>
42
 *
43
 * @since 1.0
44
 * @version 1.0
45
 *
46
 * @package Zemit
47
 */
48
class Bootstrap
49
{
50
    use EventsAwareTrait;
51
52
    /**
53
     * Bootstrap modes
54
     */
55
    const MODE_CLI = 'console';
56
    const MODE_DEFAULT = 'default';
57
    const MODE_CONSOLE = self::MODE_CLI;
58
59
    /**
60
     * Ideally, only the config service provider should be added here, then it will load other service from itself
61
     * You can also add new Service Providers here if it's absolutely required to be loaded earlier before
62
     * @var ?array abstract => concrete
63
     */
64
    public ?array $providers = [
65
        Provider\Config\ServiceProvider::class => Provider\Config\ServiceProvider::class,
66
    ];
67
68
    /**
69
     * Bootstrap mode
70
     * @var string
71
     */
72
    public string $mode;
73
74
    /**
75
     * Bootstrap console args
76
     * - This variable is currently filled by `docopt`
77
     * @var ?array
78
     */
79
    public ?array $args;
80
    
81
    /**
82
     * Dependencies
83
     * @var null|string|FactoryDefault|FactoryDefault\Cli
84
     */
85
    public $di;
86
87
    /**
88
     * @var null|string|Dotenv
89
     */
90
    public $dotenv;
91
    
92
    /**
93
     * @var null|string|Docopt
94
     */
95
    public $docopt;
96
97
    /**
98
     * @var null|string|Prepare
99
     */
100
    public $prepare;
101
102
    /**
103
     * @var null|string|Config
104
     */
105
    public $config;
106
107
    /**
108
     * @var null|string|Services
109
     */
110
    public $services;
111
112
    /**
113
     * @var null|string|Application|\Phalcon\Cli\Console
114
     */
115
    public $application;
116
117
    /**
118
     * @var null|string|Modules
119
     */
120
    public $modules;
121
122
    /**
123
     * @var null|string|Router
124
     */
125
    public $router;
126
127
    /**
128
     * @var null|string|Debug
129
     */
130
    public $debug;
131
132
    /**
133
     * @var bool|ResponseInterface
134
     */
135
    public $response;
136
137
    /**
138
     * @var string
139
     */
140
    public string $consoleDoc = <<<DOC
141
Zemit Console
142
143
Usage:
144
  zemit <module> <task> [<action>] [--help | --quiet | --verbose] [--debug] [--format=<format>] [<args>...] [-c <fds>=<value>]
145
  zemit (-h | --help)
146
  zemit (-v | --version)
147
  zemit (-i | --info)
148
149
Options:
150
  -c <key>=<value>       test
151
  -h --help               show this help message
152
  -v --version            print version number
153
  -i --info               print information
154
  -q --quiet              suppress output
155
  -V --verbose            increase verbosity
156
  -d --debug              enable debug mode
157
  --format=<format>       change output returned value format (json, xml, serialized, raw, dump)
158
159
The most commonly used zemit commands are:
160
   deployment        Populate the database
161
DOC;
162
163
    /**
164
     * Bootstrap constructor.
165
     * Setup the di, env, app, config, services, applications, modules and then the router
166
     *
167
     * @param string $mode Mode for the application 'default' 'console'
168
     *
169
     * @throws Exception
170
     */
171 7
    public function __construct(string $mode = self::MODE_DEFAULT)
172
    {
173 7
        $this->mode = $mode;
174 7
        $this->setEventsManager(new Events\Manager());
175 7
        $this->initialize();
176 7
        $this->dotenv();
177 7
        $this->docopt();
178 7
        $this->di();
179 7
        $this->prepare();
180 7
        $this->register();
181 7
        $this->config();
182 7
        $this->debug();
183 7
        $this->services();
184 7
        $this->application();
185 7
        $this->modules();
186 7
        $this->router();
187
    }
188
189
    /**
190
     * Initialisation
191
     */
192 7
    public function initialize()
193
    {
194 7
    }
195
196
    /**
197
     * Prepare the DI including itself (Bootstrap) and setup as default DI
198
     * Also use the cli factory default for console mode
199
     * @return FactoryDefault|FactoryDefault\Cli Return a factory default DI
200
     * @throws Exception
201
     */
202 7
    public function di()
203
    {
204
        // Use the phalcon cli factory default for console mode
205 7
        $this->fireSet(
206 7
            $this->di,
207 7
            $this->isConsole() ?
208
            FactoryDefault\Cli::class :
209 7
            FactoryDefault::class,
210 7
            [],
211 7
            function (Bootstrap $bootstrap) {
212
                // Register bootstrap itself
213 7
                $this->di->setShared('bootstrap', $bootstrap);
0 ignored issues
show
Bug introduced by
The method setShared() does not exist on null. ( Ignorable by Annotation )

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

213
                $this->di->/** @scrutinizer ignore-call */ 
214
                           setShared('bootstrap', $bootstrap);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
214
215
                // Set as the default DI
216 7
                Di::setDefault($this->di);
0 ignored issues
show
Bug introduced by
It seems like $this->di can also be of type null and string; however, parameter $container of Phalcon\Di::setDefault() does only seem to accept Phalcon\Di\DiInterface, 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

216
                Di::setDefault(/** @scrutinizer ignore-type */ $this->di);
Loading history...
217 7
            }
218 7
        );
219
220 7
        return $this->di;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->di also could return the type string which is incompatible with the documented return type Phalcon\Di\FactoryDefaul...n\Di\FactoryDefault\Cli.
Loading history...
221
    }
222
223
    /**
224
     * Reading .env file
225
     * @return Dotenv
226
     * @throws Exception
227
     */
228 7
    public function dotenv()
229
    {
230 7
        return $this->fireSet($this->dotenv, Env::class, [], function (Bootstrap $bootstrap) {
231 7
            $bootstrap->dotenv = Env::getDotenv();
232 7
        });
233
    }
234
235
    /**
236
     * Get arguments from command line interface (cli / console)
237
     * @return Docopt
238
     * @throws Exception
239
     */
240 7
    public function docopt()
241
    {
242 7
        return $this->fireSet($this->docopt, Docopt::class, [], function (Bootstrap $bootstrap) {
243 7
            if ($this->isConsole()) {
244
                $docoptResponse = $bootstrap->docopt->handle($bootstrap->consoleDoc, [
0 ignored issues
show
Bug introduced by
The method handle() does not exist on null. ( Ignorable by Annotation )

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

244
                /** @scrutinizer ignore-call */ 
245
                $docoptResponse = $bootstrap->docopt->handle($bootstrap->consoleDoc, [

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
245
                    'argv' => array_slice($_SERVER['argv'], 1),
246
                    'optionsFirst' => true,
247
                ]);
248
                $bootstrap->args = array_merge($bootstrap->args ?? [], $docoptResponse->args);
249
            }
250 7
        });
251
    }
252
253
    /**
254
     * Preparing some native PHP related stuff
255
     * @return Prepare
256
     * @throws Exception
257
     */
258 7
    public function prepare()
259
    {
260 7
        return $this->fireSet($this->prepare, Prepare::class);
261
    }
262
263
    /**
264
     * Registering bootstrap providers
265
     */
266 7
    public function register(array &$providers = null)
267
    {
268 7
        $providers ??= $this->providers;
269
270 7
        foreach ($providers as $key => $provider) {
271 7
            if (is_string($provider) && class_exists($provider)) {
272 7
                $provider = new $provider($this->di);
273 7
                $this->di->register($provider);
274 7
                if ($this->di->has($provider->getName())) {
275 7
                    $this->providers[$key] = $this->di->get($provider->getName());
276
                }
277
            }
278
        }
279
280 7
        return $this->providers;
281
    }
282
283
    /**
284
     * Prepare the config service
285
     * - Fire events (before & after)
286
     * - Apply current bootstrap mode ('default', 'console')
287
     * - Merge with current environment config
288
     * @return Config
289
     * @throws Exception
290
     */
291 7
    public function config()
292
    {
293 7
        if ($this->di->has('config')) {
294 7
            $this->config = $this->di->get('config');
295
        }
296 7
        return $this->fireSet($this->config, Config::class);
297
    }
298
299
    /**
300
     * @return Debug
301
     * @throws Exception
302
     */
303 7
    public function debug()
304
    {
305 7
        return $this->fireSet($this->debug, Debug::class, [], function (Bootstrap $bootstrap) {
306 7
            $config = $bootstrap->config->debug->toArray();
307 7
            $bootstrap->prepare->debug($bootstrap->config);
0 ignored issues
show
Bug introduced by
It seems like $bootstrap->config can also be of type string; however, parameter $config of Zemit\Bootstrap\Prepare::debug() does only seem to accept Phalcon\Config|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

307
            $bootstrap->prepare->debug(/** @scrutinizer ignore-type */ $bootstrap->config);
Loading history...
Bug introduced by
The method debug() does not exist on null. ( Ignorable by Annotation )

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

307
            $bootstrap->prepare->/** @scrutinizer ignore-call */ 
308
                                 debug($bootstrap->config);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
308
309
            // @todo review on phalcon5 php8+ because phalcon4 php8+ debug is doing cyclic error
310 7
            $cyclicError =
311 7
                version_compare(PHP_VERSION, '8.0.0', '>=') &&
312 7
                version_compare(\Phalcon\Version::get(), '5.0.0', '<');
313
314 7
            if (!$this->isConsole() && !$cyclicError) {
315 7
                if ($bootstrap->config->app->get('debug') || $config['enable']) {
316
                    $bootstrap->debug->listen($config['exception'] ?? true, $config['lowSeverity'] ?? false);
0 ignored issues
show
Bug introduced by
The method listen() does not exist on null. ( Ignorable by Annotation )

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

316
                    $bootstrap->debug->/** @scrutinizer ignore-call */ 
317
                                       listen($config['exception'] ?? true, $config['lowSeverity'] ?? false);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
317
                    $bootstrap->debug->setBlacklist($config['blacklist'] ?? []);
318
                    $bootstrap->debug->setShowFiles($config['showFiles'] ?? true);
319
                    $bootstrap->debug->setShowBackTrace($config['showBackTrace'] ?? true);
320
                    $bootstrap->debug->setShowFileFragment($config['showFileFragment'] ?? true);
321
                    if (is_string($config['uri'])) {
322
                        $bootstrap->debug->setUri($config['uri']);
323
                    }
324
                }
325
            }
326 7
        });
327
    }
328
329
    /**
330
     * Prepare procedural services way
331
     * - Fire events (before & after)
332
     * - Pass the current Di object as well as the current Config object
333
     * @return Services
334
     * @throws Exception
335
     */
336 7
    public function services()
337
    {
338 7
        return $this->fireSet($this->services, Services::class, [$this->di, $this->config]);
339
    }
340
341
    /**
342
     * Prepare the application
343
     * - Fire events (before & after)
344
     * - Pass the current Di object
345
     * - Depends on the current bootstrap mode ('default', 'console')
346
     * @return Console|Application
347
     * @throws Exception
348
     */
349 7
    public function application()
350
    {
351 7
        return $this->fireSet($this->application, $this->isConsole() ? Console::class : Application::class, [$this->di]);
352
    }
353
354
    /**
355
     * Prepare the application for modules
356
     * - Fire events (before & after)
357
     * - Pass the current Application object
358
     * @return Modules
359
     * @throws Exception
360
     */
361 7
    public function modules()
362
    {
363 7
        return $this->fireSet($this->modules, Modules::class, [$this->application]);
364
    }
365
366
    /**
367
     * Prepare the router
368
     * @return Router
369
     * @throws Exception
370
     */
371 7
    public function router()
372
    {
373 7
        if ($this->di->has('router')) {
374 7
            $this->router = $this->di->get('router');
375
        }
376
    
377 7
        return $this->isConsole() ?
378
            $this->fireSet($this->router, CliRouter::class, [true]) :
379 7
            $this->fireSet($this->router, Router::class, [true, $this->application]);
380
    }
381
    
382
    /**
383
     * Run the application
384
     * - Fire events (before run & after run)
385
     * - Handle both console and default application
386
     * - Return response string
387
     * @return false|string|null
388
     * @throws \Exception
389
     */
390
    public function run()
391
    {
392
        $this->fire('beforeRun');
393
        
394
        if (isset($this->application)) {
395
            
396
            if ($this->application instanceof Console) {
397
                
398
                // run console cli
399
                $content = $this->handleConsole($this->application);
400
            }
401
            
402
            else if ($this->application instanceof Application) {
403
                
404
                // run mvc http
405
                $content = $this->handleApplication($this->application);
406
            }
407
            else {
408
                $className = is_object($this->application)? get_class($this->application) : $this->application;
409
                throw new \Exception('Application \'' . $className . '\' not supported', 400);
410
            }
411
        }
412
        else {
413
            throw new \Exception('Application not found', 404);
414
        }
415
        
416
        $this->fire('afterRun', $content);
417
        return $content;
418
    }
419
    
420
    /**
421
     * @param Console $console
422
     * @return false|string|null
423
     */
424
    public function handleConsole(Console $console)
425
    {
426
        $response = null;
427
        
428
        try {
429
            ob_start();
430
            $console->handle($this->getArguments());
431
            $response = ob_get_clean();
432
        }
433
        catch (\Zemit\Exception $e) {
434
            new Cli\ExceptionHandler($e);
435
        }
436
        catch (\Exception $exception) {
437
            new Cli\ExceptionHandler($exception);
438
        }
439
        catch (\Throwable $throwable) {
440
            new Cli\ExceptionHandler($throwable);
441
        }
442
        
443
        return $response;
444
    }
445
    
446
    /**
447
     * @param Application $application
448
     * @return string
449
     * @throws \Exception
450
     */
451
    public function handleApplication(Application $application): string
452
    {
453
        $this->response = $application->handle($_SERVER['REQUEST_URI'] ?? '/');
454
        return $this->response ? $this->response->getContent() : '';
455
    }
456
457
    /**
458
     * Get & format arguments from the $this->args property
459
     * @return array Key value pair, human-readable
460
     */
461 1
    public function getArguments() : array
462
    {
463 1
        $arguments = [];
464 1
        if (!empty($this->args)) {
465
            foreach ($this->args as $key => $value) {
466
                if (preg_match('/(<(.*?)>|\-\-(.*))/', $key, $match)) {
467
                    $key = lcfirst(Text::camelize(Text::uncamelize(array_pop($match))));
468
                    $arguments[$key] = $value;
469
                }
470
            }
471
        }
472 1
        return $arguments;
473
    }
474
475
    /**
476
     * Return true if the bootstrap mode is set to 'console'
477
     * @return bool Console mode
478
     */
479 7
    public function isConsole() : bool
480
    {
481 7
        return $this->getMode() === self::MODE_CONSOLE;
482
    }
483
484
    /**
485
     * Return the raw bootstrap mode, should be either 'console' or 'default'
486
     * @return string Bootstrap mode
487
     */
488 7
    public function getMode() : string
489
    {
490 7
        return $this->mode;
491
    }
492
}
493