Completed
Push — master ( aa2f53...cd6ea2 )
by Maxim
03:44
created

Console::createCommandExecutionLock()   A

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 $catchErrors = true;
47
48
    /**
49
     * @var object[]
50
     */
51
    protected $commands = [];
52
53
    /**
54
     * @var string
55
     */
56
    protected $defaultCommandName = 'list';
57
58
    /**
59
     * @var ICommandInvoker
60
     */
61
    protected $commandInvoker;
62
63
    /**
64
     * @var ICommandExecutionLock
65
     */
66
    protected $commandExecutionLock;
67
68
    /**
69
     * @var IArgumentsParser
70
     */
71
    protected $argumentsParser;
72
73
    /**
74
     * @var IArgumentsMatcher
75
     */
76
    protected $argumentsMatcher;
77
78
    /**
79
     * @var IConsoleFormatter
80
     */
81
    protected $consoleFormatter;
82
83
    /**
84
     * @var IOutput
85
     */
86
    protected $output;
87
88
    /**
89
     * @var IInput
90
     */
91
    protected $input;
92
93
    /**
94
     * Console constructor.
95
     *
96
     * @param ICommandInvoker $commandInvoker
97
     * @param ICommandExecutionLock $commandExecutionLock
98
     */
99
    public function __construct(
100
        ICommandInvoker $commandInvoker = null,
101
        ICommandExecutionLock $commandExecutionLock = null
102
    ) {
103
        if ( ! $commandInvoker instanceof ICommandInvoker) {
104
            $commandInvoker = $this->createCommandInvoker();
105
        }
106
107
        if ( ! $commandExecutionLock instanceof ICommandExecutionLock) {
108
            $commandExecutionLock = $this->createCommandExecutionLock();
109
        }
110
111
        $this->argumentsParser = new ArgumentsParser();
112
        $this->argumentsMatcher = new ArgumentsMatcher($this->argumentsParser);
113
114
        $this->setCommandInvoker($commandInvoker);
115
        $this->setCommandExecutionLock($commandExecutionLock);
116
        $this->setConsoleFormatter(new ConsoleFormatter());
117
        $this->setOutput(new Output($this->consoleFormatter));
118
        $this->setInput(new Input());
119
120
        $this->addDefaultCommands();
121
        $this->addDefaultStyles();
122
    }
123
124
    /**
125
     * @return string
126
     */
127
    public function getTitle() {
128
        return $this->title;
129
    }
130
131
    /**
132
     * @param string $title
133
     *
134
     * @return IConsole
135
     */
136
    public function setTitle($title) {
137
        $this->title = $title;
138
139
        return $this;
140
    }
141
142
    /**
143
     * @return string
144
     */
145
    public function getDescription() {
146
        return $this->description;
147
    }
148
149
    /**
150
     * @param string $description
151
     *
152
     * @return IConsole
153
     */
154
    public function setDescription($description) {
155
        $this->description = $description;
156
157
        return $this;
158
    }
159
160
    /**
161
     * @return string
162
     */
163
    public function getVersion() {
164
        return $this->version;
165
    }
166
167
    /**
168
     * @param string $version
169
     *
170
     * @return IConsole
171
     */
172
    public function setVersion($version) {
173
        $this->version = $version;
174
175
        return $this;
176
    }
177
178
    /**
179
     * @return bool
180
     */
181
    public function getCatchErrors() {
182
        return $this->catchErrors;
183
    }
184
185
    /**
186
     * @param bool $catchErrors
187
     *
188
     * @return IConsole
189
     */
190
    public function setCatchErrors($catchErrors) {
191
        $this->catchErrors = $catchErrors;
192
193
        return $this;
194
    }
195
196
    /**
197
     * @return ICommand[]
198
     */
199
    public function getCommands() {
200
        return $this->commands;
201
    }
202
203
    /**
204
     * @param object[] $commands
205
     */
206
    public function setCommands(array $commands) {
207
        $this->commands = [];
208
        $this->addCommands($commands);
209
    }
210
211
    /**
212
     * @param object[] $commands
213
     */
214
    public function addCommands(array $commands) {
215
        foreach ($commands as $command) {
216
            $this->addCommand($command);
217
        }
218
    }
219
220
    /**
221
     * @param object $command
222
     */
223
    public function addCommand($command) {
224
        if ($command instanceof ICommand) {
225
            $consoleCommand = $command;
226
            $command = $consoleCommand->getHandler();
227
        } else {
228
            $consoleCommand = new Command();
229
        }
230
231
        $this->validateCommand($command);
232
233
        if ( ! is_object($command)) {
234
            $command = $this->commandInvoker->create($command);
235
        }
236
237
        $consoleCommand->setHandler($command);
238
239
        $this->commandInvoker->setup($command, $consoleCommand);
240
        $this->commands[] = $consoleCommand;
241
    }
242
243
244
    /**
245
     * @return string
246
     */
247
    public function getDefaultCommandName() {
248
        return $this->defaultCommandName;
249
    }
