Completed
Push — master ( cd6ea2...a40d21 )
by Maxim
02:05
created

Console::setAllowParallel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
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
            $this->commandExecutionLock->lockCommand($this, $command);
417
418
            if ($isolate) {
419
                $input = clone $this->input;
420
                $output = clone $this->output;
421
            } else {
422
                $input = $this->input;
423
                $output = $this->output;
424
            }
425
426
            $input->setCommand($command);
427
428
            return $this->commandInvoker->run(
429
                $command->getHandler(), $input, $output, $this
430
            );
431
        } catch (Exception $ex) {
432 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...
433
                $widget = new ExceptionWidget($this->input, $this->output);
434
                $widget->render($ex);
435
            } else {
436
                throw $ex;
437
            }
438
        }
439
    }
440
441
    /**
442
     * @param array $groupedArgs
443
     *
444
     * @return bool
445
     */
446
    protected function runGlobalCommands(array $groupedArgs) {
447
        // run commands that are global but will for sure
448
        // not generate any output or interrupt the flow
449
        foreach ($this->getGlobalHiddenCommands() as $command) {
450
            $command = clone $command;
451
            $groupedArgs = $this->argumentsMatcher->matchCommand($command, $groupedArgs, false);
452
            $this->runCommand($command, false);
453
        }
454
455
        // run commands that might generate output or
456
        // try to interrupt the flow
457
        foreach ($this->getGlobalNotHiddenCommands() as $command) {
458
            $command = clone $command;
459
460
            // dirty hack, fix later
461
            // global commands should not steal arguments
462
            $args = $groupedArgs['arguments'];
463
            $groupedArgs = $this->argumentsMatcher->matchCommand($command, $groupedArgs, false);
464
            $groupedArgs['arguments'] = $args;
465
            $continue = $this->runCommand($command);
466
467
            if ($continue === false) {
468
                return [$groupedArgs, false];
469
            }
470
        }
471
472
        return [$groupedArgs, true];
473
    }
474
475
    /**
476
     * @param $command
477
     *
478
     * @throws InvalidCommandException
479
     */
480
    protected function validateCommand($command) {
481
        if ( ! is_string($command) && ! is_object($command)) {
482
            throw new InvalidCommandException(s(
483
                'Command must be either a class name or an instance, "%s" given.',
484
                get_type($command)
485
            ));
486
        }
487
488
        if (is_string($command) && ! class_exists($command)) {
489
            throw new InvalidCommandException(s(
490
                'Command "%s" does not exist.',
491
                $command
492
            ));
493
        }
494
495
        if ( ! method_exists($command, 'setup') || ! method_exists($command, 'run')) {
496
            throw new InvalidCommandException(s(
497
                'Command "%s" must implement methods "setup" and "run".',
498
                get_type($command)
499
            ));
500
        }
501
    }
502
503
    /**
504
     * @return ICommandInvoker
505
     */
506
    protected function createCommandInvoker() {
507
        return new CommandInvoker();
508
    }
509
510
    /**
511
     * @return ICommandExecutionLock
512
     */
513
    private function createCommandExecutionLock() {
514
        return new CommandExecutionLock();
515
    }
516
517
    /**
518
     * Register default command handlers.
519
     */
520
    protected function addDefaultCommands() {
521
        $this->addCommands([
522
            new GlobalVersionCommand(),
523
            new GlobalFormatCommand(),
524
            new GlobalSilentModeCommand(),
525
            new GlobalNoInteractionCommand(),
526
            new GlobalHelpCommand(),
527
            new GlobalVerbosityCommand(),
528
            new GlobalPassthroughCommand(),
529
            new ListCommand(),
530
            new HelpCommand(),
531
        ]);
532
    }
533
534
    /**
535
     * Register default formatter styles.
536
     */
537
    protected function addDefaultStyles() {
538
        $this->consoleFormatter->style('error')->parseStyle('clr=white bg=red');
539
        $this->consoleFormatter->style('warning')->parseStyle('clr=black bg=yellow');
540
        $this->consoleFormatter->style('success')->parseStyle('clr=black bg=green');
541
542
        $this->consoleFormatter->style('question')->parseStyle('clr=green');
543
        $this->consoleFormatter->style('header')->parseStyle('clr=yellow');
544
        $this->consoleFormatter->style('title')->parseStyle('clr=yellow');
545
        $this->consoleFormatter->style('keyword')->parseStyle('clr=green');
546
547
        $this->consoleFormatter->style('green')->parseStyle('clr=green');
548
        $this->consoleFormatter->style('yellow')->parseStyle('clr=yellow');
549
        $this->consoleFormatter->style('red')->parseStyle('clr=red');
550
        $this->consoleFormatter->style('white')->parseStyle('clr=white');
551
        $this->consoleFormatter->style('blue')->parseStyle('clr=blue');
552
        $this->consoleFormatter->style('gray')->parseStyle('clr=gray');
553
        $this->consoleFormatter->style('black')->parseStyle('clr=black');
554
555
        $this->consoleFormatter->style('bold')->parseStyle('fmt=bold');
556
        $this->consoleFormatter->style('italic')->parseStyle('fmt=italic');
557
        $this->consoleFormatter->style('underline')->parseStyle('fmt=underline');
558
        $this->consoleFormatter->style('strikethrough')->parseStyle('fmt=strikethrough');
559
    }
560
561
    /**
562
     * @return ICommand[]
563
     */
564 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...
565
        $commands = [];
566
567
        foreach ($this->getCommands() as $command) {
568
            if ($command->isGlobal() && $command->isHidden()) {
569
                $commands[] = $command;
570
            }
571
        }
572
573
        return $commands;
574
    }
575
576
    /**
577
     * @return ICommand[]
578
     */
579 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...
580
        $commands = [];
581
582
        foreach ($this->getCommands() as $command) {
583
            if ($command->isGlobal() && ! $command->isHidden()) {
584
                $commands[] = $command;
585
            }
586
        }
587
588
        return $commands;
589
    }
590
591
    /**
592
     * @return ICommand[]
593
     */
594 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...
595
        $commands = [];
596
597
        foreach ($this->getCommands() as $command) {
598
            if ( ! $command->isGlobal()) {
599
                $commands[] = $command;
600
            }
601
        }
602
603
        return $commands;
604
    }
605
}
606