Test Failed
Push — master ( ffc597...4abc3b )
by Julien
12:58 queued 09:01
created

Bootstrap::dotenv()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 14
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 14
ccs 5
cts 5
cp 1
rs 10
cc 2
nc 3
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 Phalcon\Di;
14
use Phalcon\Di\FactoryDefault;
15
use Phalcon\Http\Response;
16
use Phalcon\Text;
17
use Phalcon\Events;
18
19
use Zemit\Bootstrap\Prepare;
20
use Zemit\Bootstrap\Config;
21
use Zemit\Bootstrap\Services;
22
use Zemit\Bootstrap\Modules;
23
use Zemit\Bootstrap\Router;
24
use Zemit\Events\EventsAwareTrait;
25
use Zemit\Mvc\Application;
26
use Zemit\Cli\Console;
27
use Zemit\Cli\Router as CliRouter;
28
29
use Dotenv\Dotenv;
30
use Docopt;
31
32
/**
33
 * Class Bootstrap
34
 * Zemit Core's Bootstrap for the MVC Application & CLI Console mode
35
 *
36
 * @author Julien Turbide <[email protected]>
37
 * @copyright Zemit Team <[email protected]>
38
 *
39
 * @since 1.0
40
 * @version 1.0
41
 *
42
 * @package Zemit
43
 */