250
251
    /**
252
     * @param string $commandName
253
     */
254
    public function setDefaultCommandName($commandName) {
255
        $this->defaultCommandName = $commandName;
256
    }
257
258
    /**
259
     * @param array $argv
260
     */
261
    public function parseArgv(array $argv = null) {
262
        if ( ! is_array($argv)) {
263
            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...
264
        }
265
266
        $this->parseArgs(array_slice($argv, 1));
267
    }
268
269
    /**
270
     * @param array $args
271
     */
272
    public function parseArgs(array $args) {
273
        $this->parseString(implode(' ', $args));
274
    }
275
276
    /**
277
     * @param $string
278
     */
279
    public function parseString($string) {
280
        $this->handleArgs($this->argumentsParser->parse($string));
281
    }
282
283
    /**
284
     * @return IConsoleFormatter
285
     */
286
    public function getConsoleFormatter() {
287
        return $this->consoleFormatter;
288
    }
289
290
    /**
291
     * @param IConsoleFormatter $consoleFormatter
292
     */
293
    public function setConsoleFormatter(IConsoleFormatter $consoleFormatter) {
294
        $this->consoleFormatter = $consoleFormatter;
295
    }
296
297
    /**
298
     * @return IOutput
299
     */
300
    public function getOutput() {
301
        return $this->output;
302
    }
303
304
    /**
305
     * @param IOutput $output
306
     */
307
    public function setOutput(IOutput $output) {
308
        $this->output = $output;
309
    }
310
311
    /**
312
     * @return IInput
313
     */
314
    public function getInput() {
315
        return $this->input;
316
    }
317
318
    /**
319
     * @param IInput $input
320
     */
321
    public function setInput(IInput $input) {
322
        $this->input = $input;
323
    }
324
325
    /**
326
     * @return ICommandInvoker
327
     */
328
    public function getCommandInvoker() {
329
        return $this->commandInvoker;
330
    }
331
332
    /**
333
     * @param ICommandInvoker $commandInvoker
334
     */
335
    public function setCommandInvoker(ICommandInvoker $commandInvoker) {
336
        $this->commandInvoker = $commandInvoker;
337
    }
338
339
    /**
340
     * @return ICommandExecutionLock
341
     */
342
    public function getCommandExecutionLock() {
343
        return $this->commandExecutionLock;
344
    }
345
346
    /**
347
     * @param ICommandExecutionLock $commandExecutionLock
348
     */
349
    public function setCommandExecutionLock(ICommandExecutionLock $commandExecutionLock) {
350
        $this->commandExecutionLock = $commandExecutionLock;
351
    }
352
353
    /**
354
     * @param array $args
355
     *
356
     * @throws Exception
357
     */
358
    protected function handleArgs(array $args) {
359
        $groupedArgs = $this->argumentsParser->group($args);
360
        list($groupedArgs, $continue) = $this->runGlobalCommands($groupedArgs);
361
362
        if ($continue === false) {
363
            return;
364
        }
365
366
        try {
367
            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...
368
                ->matchCommands($this->getNotGlobalCommands(), $groupedArgs);
369
            $command = clone $command;
370
            $this->runCommand($command);
371
        } catch (MissingCommandNameException $ex) {
372
            array_unshift($args, $this->getDefaultCommandName());
373
            $this->parseArgs($args);
374
        } catch (Exception $ex) {
375 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...
376
                $widget = new ExceptionWidget($this->input, $this->output);
377
                $widget->render($ex);
378
            } else {
379
                throw $ex;
380
            }
381
        }
382
    }
383
384
    /**
385
     * @param ICommand $command
386
     * @param bool $isolate
387
     *
388
     * @return mixed
389
     * @throws Exception
390
     */
391
    protected function runCommand(ICommand $command, $isolate = true) {
392
        try {
393
            $this->commandExecutionLock->lockCommand($command);
394
395
            if ($isolate) {
396
                $input = clone $this->input;
397
                $output = clone $this->output;
398
            } else {
399
                $input = $this->input;
400
                $output = $this->output;
401
            }
402
403
            $input->setCommand($command);
404
405
            return $this->commandInvoker->run(
406
                $command->getHandler(), $input, $output, $this
407
            );
408
        } catch (Exception $ex) {
409 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...
410
                $widget = new ExceptionWidget($this->input, $this->output);
411
                $widget->render($ex);
412
            } else {
413
                throw $ex;
414
            }
415
        }
416
    }
417
418
    /**
419
     * @param array $groupedArgs
420
     *
421
     * @return bool
422
     */
