CliApplication   C
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 229
Duplicated Lines 3.06 %

Coupling/Cohesion

Components 1
Dependencies 41

Importance

Changes 0
Metric Value
dl 7
loc 229
c 0
b 0
f 0
wmc 29
lcom 1
cbo 41
rs 5

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
A init() 0 5 2
A run() 0 7 2
B doRun() 0 27 3
D printResult() 0 44 10
A selectAction() 4 17 2
A printActionHeader() 0 12 2
A printUsage() 0 7 1
A printActions() 3 9 2
A registerFields() 0 15 1
A registerRenderers() 0 16 1
A shortDescription() 0 4 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
namespace rtens\domin\delivery\cli;
3
4
use rtens\domin\Action;
5
use rtens\domin\ActionRegistry;
6
use rtens\domin\delivery\cli\fields\ArrayField;
7
use rtens\domin\delivery\cli\fields\BooleanField;
8
use rtens\domin\delivery\cli\fields\DateIntervalField;
9
use rtens\domin\delivery\cli\fields\DateTimeField;
10
use rtens\domin\delivery\cli\fields\EnumerationField;
11
use rtens\domin\delivery\cli\fields\FileField;
12
use rtens\domin\delivery\cli\fields\HtmlField;
13
use rtens\domin\delivery\cli\fields\IdentifierField;
14
use rtens\domin\delivery\cli\fields\MultiField;
15
use rtens\domin\delivery\cli\fields\NullableField;
16
use rtens\domin\delivery\cli\fields\ObjectField;
17
use rtens\domin\delivery\cli\fields\PrimitiveField;
18
use rtens\domin\delivery\cli\fields\RangeField;
19
use rtens\domin\delivery\cli\renderers\ArrayRenderer;
20
use rtens\domin\delivery\cli\renderers\BooleanRenderer;
21
use rtens\domin\delivery\cli\renderers\ChartRenderer;
22
use rtens\domin\delivery\cli\renderers\DateIntervalRenderer;
23
use rtens\domin\delivery\cli\renderers\DateTimeRenderer;
24
use rtens\domin\delivery\cli\renderers\DelayedOutputRenderer;
25
use rtens\domin\delivery\cli\renderers\FileRenderer;
26
use rtens\domin\delivery\cli\renderers\HtmlRenderer;
27
use rtens\domin\delivery\cli\renderers\IdentifierRenderer;
28
use rtens\domin\delivery\cli\renderers\ObjectRenderer;
29
use rtens\domin\delivery\cli\renderers\PrimitiveRenderer;
30
use rtens\domin\delivery\cli\renderers\tables\DataTableRenderer;
31
use rtens\domin\delivery\cli\renderers\tables\ObjectTableRenderer;
32
use rtens\domin\delivery\cli\renderers\tables\TableRenderer;
33
use rtens\domin\delivery\FieldRegistry;
34
use rtens\domin\delivery\ParameterReader;
35
use rtens\domin\delivery\RendererRegistry;
36
use rtens\domin\execution\access\AccessControl;
37
use rtens\domin\execution\ExecutionResult;
38
use rtens\domin\execution\FailedResult;
39
use rtens\domin\execution\MissingParametersResult;
40
use rtens\domin\execution\NoResult;
41
use rtens\domin\execution\NotPermittedResult;
42
use rtens\domin\execution\RedirectResult;
43
use rtens\domin\execution\ValueResult;
44
use rtens\domin\Executor;
45
use rtens\domin\reflection\CommentParser;
46
use rtens\domin\reflection\types\TypeFactory;
47
use watoki\factory\Factory;
48
49
class CliApplication {
50
51
    const INTERACTIVE_MODE = '!';
52
53
    const OK = 0;
54
    const ERROR = 1;
55
56
    /** @var Factory */
57
    public $factory;
58
59
    /** @var ActionRegistry */
60
    public $actions;
61
62
    /** @var FieldRegistry */
63
    public $fields;
64
65
    /** @var RendererRegistry */
66
    public $renderers;
67
68
    /** @var TypeFactory */
69
    public $types;
70
71
    /** @var CommentParser */
72
    public $parser;
73
74
    /** @var AccessControl */
75
    public $access;
76
77
    /**
78
     * @param Factory $factory <-
79
     * @param ActionRegistry $actions <-
80
     * @param FieldRegistry $fields <-
81
     * @param RendererRegistry $renderers <-
82
     * @param TypeFactory $types <-
83
     * @param CommentParser $parser <-
84
     * @param AccessControl $access <-
85
     */
86
    public function __construct(Factory $factory, ActionRegistry $actions, FieldRegistry $fields,
87
                                RendererRegistry $renderers, TypeFactory $types, CommentParser $parser,
88
                                AccessControl $access) {
89
        $this->factory = $factory;
90
91
        $this->actions = $actions;
92
        $this->fields = $fields;
93
        $this->renderers = $renderers;
94
        $this->types = $types;
95
        $this->parser = $parser;
96
        $this->access = $access;
97
    }
98
99
    /**
100
     * @param callable $callback Receives the CliApplication instance
101
     * @param null|Factory $factory
102
     * @return Factory
103
     */
104
    public static function init(callable $callback, Factory $factory = null) {
105
        $factory = $factory ?: new Factory();
106
        $callback($factory->setSingleton($factory->getInstance(self::class)));
107
        return $factory;
108
    }
109
110
    public static function run(Factory $factory, Console $console = null) {
111
        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...
112
113
        /** @var self $app */
114
        $app = $factory->getInstance(self::class);
115
        return $app->doRun($console ?: new Console($argv));
116
    }
117
118
    private function doRun(Console $console) {
119
        $outFile = null;
120
121
        if ($console->getArguments()) {
122
            if ($console->getArguments()[0] == self::INTERACTIVE_MODE) {
123
                $outFile = $console->getOption('out', null);
124
125
                $actionId = $this->selectAction($console);
126
                $reader = new InteractiveCliParameterReader($this->fields, $console);
127
128
                $this->printActionHeader($console, $actionId);
129
            } else {
130
                $actionId = $console->getArguments()[0];
131
                $reader = new CliParameterReader($console);
132
            }
133
        } else {
134
            $this->printUsage($console);
135
            $this->printActions($console);
136
            return self::OK;
137
        }
138
139
        $this->registerFields($reader);
140
        $this->registerRenderers();
141
142
        $executor = new Executor($this->actions, $this->fields, $reader, $this->access);
143
        return $this->printResult($console, $executor->execute($actionId), $outFile);
144
    }
145
146
    private function printResult(Console $console, ExecutionResult $result, $outFile) {
147
        if ($result instanceof ValueResult) {
148
            $value = $result->getValue();
149
            $rendered = (string)$this->renderers->getRenderer($value)->render($value);
150
151
            if ($outFile) {
152
                file_put_contents($outFile, $rendered);
153
                $console->writeLine("Output written to [$outFile]");
154
            } else {
155
                $console->writeLine($rendered);
156
            }
157
            return self::OK;
158
159
        } else if ($result instanceof MissingParametersResult) {
160
            $console->writeLine();
161
            $console->writeLine("Missing parameters!");
162
            foreach ($result->getMissingNames() as $missing) {
163
                $console->writeLine('  ' . $missing . ': ' . $result->getException($missing)->getMessage());
164
            }
165
            return self::ERROR;
166
167
        } else if ($result instanceof NotPermittedResult) {
168
            $console->writeLine('Permission denied');
169
            return self::ERROR;
170
171
        } else if ($result instanceof FailedResult) {
172
            $console->writeLine("Error: " . $result->getMessage());
173
174
            $exception = $result->getException();
175
            $console->error(
176
                get_class($exception) . ': ' . $exception->getMessage() . ' ' .
177
                '[' . $exception->getFile() . ':' . $exception->getLine() . ']' . "\n" .
178
                $exception->getTraceAsString()
179
            );
180
            return $exception->getCode() ?: self::ERROR;
181
182
        } else if ($result instanceof NoResult || $result instanceof RedirectResult) {
183
            return self::OK;
184
185
        } else {
186
            $console->writeLine('Cannot print [' . (new \ReflectionClass($result))->getShortName() . ']');
187
            return self::OK;
188
        }
189
    }
190
191
    private function selectAction(Console $console) {
192
        $console->writeLine();
193
        $console->writeLine('Available Actions');
194
        $console->writeLine('~~~~~~~~~~~~~~~~~');
195
196
        $i = 1;
197
        $actionIds = [];
198 View Code Duplication
        foreach ($this->actions->getAllActions() as $id => $action) {
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...
199
            $console->writeLine($i++ . " - " . $action->caption() . $this->shortDescription($action));
200
            $actionIds[] = $id;
201
        }
202
203
        $console->writeLine();
204
        $actionIndex = $console->read('Action: ');
205
206
        return $actionIds[$actionIndex - 1];
207
    }
208
209
    private function printActionHeader(Console $console, $actionId) {
210
        $action = $this->actions->getAction($actionId);
211
        $console->writeLine();
212
        $console->writeLine($action->caption());
213
        $console->writeLine(str_repeat('~', strlen($action->caption())));
214
        $console->writeLine();
215
216
        if ($action->description()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $action->description() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
217
            $console->writeLine($action->description());
218
            $console->writeLine();
219
        }
220
    }
221
222
    private function printUsage(Console $console) {
223
        $console->writeLine();
224
225
        $console->writeLine("Interactive mode: php {$console->getScriptName()} !");
226
        $console->writeLine("Execute Action:   php {$console->getScriptName()} <actionId> --<parameterName> <parameterValue> ...");
227
        $console->writeLine();
228
    }
229
230
    private function printActions(Console $console) {
231
        $console->writeLine('Available Actions');
232
        $console->writeLine('~~~~~~~~~~~~~~~~~');
233
234 View Code Duplication
        foreach ($this->actions->getAllActions() as $id => $action) {
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...
235
            $console->writeLine($id . ' - ' . $action->caption() . $this->shortDescription($action));
236
        }
237
        $console->writeLine();
238
    }
239
240
    private function registerFields(ParameterReader $reader) {
241
        $this->fields->add(new PrimitiveField());
242
        $this->fields->add(new RangeField());
243
        $this->fields->add(new BooleanField());
244
        $this->fields->add(new FileField());
245
        $this->fields->add(new HtmlField($reader));
246
        $this->fields->add(new DateTimeField());
247
        $this->fields->add(new DateIntervalField());
248
        $this->fields->add(new ArrayField($this->fields, $reader));
249
        $this->fields->add(new NullableField($this->fields, $reader));
250
        $this->fields->add(new ObjectField($this->types, $this->fields, $reader));
251
        $this->fields->add(new MultiField($this->fields, $reader));
252
        $this->fields->add(new IdentifierField($this->fields));
253
        $this->fields->add(new EnumerationField($this->fields));
254
    }
255
256
    private function registerRenderers() {
257
        $this->renderers->add(new BooleanRenderer());
258
        $this->renderers->add(new PrimitiveRenderer());
259
        $this->renderers->add(new DateTimeRenderer());
260
        $this->renderers->add(new DateIntervalRenderer());
261
        $this->renderers->add(new HtmlRenderer());
262
        $this->renderers->add(new IdentifierRenderer());
263
        $this->renderers->add(new FileRenderer(''));
264
        $this->renderers->add(new DelayedOutputRenderer());
265
        $this->renderers->add(new ObjectTableRenderer($this->renderers));
266
        $this->renderers->add(new DataTableRenderer($this->renderers));
267
        $this->renderers->add(new TableRenderer($this->renderers));
268
        $this->renderers->add(new ChartRenderer($this->renderers));
269
        $this->renderers->add(new ArrayRenderer($this->renderers));
270
        $this->renderers->add(new ObjectRenderer($this->renderers, $this->types));
271
    }
272
273
    private function shortDescription(Action $action) {
274
        $description = $this->parser->shorten($action->description());
275
        return $description ? " ($description)" : '';
276
    }
277
}
278