Total Complexity | 72 |
Total Lines | 398 |
Duplicated Lines | 0 % |
Changes | 2 | ||
Bugs | 0 | Features | 0 |
Complex classes like Command often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Command, and based on these observations, apply Extract Interface, too.
1 | <?php namespace Tarsana\Command; |
||
19 | class Command { |
||
20 | |||
21 | protected $name; |
||
22 | protected $version; |
||
23 | protected $description; |
||
24 | |||
25 | protected $syntax; |
||
26 | protected $descriptions; |
||
27 | |||
28 | protected $options; |
||
29 | protected $args; |
||
30 | |||
31 | protected $action; |
||
32 | protected $commands; |
||
33 | |||
34 | protected $console; |
||
35 | protected $fs; |
||
36 | protected $templatesLoader; |
||
37 | protected $config; |
||
38 | |||
39 | public static function create(callable $action = null) { |
||
40 | $command = new Command; |
||
41 | if (null !== $action) |
||
42 | $command->action($action); |
||
43 | return $command; |
||
44 | } |
||
45 | |||
46 | public function __construct() |
||
47 | { |
||
48 | $this->commands([]) |
||
49 | ->name('Unknown') |
||
50 | ->version('1.0.0') |
||
51 | ->description('...') |
||
52 | ->descriptions([]) |
||
53 | ->options([]) |
||
54 | ->console(new Console) |
||
55 | ->fs(new Filesystem('.')) |
||
56 | ->configPaths([]) |
||
57 | ->setupSubCommands() |
||
58 | ->init(); |
||
59 | } |
||
60 | |||
61 | /** |
||
62 | * name getter and setter. |
||
63 | * |
||
64 | * @param string |
||
65 | * @return mixed |
||
66 | */ |
||
67 | public function name(string $value = null) |
||
68 | { |
||
69 | if (null === $value) { |
||
70 | return $this->name; |
||
71 | } |
||
72 | $this->name = $value; |
||
73 | return $this; |
||
74 | } |
||
75 | |||
76 | /** |
||
77 | * version getter and setter. |
||
78 | * |
||
79 | * @param string |
||
80 | * @return mixed |
||
81 | */ |
||
82 | public function version(string $value = null) |
||
83 | { |
||
84 | if (null === $value) { |
||
85 | return $this->version; |
||
86 | } |
||
87 | $this->version = $value; |
||
88 | return $this; |
||
89 | } |
||
90 | |||
91 | /** |
||
92 | * description getter and setter. |
||
93 | * |
||
94 | * @param string |
||
95 | * @return mixed |
||
96 | */ |
||
97 | public function description(string $value = null) |
||
98 | { |
||
99 | if (null === $value) { |
||
100 | return $this->description; |
||
101 | } |
||
102 | $this->description = $value; |
||
103 | return $this; |
||
104 | } |
||
105 | |||
106 | /** |
||
107 | * descriptions getter and setter. |
||
108 | * |
||
109 | * @param string |
||
110 | * @return mixed |
||
111 | */ |
||
112 | public function descriptions(array $value = null) |
||
113 | { |
||
114 | if (null === $value) { |
||
115 | return $this->descriptions; |
||
116 | } |
||
117 | $this->descriptions = $value; |
||
118 | return $this; |
||
119 | } |
||
120 | |||
121 | /** |
||
122 | * syntax getter and setter. |
||
123 | * |
||
124 | * @param string|null $syntax |
||
125 | * @return Syntax|self |
||
126 | */ |
||
127 | public function syntax(string $syntax = null) |
||
128 | { |
||
129 | if (null === $syntax) |
||
130 | return $this->syntax; |
||
131 | |||
132 | $this->syntax = S::syntax()->parse("{{$syntax}| }"); |
||
133 | return $this; |
||
134 | } |
||
135 | |||
136 | /** |
||
137 | * options getter and setter. |
||
138 | * |
||
139 | * @param array |
||
140 | * @return mixed |
||
141 | */ |
||
142 | public function options(array $options = null) |
||
143 | { |
||
144 | if (null === $options) { |
||
145 | return $this->options; |
||
146 | } |
||
147 | |||
148 | $this->options = []; |
||
149 | foreach($options as $option) |
||
150 | $this->options[$option] = false; |
||
151 | |||
152 | return $this; |
||
153 | } |
||
154 | |||
155 | /** |
||
156 | * option getter. |
||
157 | * |
||
158 | * @param string |
||
159 | * @return mixed |
||
160 | */ |
||
161 | public function option(string $name) |
||
162 | { |
||
163 | if (!array_key_exists($name, $this->options)) |
||
164 | throw new \InvalidArgumentException("Unknown option '{$name}'"); |
||
165 | return $this->options[$name]; |
||
166 | } |
||
167 | |||
168 | /** |
||
169 | * args getter and setter. |
||
170 | * |
||
171 | * @param stdClass |
||
172 | * @return mixed |
||
173 | */ |
||
174 | public function args(\stdClass $value = null) |
||
175 | { |
||
176 | if (null === $value) { |
||
177 | return $this->args; |
||
178 | } |
||
179 | $this->args = $value; |
||
180 | return $this; |
||
181 | } |
||
182 | |||
183 | /** |
||
184 | * console getter and setter. |
||
185 | * |
||
186 | * @param ConsoleInterface |
||
187 | * @return mixed |
||
188 | */ |
||
189 | public function console(ConsoleInterface $value = null) |
||
190 | { |
||
191 | if (null === $value) { |
||
192 | return $this->console; |
||
193 | } |
||
194 | $this->console = $value; |
||
195 | foreach ($this->commands as $name => $command) { |
||
196 | $command->console = $value; |
||
197 | } |
||
198 | return $this; |
||
199 | } |
||
200 | |||
201 | /** |
||
202 | * fs getter and setter. |
||
203 | * |
||
204 | * @param Tarsana\IO\Filesystem|string |
||
205 | * @return mixed |
||
206 | */ |
||
207 | public function fs($value = null) |
||
219 | } |
||
220 | |||
221 | /** |
||
222 | * templatesLoader getter and setter. |
||
223 | * |
||
224 | * @param Tarsana\Command\Interfaces\Template\TemplateLoaderInterface |
||
225 | * @return mixed |
||
226 | */ |
||
227 | public function templatesLoader(TemplateLoaderInterface $value = null) |
||
228 | { |
||
229 | if (null === $value) { |
||
230 | return $this->templatesLoader; |
||
231 | } |
||
232 | $this->templatesLoader = $value; |
||
233 | foreach ($this->commands as $name => $command) { |
||
234 | $command->templatesLoader = $value; |
||
235 | } |
||
236 | return $this; |
||
237 | } |
||
238 | |||
239 | public function templatesPath(string $path, string $cachePath = null) |
||
240 | { |
||
241 | $this->templatesLoader = new TemplateLoader($path, $cachePath); |
||
242 | foreach ($this->commands as $name => $command) { |
||
243 | $command->templatesLoader = $this->templatesLoader(); |
||
244 | } |
||
245 | return $this; |
||
246 | } |
||
247 | |||
248 | public function template(string $name) |
||
249 | { |
||
250 | if (null === $this->templatesLoader) |
||
251 | throw new \Exception("Please initialize the templates loader before trying to load templates!"); |
||
252 | return $this->templatesLoader->load($name); |
||
253 | } |
||
254 | |||
255 | public function configPaths(array $paths) |
||
256 | { |
||
257 | $configLoader = new ConfigLoader($this->fs); |
||
258 | $this->config = $configLoader->load($paths); |
||
259 | foreach ($this->commands as $name => $command) { |
||
260 | $command->config = $this->config; |
||
261 | } |
||
262 | return $this; |
||
263 | } |
||
264 | |||
265 | public function config(string $path = null) |
||
266 | { |
||
267 | return $this->config->get($path); |
||
268 | } |
||
269 | |||
270 | /** |
||
271 | * action getter and setter. |
||
272 | * |
||
273 | * @param callable |
||
274 | * @return mixed |
||
275 | */ |
||
276 | public function action(callable $value = null) |
||
277 | { |
||
278 | if (null === $value) { |
||
279 | return $this->action; |
||
280 | } |
||
281 | $this->action = $value; |
||
282 | return $this; |
||
283 | } |
||
284 | |||
285 | /** |
||
286 | * commands getter and setter. |
||
287 | * |
||
288 | * @param array |
||
289 | * @return mixed |
||
290 | */ |
||
291 | public function commands(array $value = null) |
||
292 | { |
||
293 | if (null === $value) { |
||
294 | return $this->commands; |
||
295 | } |
||
296 | $this->commands = []; |
||
297 | foreach ($value as $name => $command) { |
||
298 | $this->command($name, $command); |
||
299 | } |
||
300 | return $this; |
||
301 | } |
||
302 | |||
303 | public function command(string $name, Command $command = null) |
||
304 | { |
||
305 | if (null === $command) { |
||
306 | if (!array_key_exists($name, $this->commands)) |
||
307 | throw new \InvalidArgumentException("subcommand '{$name}' not found!"); |
||
308 | return $this->commands[$name]; |
||
309 | } |
||
310 | $this->commands[$name] = $command; |
||
311 | return $this; |
||
312 | } |
||
313 | |||
314 | public function hasCommand(string $name) : bool |
||
315 | { |
||
316 | return array_key_exists($name, $this->commands); |
||
317 | } |
||
318 | |||
319 | protected function setupSubCommands() |
||
320 | { |
||
321 | return $this->command('--help', new HelpCommand($this)) |
||
322 | ->command('--version', new VersionCommand($this)) |
||
323 | ->command('-i', new InteractiveCommand($this)); |
||
324 | } |
||
325 | |||
326 | public function describe(string $name, string $description = null) |
||
327 | { |
||
328 | if (null === $description) |
||
329 | return array_key_exists($name, $this->descriptions) |
||
330 | ? $this->descriptions[$name] : ''; |
||
331 | if (substr($name, 0, 2) == '--' && array_key_exists($name, $this->options())) { |
||
332 | $this->descriptions[$name] = $description; |
||
333 | return $this; |
||
334 | } |
||
335 | try { |
||
336 | $this->syntax->field($name); |
||
337 | // throws exception if field is missing |
||
338 | $this->descriptions[$name] = $description; |
||
339 | return $this; |
||
340 | } catch (\Exception $e) { |
||
341 | throw new \InvalidArgumentException("Unknown field '{$name}'"); |
||
342 | } |
||
343 | } |
||
344 | |||
345 | public function run(array $args = null, array $options = [], bool $rawArgs = true) |
||
346 | { |
||
347 | try { |
||
348 | $this->clear(); |
||
349 | |||
350 | if ($rawArgs) { |
||
351 | if (null === $args) { |
||
352 | $args = $GLOBALS['argv']; |
||
353 | array_shift($args); |
||
354 | } |
||
355 | |||
356 | if (!empty($args) && array_key_exists($args[0], $this->commands)) { |
||
357 | $name = $args[0]; |
||
358 | array_shift($args); |
||
359 | return $this->command($name)->run($args); |
||
360 | } |
||
361 | |||
362 | $this->parseArguments($args); |
||
363 | } else { |
||
364 | $this->args = (object) $args; |
||
365 | foreach ($options as $name) { |
||
366 | if (!array_key_exists($name, $this->options)) |
||
367 | throw new \Exception("Unknown option '{$name}'"); |
||
368 | $this->options[$name] = true; |
||
369 | } |
||
370 | } |
||
371 | |||
372 | return $this->fire(); |
||
373 | } catch (\Exception $e) { |
||
374 | $this->handleError($e); |
||
375 | } |
||
376 | } |
||
377 | |||
378 | protected function fire() |
||
379 | { |
||
380 | return (null === $this->action) |
||
381 | ? $this->execute() |
||
382 | : ($this->action)($this); |
||
383 | } |
||
384 | |||
385 | protected function clear() |
||
386 | { |
||
387 | $this->args = null; |
||
388 | foreach($this->options as $name => $value) { |
||
389 | $this->options[$name] = false; |
||
390 | } |
||
391 | } |
||
392 | |||
393 | protected function parseArguments(array $args) |
||
394 | { |
||
395 | $arguments = []; |
||
396 | foreach ($args as &$arg) { |
||
397 | if (array_key_exists($arg, $this->options)) |
||
398 | $this->options[$arg] = true; |
||
399 | else |
||
400 | $arguments[] = $arg; |
||
401 | } |
||
402 | if (null === $this->syntax) { |
||
403 | $this->args = null; |
||
404 | } else { |
||
405 | $arguments = T::join($arguments, ' '); |
||
406 | $this->args = $this->syntax->parse($arguments); |
||
407 | } |
||
408 | } |
||
409 | |||
410 | protected function handleError(\Exception $e) { |
||
411 | $output = (new ExceptionPrinter)->print($e); |
||
412 | $this->console()->error($output); |
||
413 | } |
||
414 | |||
415 | protected function init() {} |
||
416 | protected function execute() {} |
||
417 | |||
418 | } |
||
419 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths