Console::getAllowParallel()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Weew\Console;
4
5
use Exception;
6
use Weew\Console\Commands\GlobalFormatCommand;
7
use Weew\Console\Commands\GlobalHelpCommand;
8
use Weew\Console\Commands\GlobalNoInteractionCommand;
9
use Weew\Console\Commands\GlobalPassthroughCommand;
10
use Weew\Console\Commands\GlobalSilentModeCommand;
11
use Weew\Console\Commands\GlobalVerbosityCommand;
12
use Weew\Console\Commands\HelpCommand;
13
use Weew\Console\Commands\ListCommand;
14
use Weew\Console\Commands\GlobalVersionCommand;
15
use Weew\Console\Exceptions\InvalidCommandException;
16
use Weew\Console\Widgets\ExceptionWidget;
17
use Weew\ConsoleArguments\ArgumentsMatcher;
18
use Weew\ConsoleArguments\ArgumentsParser;
19
use Weew\ConsoleArguments\Command;
20
use Weew\ConsoleArguments\Exceptions\MissingCommandNameException;
21
use Weew\ConsoleArguments\IArgumentsMatcher;
22
use Weew\ConsoleArguments\IArgumentsParser;
23
use Weew\ConsoleArguments\ICommand;
24
use Weew\ConsoleFormatter\ConsoleFormatter;
25
use Weew\ConsoleFormatter\IConsoleFormatter;
26
27
class Console implements IConsole {
28
    /**
29
     * @var string
30
     */
31
    protected $title;
32
33
    /**
34
     * @var string
35
     */
36
    protected $description;
37
38
    /**
39
     * @var string
40
     */
41
    protected $version = '1.0';
42
43
    /**
44
     * @var bool
45
     */
46
    protected $allowParallel = true;
47
48
    /**
49
     * @var bool
50
     */
51
    protected $catchErrors = true;
52
53
    /**
54
     * @var object[]
55
     */
56
    protected $commands = [];
57
58
    /**
59
     * @var string
60
     */
61
    protected $defaultCommandName = 'list';
62
63
    /**
64
     * @var ICommandInvoker
65
     */
66
    protected $commandInvoker;
67
68
    /**
69
     * @var ICommandExecutionLock
70
     */
71
    protected $commandExecutionLock;
72
73
    /**
74
     * @var IArgumentsParser
75
     */
76
    protected $argumentsParser;
77
78
    /**
79
     * @var IArgumentsMatcher
80
     */
81
    protected $argumentsMatcher;
82
83
    /**
84
     * @var IConsoleFormatter
85
     */
86
    protected $consoleFormatter;
87
88
    /**
89
     * @var IOutput
90
     */
91
    protected $output;
92
93
    /**
94
     * @var IInput
95
     */
96
    protected $input;
97
98
    /**
99
     * Console constructor.
100
     *
101
     * @param ICommandInvoker $commandInvoker
102
     * @param ICommandExecutionLock $commandExecutionLock
103
     */
104
    public function __construct(
105
        ICommandInvoker $commandInvoker = null,
106
        ICommandExecutionLock $commandExecutionLock = null
107
    ) {
108
        if ( ! $commandInvoker instanceof ICommandInvoker) {
109
            $commandInvoker = $this->createCommandInvoker();
110
        }
111
112
        if ( ! $commandExecutionLock instanceof ICommandExecutionLock) {
113
            $commandExecutionLock = $this->createCommandExecutionLock();
114
        }
115
116
        $this->argumentsParser = new ArgumentsParser();
117
        $this->argumentsMatcher = new ArgumentsMatcher($this->argumentsParser);
118
119
        $this->setCommandInvoker($commandInvoker);
120
        $this->setCommandExecutionLock($commandExecutionLock);
121
        $this->setConsoleFormatter(new ConsoleFormatter());
122
        $this->setOutput(new Output($this->consoleFormatter));
123
        $this->setInput(new Input());
124
125
        $this->addDefaultCommands();
126
        $this->addDefaultStyles();
127
    }
128
129
    /**
130
     * @return string
131
     */
132
    public function getTitle() {
133
        return $this->title;
134
    }
135
136
    /**
137
     * @param string $title
138
     *
139
     * @return IConsole
140
     */
141
    public function setTitle($title) {
142
        $this->title = $title;
143
144
        return $this;
145
    }
146
147
    /**
148
     * @return string
149
     */
150
    public function getDescription() {
151
        return $this->description;
152
    }
153
154
    /**
155
     * @param string $description
156
     *
157
     * @return IConsole
158
     */
159
    public function setDescription($description) {
160
        $this->description = $description;
161
162
        return $this;
163
    }
164
165
    /**
166
     * @return string
167
     */
168
    public function getVersion() {
169
        return $this->version;
170
    }
171
172
    /**
173
     * @param string $version
174
     *
175
     * @return IConsole
176
     */
177
    public function setVersion($version) {
178
        $this->version = $version;
179
180
        return $this;
181
    }
182
183
    /**
184
     * @return bool
185
     */
186
    public function getAllowParallel() {
187
        return $this->allowParallel;
188
    }
189
190
    /**
191
     * @param bool $allowParallel
192
     *
193
     * @return IConsole
194
     */
195
    public function setAllowParallel($allowParallel) {
196
        $this->allowParallel = $allowParallel;
197
198
        return $this;
199
    }
200
201
    /**
202
     * @return bool
203
     */
204
    public function getCatchErrors() {
205
        return $this->catchErrors;
206
    }
207
208
    /**
209
     * @param bool $catchErrors
210
     *
211
     * @return IConsole
212
     */
213
    public function setCatchErrors($catchErrors) {
214
        $this->catchErrors = $catchErrors;
215
216
        return $this;
217
    }
218
219
    /**
220
     * @return ICommand[]
221
     */
222
    public function getCommands() {
223
        return $this->commands;
224
    }
225
226
    /**
227
     * @param object[] $commands
228
     */
229
    public function setCommands(array $commands) {
230
        $this->commands = [];
231
        $this->addCommands($commands);
232
    }
233
234
    /**
235
     * @param object[] $commands
236
     */
237
    public function addCommands(array $commands) {
238
        foreach ($commands as $command) {
239
            $this->addCommand($command);
240
        }
241
    }
242
243
    /**
244
     * @param object $command
245
     */
246
    public function addCommand($command) {
247
        if ($command instanceof ICommand) {
248
            $consoleCommand = $command;
249
            $command = $consoleCommand->getHandler();
250
        } else {
251
            $consoleCommand = new Command();
252
        }
253
254
        $this->validateCommand($command);
255
256
        if ( ! is_object($command)) {
257
            $command = $this->commandInvoker->create($command);
258
        }
259
260
        $consoleCommand->setHandler($command);
261
262
        $this->commandInvoker->setup($command, $consoleCommand);
263
        $this->commands[] = $consoleCommand;
264
    }
265
266
267
    /**
268
     * @return string
269
     */
270
    public function getDefaultCommandName() {
271
        return $this->defaultCommandName;
272
    }
273
274
    /**
275
     * @param string $commandName
276
     */
277
    public function setDefaultCommandName($commandName) {
278
        $this->defaultCommandName = $commandName;
279
    }
280
281
    /**
282
     * @param array $argv
283
     */
284
    public function parseArgv(array $argv = null) {
285
        if ( ! is_array($argv)) {
286
            global $argv;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
287
        }
288
289
        $this->parseArgs(array_slice($argv, 1));
290
    }
291
292
    /**
293
     * @param array $args
294
     */
295
    public function parseArgs(array $args) {
296
        $this->parseString(implode(' ', $args));
297
    }
298
299
    /**
300
     * @param $string
301
     */
302
    public function parseString($string) {
303
        $this->handleArgs($this->argumentsParser->parse($string));
304
    }
305
306
    /**
307
     * @return IConsoleFormatter
308
     */
309
    public function getConsoleFormatter() {
310
        return $this->consoleFormatter;
311
    }
312
313
    /**
314
     * @param IConsoleFormatter $consoleFormatter
315
     */
316
    public function setConsoleFormatter(IConsoleFormatter $consoleFormatter) {
317
        $this->consoleFormatter = $consoleFormatter;
318
    }
319
320
    /**
321
     * @return IOutput
322
     */
323
    public function getOutput() {
324
        return $this->output;
325
    }
326
327
    /**
328
     * @param IOutput $output
329
     */
330
    public function setOutput(IOutput $output) {
331
        $this->output = $output;
332
    }
333
334
    /**
335
     * @return IInput
336
     */
337
    public function getInput() {
338
        return $this->input;
339
    }
340
341
    /**
342
     * @param IInput $input
343
     */
344
    public function setInput(IInput $input) {
345
        $this->input = $input;
346
    }
347
348
    /**
349
     * @return ICommandInvoker
350
     */
351
    public function getCommandInvoker() {
352
        return $this->commandInvoker;
353
    }
354
355
    /**
356
     * @param ICommandInvoker $commandInvoker
357
     */
358
    public function setCommandInvoker(ICommandInvoker $commandInvoker) {
359
        $this->commandInvoker = $commandInvoker;
360
    }
361
362
    /**
363
     * @return ICommandExecutionLock
364
     */
365
    public function getCommandExecutionLock() {
366
        return $this->commandExecutionLock;
367
    }
368
369
    /**
370
     * @param ICommandExecutionLock $commandExecutionLock
371
     */
372
    public function setCommandExecutionLock(ICommandExecutionLock $commandExecutionLock) {
373
        $this->commandExecutionLock = $commandExecutionLock;
374
    }
375
376
    /**
377
     * @param array $args
378
     *
379
     * @throws Exception
380
     */
381
    protected function handleArgs(array $args) {
382
        $groupedArgs = $this->argumentsParser->group($args);
383
        list($groupedArgs, $continue) = $this->runGlobalCommands($groupedArgs);
384
385
        if ($continue === false) {
386
            return;
387
        }
388
389
        try {
390
            list($command, $groupedArgs) = $this->argumentsMatcher
0 ignored issues
show
Unused Code introduced by
The assignment to $groupedArgs is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
391
                ->matchCommands($this->getNotGlobalCommands(), $groupedArgs);
392
            $command = clone $command;
393
            $this->runCommand($command);
394
        } catch (MissingCommandNameException $ex) {
395
            array_unshift($args, $this->getDefaultCommandName());
396
            $this->parseArgs($args);
397
        } catch (Exception $ex) {
398 View Code Duplication
            if ($this->getCatchErrors()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
399
                $widget = new ExceptionWidget($this->input, $this->output);
400
                $widget->render($ex);
401
            } else {
402
                throw $ex;
403
            }
404
        }
405
    }
406
407
    /**
408
     * @param ICommand $command
409
     * @param bool $isolate
410
     *
411
     * @return mixed
412
     * @throws Exception
413
     */
414
    protected function runCommand(ICommand $command, $isolate = true) {
415
        try {
416
            if ( ! $command->isGlobal()) {
417
                $this->commandExecutionLock->lockCommand($command, $this->getAllowParallel());
418
            }
419
420
            if ($isolate) {
421
                $input = clone $this->input;
422
                $output = clone $this->output;
423
            } else {
424
                $input = $this->input;
425
                $output = $this->output;
426
            }
427
428
            $input->setCommand($command);
429
430
            return $this->commandInvoker->run(
431
                $command->getHandler(), $input, $output, $this
432
            );
433
        } catch (Exception $ex) {
434 View Code Duplication
            if ($this->getCatchErrors()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
435
                $widget = new ExceptionWidget($this->input, $this->output);
436
                $widget->render($ex);
437
            } else {
438
                throw $ex;
439
            }
440
        }
441
    }
442
443
    /**
444
     * @param array $groupedArgs
445
     *
446
     * @return bool
447
     */
448
    protected function runGlobalCommands(array $groupedArgs) {
449
        // run commands that are global but will for sure
450
        // not generate any output or interrupt the flow
451
        foreach ($this->getGlobalHiddenCommands() as $command) {
452
            $command = clone $command;
453
            $groupedArgs = $this->argumentsMatcher->matchCommand($command, $groupedArgs, false);
454
            $this->runCommand($command, false);
455
        }
456
457
        // run commands that might generate output or
458
        // try to interrupt the flow
459
        foreach ($this->getGlobalNotHiddenCommands() as $command) {
460
            $command = clone $command;
461
462
            // dirty hack, fix later
463
            // global commands should not steal arguments
464
            $args = $groupedArgs['arguments'];
465
            $groupedArgs = $this->argumentsMatcher->matchCommand($command, $groupedArgs, false);
466
            $groupedArgs['arguments'] = $args;
467
            $continue = $this->runCommand($command);
468
469
            if ($continue === false) {
470
                return [$groupedArgs, false];
471
            }
472
        }
473
474
        return [$groupedArgs, true];
475
    }
476
477
    /**
478
     * @param $command
479
     *
480
     * @throws InvalidCommandException
481
     */
482
    protected function validateCommand($command) {
483
        if ( ! is_string($command) && ! is_object($command)) {
484
            throw new InvalidCommandException(s(
485
                'Command must be either a class name or an instance, "%s" given.',
486
                get_type($command)
487
            ));
488
        }
489
490
        if (is_string($command) && ! class_exists($command)) {
491
            throw new InvalidCommandException(s(
492
                'Command "%s" does not exist.',
493
                $command
494
            ));
495
        }
496
497
        if ( ! method_exists($command, 'setup') || ! method_exists($command, 'run')) {
498
            throw new InvalidCommandException(s(
499
                'Command "%s" must implement methods "setup" and "run".',
500
                get_type($command)
501
            ));
502
        }
503
    }
504
505
    /**
506
     * @return ICommandInvoker
507
     */
508
    protected function createCommandInvoker() {
509
        return new CommandInvoker();
510
    }
511
512
    /**
513
     * @return ICommandExecutionLock
514
     */
515
    private function createCommandExecutionLock() {
516
        return new CommandExecutionLock();
517
    }
518
519
    /**
520
     * Register default command handlers.
521
     */
522
    protected function addDefaultCommands() {
523
        $this->addCommands([
524
            new GlobalVersionCommand(),
525
            new GlobalFormatCommand(),
526
            new GlobalSilentModeCommand(),
527
            new GlobalNoInteractionCommand(),
528
            new GlobalHelpCommand(),
529
            new GlobalVerbosityCommand(),
530
            new GlobalPassthroughCommand(),
531
            new ListCommand(),
532
            new HelpCommand(),
533
        ]);
534
    }
535
536
    /**
537
     * Register default formatter styles.
538
     */
539
    protected function addDefaultStyles() {
540
        $this->consoleFormatter->style('error')->parseStyle('clr=white bg=red');
541
        $this->consoleFormatter->style('warning')->parseStyle('clr=black bg=yellow');
542
        $this->consoleFormatter->style('success')->parseStyle('clr=black bg=green');
543
544
        $this->consoleFormatter->style('question')->parseStyle('clr=green');
545
        $this->consoleFormatter->style('header')->parseStyle('clr=yellow');
546
        $this->consoleFormatter->style('title')->parseStyle('clr=yellow');
547
        $this->consoleFormatter->style('keyword')->parseStyle('clr=green');
548
549
        $this->consoleFormatter->style('green')->parseStyle('clr=green');
550
        $this->consoleFormatter->style('yellow')->parseStyle('clr=yellow');
551
        $this->consoleFormatter->style('red')->parseStyle('clr=red');
552
        $this->consoleFormatter->style('white')->parseStyle('clr=white');
553
        $this->consoleFormatter->style('blue')->parseStyle('clr=blue');
554
        $this->consoleFormatter->style('gray')->parseStyle('clr=gray');
555
        $this->consoleFormatter->style('black')->parseStyle('clr=black');
556
557
        $this->consoleFormatter->style('bold')->parseStyle('fmt=bold');
558
        $this->consoleFormatter->style('italic')->parseStyle('fmt=italic');
559
        $this->consoleFormatter->style('underline')->parseStyle('fmt=underline');
560
        $this->consoleFormatter->style('strikethrough')->parseStyle('fmt=strikethrough');
561
    }
562
563
    /**
564
     * @return ICommand[]
565
     */
566 View Code Duplication
    protected function getGlobalHiddenCommands() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
567
        $commands = [];
568
569
        foreach ($this->getCommands() as $command) {
570
            if ($command->isGlobal() && $command->isHidden()) {
571
                $commands[] = $command;
572
            }
573
        }
574
575
        return $commands;
576
    }
577
578
    /**
579
     * @return ICommand[]
580
     */
581 View Code Duplication
    protected function getGlobalNotHiddenCommands() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
582
        $commands = [];
583
584
        foreach ($this->getCommands() as $command) {
585
            if ($command->isGlobal() && ! $command->isHidden()) {
586
                $commands[] = $command;
587
            }
588
        }
589
590
        return $commands;
591
    }
592
593
    /**
594
     * @return ICommand[]
595
     */
596 View Code Duplication
    protected function getNotGlobalCommands() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
597
        $commands = [];
598
599
        foreach ($this->getCommands() as $command) {
600
            if ( ! $command->isGlobal()) {
601
                $commands[] = $command;
602
            }
603
        }
604
605
        return $commands;
606
    }
607
}
608