44
class Bootstrap
45
{
46
    use EventsAwareTrait;
47
    
48
    /**
49
     * Bootstrap modes
50
     */
51
    const MODE_CLI = 'console';
52
    const MODE_DEFAULT = 'default';
53
    const MODE_CONSOLE = self::MODE_CLI;
54
    
55
    /**
56
     * @deprecated Use MODE_DEFAULT instead
57
     */
58
    const MODE_NORMAL = self::MODE_DEFAULT;
59
    
60
    /**
61
     * Ideally, only the config service provider should be added here, then it will load other service from itself
62
     * You can also add new Service Providers here if it's absolutely required to be loaded earlier before
63
     * @var array abstract => concrete
64
     */
65
    public $providers = [
66
        Provider\Config\ServiceProvider::class => Provider\Config\ServiceProvider::class,
67
    ];
68
    
69
    /**
70
     * Bootstrap mode
71
     * @var string
72
     */
73
    public $mode;
74
    
75
    /**
76
     * Bootstrap console args
77
     * @var array
78
     */
79
    public $args;
80
    
81
    /**
82
     * Dependencies
83
     * @var FactoryDefault|FactoryDefault\Cli
84
     */
85
    public $di;
86
    
87
    /**
88
     * @var Dotenv
89
     */
90
    public $dotenv;
91
    
92
    /**
93
     * @var Prepare
94
     */
95
    public $prepare;
96
    
97
    /**
98
     * @var Config
99
     */
100
    public $config;
101
    
102
    /**
103
     * @var Services
104
     */
105
    public $services;
106
    
107
    /**
108
     * @var Application|\Phalcon\Cli\Console
109
     */
110
    public $application;
111
    
112
    /**
113
     * @var Modules
114
     */
115
    public $modules;
116
    
117
    /**
118
     * @var Router
119
     */
120
    public $router;
121
    
122
    /**
123
     * @var Debug
124
     */
125
    public $debug;
126
    
127
    /**
128
     * @var Response
129
     */
130
    public $response;
131
    
132
    /**
133
     * @var Docopt
134
     */
135
    public $docopt;
136
    
137
    /**
138
     * @var string
139
     */
140
    public $consoleDoc = <<<DOC
141
Zemit Console
142
143
Usage:
144
  zemit <module> <task> [<action>] [<params> ...]  [--env=<env>] [--debug=<debug>] [--plugin=<plugin>] [--log-file=<file>]
145
  zemit (-h | --help)
146
  zemit (-v | --version)
147
  zemit (-i | --info)
148
  zemit (-c | --config)
149
150
Options:
151
  -h --help               show this help message
152
  -v --version            print version number
153
  -i --info               print environment informations
154
  -c --config             print the generated config that is used
155
  -V --verbose            increase verbosity
156
  -q --quiet              suppress non-error messages
157
  -f --force              force action even if not safe
158
  -n --dry-run            perform a trial run with no changes made
159
  -p --plugins            execute task for all plugins at once
160
  --ignore-errors         keep executing the task even after errors
161
  --plugin=<plugin>       plugin to execute the task
162
  --log-file=<file>       log what we're doing to the specified file [default: private/logs/cli.log]
163
  --debug=<debug>         Force the debug and ignore debug value from the config [default: false]
164
  --env=<env>             Force environment to pick the configuration files [default: development]
165
DOC;
166
    
167
    /**
168
     * Bootstrap constructor.
169
     * Setup the di, env, app, config, services, applications, modules and then the router
170
     *
171
     * @param string $mode Mode for the application 'default' 'console'
172
     *
173
     * @throws Exception
174
     */
175 4
    public function __construct($mode = self::MODE_DEFAULT)
176
    {
177 4
        $this->mode = $mode;
178 4
        $this->setEventsManager(new Events\Manager());
179 4
        $this->initialize();
180 4
        $this->dotenv();
181 4
        $this->docopt();
182 4
        $this->di();
183 4
        $this->prepare();
184 4
        $this->register();
185 4
        $this->config();
186 4
        $this->debug();
187 4
        $this->services();
188 4
        $this->application();
189 4
        $this->modules();
190
//        $this->router(); // using serviceProvider now
191
    }
192
    
193
    /**
194
     * Initialisation
195
     */
196
    public function initialize()
197
    {
198
    }
199
    
200
    /**
201
     * Prepare the DI including itself (Bootstrap) and setup as default DI
202
     * Also use the cli factory default for console mode
203
     * @return FactoryDefault|FactoryDefault\Cli Return a factory default DI
204
     * @throws Exception
205
     */
206 4
    public function di()
207
    {
208
        // Use the phalcon cli factory default for console mode
209 4
        $this->fireSet(
210 4
            $this->di,
211 4
            $this->isConsole() ?
212
            FactoryDefault\Cli::class :
213 4
            FactoryDefault::class,
214 4
            [],
215 4
            function (Bootstrap $bootstrap) {
216
                // Register bootstrap itself
217 4
                $this->di->setShared('bootstrap', $bootstrap);
218
                
219
                // Set as the default DI
220 4
                Di::setDefault($this->di);
221
            }
222
        );
223
        
224 4
        return $this->di;
225
    }
226
    
227
    /**
228
     * Reading .env file
229
     * @return Dotenv
230
     * @throws Exception
231
     */
232 4
    public function dotenv()
233
    {
234
        
235
        try {
236 4
            $this->dotenv = Dotenv::create(dirname(APP_PATH)); // @todo fix this to handle fireset instead, using a new class extending dotenv
237 4
            $this->dotenv->load();
238
//            $this->fireSet($this->dotenv, Dotenv::class, [dirname(APP_PATH)], function (Bootstrap $bootstrap) {
239
//                $bootstrap->dotenv->load();
240
//            });
241 4
        } catch (\Dotenv\Exception\InvalidPathException|\Dotenv\Exception\InvalidFileException $e) {
242
            // just ignore and run the application anyway
243
        }
244
        
245 4
        return $this->dotenv;
246
    }
247
    
248
    /**
249
     * Get arguments from command line interface (cli / console)
250
     * @return Docopt
251
     * @throws Exception
252
     */
253 4
    public function docopt()
254
    {
255 4
        return $this->fireSet($this->docopt, Docopt::class, [], function (Bootstrap $bootstrap) {
256 4
            if ($this->isConsole()) {
257
                $args = $bootstrap->docopt->handle($bootstrap->consoleDoc);
258
                $bootstrap->args = is_null($bootstrap->args)? $args : array_merge($bootstrap->args, $args);
0 ignored issues
show
introduced by
The condition is_null($bootstrap->args) is always false.
Loading history...
Bug introduced by
$args of type Docopt\Response is incompatible with the type array expected by parameter $arrays of array_merge(). ( Ignorable by Annotation )

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

258
                $bootstrap->args = is_null($bootstrap->args)? $args : array_merge($bootstrap->args, /** @scrutinizer ignore-type */ $args);
Loading history...
259
            }
260
        });
261
    }
262
    
263
    /**
264
     * Preparing some native PHP related stuff
265
     * @return Prepare
266
     * @throws Exception
267
     */
268 4
    public function prepare()
269
    {
270 4
        return $this->fireSet($this->prepare, Prepare::class);
271
    }
272
    
273
    /**
274
     * Registering bootstrap providers
275
     */
276 4
    public function register(array &$providers = null)
277
    {
278 4
        $providers ??= $this->providers;
279
        
280 4
        foreach ($providers as $key => $provider) {
281 4
            if (is_string($provider) && class_exists($provider)) {
282 4
                $provider = new $provider($this->di);
283 4
                $this->di->register($provider);
284 4
                if ($this->di->has($provider->getName())) {
285 4
                    $this->providers[$key] = $this->di->get($provider->getName());
286
                }
287
            }
288
        }
289
        
290 4
        return $this->providers;
291
    }
292
    
293
    /**
294
     * Prepare the config service
295
     * - Fire events (before & after)
296
     * - Apply current bootstrap mode ('default', 'console')
297
     * - Merge with current environment config
298
     * @return Config
299
     * @throws Exception
300
     */
301 4
    public function config()
302
    {
303 4
        if ($this->di->has('config')) {
304 4
            $this->config = $this->di->get('config');
305
        }
306 4
        return $this->fireSet($this->config, Config::class, [], function (Bootstrap $bootstrap) {
307 4
            $bootstrap->config->mode = $bootstrap->getMode();
0 ignored issues
show
Bug Best Practice introduced by
The property mode does not exist on Zemit\Bootstrap\Config. Since you implemented __set, consider adding a @property annotation.
Loading history...
308 4
            $bootstrap->config->mergeEnvConfig();
309
//            $bootstrap->prepare->php();
310
        });
311
    }
312
    
313
    /**
314
     * @return Debug
315
     * @throws Exception
316
     */
317 4
    public function debug()
318
    {
319 4
        return $this->fireSet($this->debug, Debug::class, [], function (Bootstrap $bootstrap) {
320 4
            $config = $bootstrap->config->debug;
0 ignored issues
show
Bug Best Practice introduced by
The property debug does not exist on Zemit\Bootstrap\Config. Since you implemented __get, consider adding a @property annotation.
Loading history...
321 4
            $bootstrap->prepare()->debug($bootstrap->config);
322
            
323 4
            if ($bootstrap->config->app->debug || $bootstrap->config->debug->enable) {
0 ignored issues
show
Bug Best Practice introduced by
The property app does not exist on Zemit\Bootstrap\Config. Since you implemented __get, consider adding a @property annotation.
Loading history...
324
                if (is_bool($config)) {
325
                    $bootstrap->debug->listen();
326
                } else {
327
                    $bootstrap->debug->listen($config->exception ?? true, $config->lowSeverity ?? true);
328
                    $bootstrap->debug->setBlacklist($config->has('blacklist')? $config->blacklist->toArray() : []);
329
                    $bootstrap->debug->setShowFiles($config->showFiles ?? true);
330
                    $bootstrap->debug->setShowBackTrace($config->showBackTrace ?? true);
331
                    $bootstrap->debug->setShowFileFragment($config->showFileFragment ?? true);
332
                    if (is_string($config->uri)) {
333
                        $bootstrap->debug->setUri($config->uri);
334
                    }
335
                }
336
            }
337
        });
338
    }
339
    
340
    /**
341
     * Prepare procedural services way
342
     * - Fire events (before & after)
343
     * - Pass the current Di object as well as the current Config object
344
     * @return Services
345
     * @throws Exception
346
     */
347 4
    public function services()
348
    {
349 4
        return $this->fireSet($this->services, Services::class, [$this->di, $this->config]);
350
    }
351
    
352
    /**
353
     * Prepare the application
354
     * - Fire events (before & after)
355
     * - Pass the current Di object
356
     * - Depends on the current bootstrap mode ('default', 'console')
357
     * @return \Phalcon\Cli\Console|Application
358
     * @throws Exception
359
     */
360 4
    public function application()
361
    {
362 4
        return $this->fireSet(
363 4
            $this->application,
364 4
            $this->isConsole() ?
365
                Console::class :
366 4
                Application::class,
367 4
            [$this->di]
368
        );
369
    }
370
    
371
    /**
372
     * Prepare the application for modules
373
     * - Fire events (before & after)
374
     * - Pass the current Application object
375
     * @return Modules
376
     * @throws Exception
377
     */
378 4
    public function modules()
379
    {
380 4
        return $this->fireSet($this->modules, Modules::class, [$this->application]);
381
    }
382
    
383
    /**
384
     * Prepare the router
385
     * - Fire events (before & after router)
386
     * - Pass the current application for default mode
387
     * - Depends on the bootstrap mode ('default', 'default')
388
     * - Force Re-inject router in the bootstrap DI @TODO is it still necessary
389
     * @return Router
390
     * @throws Exception
391
     */
392
    public function router()
393
    {
394
        return $this->fireSet($this->router, $this->isConsole() ? CliRouter::class : Router::class, [true, $this->application]);
395
    }
396
    
397
    /**
398
     * Run Zemit App
399
     * - Fire events (before run & after run)
400
     * - Handle both console and default application
401
     * - Return response string
402
     * @return string
403
     * @throws Exception If the application can't be found
404
     */
405
    public function run()
406
    {
407
        $this->fire('beforeRun');
408
        
409
        // cli console mode, get the arguments from the doctlib
410
        if ($this->isConsole() || $this->application instanceof Console) {
411
            try {
412
                ob_start();
413
                $this->application->handle($this->getArguments());
0 ignored issues
show
Bug introduced by
$this->getArguments() of type array is incompatible with the type string expected by parameter $uri of Phalcon\Mvc\Application::handle(). ( Ignorable by Annotation )

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

413
                $this->application->handle(/** @scrutinizer ignore-type */ $this->getArguments());
Loading history...
414
                $responseString = ob_get_clean();
415
                $this->fire('afterRun');
416
                return $responseString;
417
            } catch (\Zemit\Exception $e) {
418
                new Cli\ExceptionHandler($e);
419
                // do zemit related stuff here
420
                exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
421
            } catch (\Phalcon\Exception $e) {
422
                new Cli\ExceptionHandler($e);
423
                // do phalcon related stuff here
424
                exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
425
            } catch (\Exception $exception) {
426
                new Cli\ExceptionHandler($exception);
427
                exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
428
            } catch (\Throwable $throwable) {
429
                new Cli\ExceptionHandler($throwable);
430
                exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
431
            }
432
        } elseif (isset($this->application) && ($this->application instanceof \Phalcon\Mvc\Application)) {
433
            // we don't need a try catch here, its handled by the application
434
            // or the user can wrap it with try catch into the public/index.php instead
435
            $this->response = $this->application->handle($_SERVER['REQUEST_URI'] ?? '/');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->application->hand...['REQUEST_URI'] ?? '/') of type true is incompatible with the declared type Phalcon\Http\Response of property $response.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
436
            $this->fire('afterRun');
437
            return $this->response->getContent();
438
        } else {
439
            if (empty($this->application)) {
440
                throw new \Exception('Application \'\' not found', 404);
441
            } else {
442
                throw new \Exception('Application \''.get_class($this->application).'\' not supported', 400);
443
            }
444
        }
445
    }
446
    
447
    /**
448
     * Get & format arguments from the $this->args property
449
     * @return array Key value pair, human readable
450
     */
451 1
    public function getArguments()
452
    {
453 1
        $arguments = [];
454 1
        if ($this->args) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->args of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
455
            foreach ($this->args as $key => $value) {
456
                if (preg_match('/(<(.*?)>|\-\-(.*))/', $key, $match)) {
457
                    $key = lcfirst(Text::camelize(Text::uncamelize(array_pop($match))));
458
                    $arguments[$key] = $value;
459
                }
460
            }
461
        }
462 1
        return $arguments;
463
    }
464
    
465
    /**
466
     * Return True if the bootstrap mode is set to 'console'
467
     * @return bool Console mode
468
     */
469 4
    public function isConsole() : bool
470
    {
471 4
        return $this->getMode() === self::MODE_CONSOLE;
472
    }
473
    
474
    /**
475
     * Return the raw bootstrap mode, should be either 'console' or 'default'
476
     * @return string Bootstrap mode
477
     */
478 4
    public function getMode() : string
479
    {
480 4
        return $this->mode;
481
    }
482
}
483