423
    protected function runGlobalCommands(array $groupedArgs) {
424
        // run commands that are global but will for sure
425
        // not generate any output or interrupt the flow
426
        foreach ($this->getGlobalHiddenCommands() as $command) {
427
            $command = clone $command;
428
            $groupedArgs = $this->argumentsMatcher->matchCommand($command, $groupedArgs, false);
429
            $this->runCommand($command, false);
430
        }
431
432
        // run commands that might generate output or
433
        // try to interrupt the flow
434
        foreach ($this->getGlobalNotHiddenCommands() as $command) {
435
            $command = clone $command;
436
437
            // dirty hack, fix later
438
            // global commands should not steal arguments
439
            $args = $groupedArgs['arguments'];
440
            $groupedArgs = $this->argumentsMatcher->matchCommand($command, $groupedArgs, false);
441
            $groupedArgs['arguments'] = $args;
442
            $continue = $this->runCommand($command);
443
444
            if ($continue === false) {
445
                return [$groupedArgs, false];
446
            }
447
        }
448
449
        return [$groupedArgs, true];
450
    }
451
452
    /**
453
     * @param $command
454
     *
455
     * @throws InvalidCommandException
456
     */
457
    protected function validateCommand($command) {
458
        if ( ! is_string($command) && ! is_object($command)) {
459
            throw new InvalidCommandException(s(
460
                'Command must be either a class name or an instance, "%s" given.',
461
                get_type($command)
462
            ));
463
        }
464
465
        if (is_string($command) && ! class_exists($command)) {
466
            throw new InvalidCommandException(s(
467
                'Command "%s" does not exist.',
468
                $command
469
            ));
470
        }
471
472
        if ( ! method_exists($command, 'setup') || ! method_exists($command, 'run')) {
473
            throw new InvalidCommandException(s(
474
                'Command "%s" must implement methods "setup" and "run".',
475
                get_type($command)
476
            ));
477
        }
478
    }
479
480
    /**
481
     * @return ICommandInvoker
482
     */
483
    protected function createCommandInvoker() {
484
        return new CommandInvoker();
485
    }
486
487
    /**
488
     * @return ICommandExecutionLock
489
     */
490
    private function createCommandExecutionLock() {
491
        return new CommandExecutionLock();
492
    }
493
494
    /**
495
     * Register default command handlers.
496
     */
497
    protected function addDefaultCommands() {
498
        $this->addCommands([
499
            new GlobalVersionCommand(),
500
            new GlobalFormatCommand(),
501
            new GlobalSilentModeCommand(),
502
            new GlobalNoInteractionCommand(),
503
            new GlobalHelpCommand(),
504
            new GlobalVerbosityCommand(),
505
            new GlobalPassthroughCommand(),
506
            new ListCommand(),
507
            new HelpCommand(),
508
        ]);
509
    }
510
511
    /**
512
     * Register default formatter styles.
513
     */
514
    protected function addDefaultStyles() {
515
        $this->consoleFormatter->style('error')->parseStyle('clr=white bg=red');
516
        $this->consoleFormatter->style('warning')->parseStyle('clr=black bg=yellow');
517
        $this->consoleFormatter->style('success')->parseStyle('clr=black bg=green');
518
519
        $this->consoleFormatter->style('question')->parseStyle('clr=green');
520
        $this->consoleFormatter->style('header')->parseStyle('clr=yellow');
521
        $this->consoleFormatter->style('title')->parseStyle('clr=yellow');
522
        $this->consoleFormatter->style('keyword')->parseStyle('clr=green');
523
524
        $this->consoleFormatter->style('green')->parseStyle('clr=green');
525
        $this->consoleFormatter->style('yellow')->parseStyle('clr=yellow');
526
        $this->consoleFormatter->style('red')->parseStyle('clr=red');
527
        $this->consoleFormatter->style('white')->parseStyle('clr=white');
528
        $this->consoleFormatter->style('blue')->parseStyle('clr=blue');
529
        $this->consoleFormatter->style('gray')->parseStyle('clr=gray');
530
        $this->consoleFormatter->style('black')->parseStyle('clr=black');
531
532
        $this->consoleFormatter->style('bold')->parseStyle('fmt=bold');
533
        $this->consoleFormatter->style('italic')->parseStyle('fmt=italic');
534
        $this->consoleFormatter->style('underline')->parseStyle('fmt=underline');
535
        $this->consoleFormatter->style('strikethrough')->parseStyle('fmt=strikethrough');
536
    }
537
538
    /**
539
     * @return ICommand[]
540
     */
541 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...
542
        $commands = [];
543
544
        foreach ($this->getCommands() as $command) {
545
            if ($command->isGlobal() && $command->isHidden()) {
546
                $commands[] = $command;
547
            }
548
        }
549
550
        return $commands;
551
    }
552
553
    /**
554
     * @return ICommand[]
555
     */
556 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...
557
        $commands = [];
558
559
        foreach ($this->getCommands() as $command) {
560
            if ($command->isGlobal() && ! $command->isHidden()) {
561
                $commands[] = $command;
562
            }
563
        }
564
565
        return $commands;
566
    }
567
568
    /**
569
     * @return ICommand[]
570
     */
571 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...
572
        $commands = [];
573
574
        foreach ($this->getCommands() as $command) {
575
            if ( ! $command->isGlobal()) {
576
                $commands[] = $command;
577
            }
578
        }
579
580
        return $commands;
581
    }
582
}
583