ApiGen   F
last analyzed

Complexity

Total Complexity 60

Size/Duplication

Total Lines 496
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
wmc 60
lcom 2
cbo 3
dl 0
loc 496
rs 3.6
c 0
b 0
f 0

39 Methods

Rating   Name   Duplication   Size   Complexity  
A forceTraversable() 0 8 3
A asList() 0 5 2
B asTextBool() 0 23 9
A config() 0 5 1
A source() 0 7 2
A destination() 0 5 1
A extensions() 0 5 1
A exclude() 0 7 2
A skipDocPath() 0 7 2
A skipDocPrefix() 0 7 2
A charset() 0 5 1
A mainProjectNamePrefix() 0 5 1
A title() 0 5 1
A baseUrl() 0 5 1
A googleCseId() 0 5 1
A googleAnalytics() 0 5 1
A templateConfig() 0 5 1
A allowedHtml() 0 5 1
A groups() 0 5 1
A autocomplete() 0 5 1
A accessLevels() 0 5 1
A internal() 0 5 1
A php() 0 5 1
A tree() 0 5 1
A deprecated() 0 5 1
A todo() 0 5 1
A sourceCode() 0 5 1
A download() 0 5 1
A report() 0 5 1
A wipeout() 0 5 1
A quiet() 0 5 1
A progressbar() 0 5 1
A colors() 0 5 1
A updateCheck() 0 5 1
A run() 0 5 1
A __construct() 0 19 5
A args() 0 16 3
A debug() 0 5 1
A getCommand() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like ApiGen 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 ApiGen, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Robo\Task\ApiGen;
3
4
use Robo\Contract\CommandInterface;
5
use Robo\Exception\TaskException;
6
use Robo\Task\BaseTask;
7
use Traversable;
8
9
/**
10
 * Executes ApiGen command to generate documentation
11
 *
12
 * ``` php
13
 * <?php
14
 * // ApiGen Command
15
 * $this->taskApiGen('./vendor/apigen/apigen.phar')
16
 *      ->config('./apigen.neon')
17
 *      ->templateConfig('vendor/apigen/apigen/templates/bootstrap/config.neon')
18
 *      ->wipeout(true)
19
 *       ->run();
20
 * ?>
21
 * ```
22
 */
23
class ApiGen extends BaseTask implements CommandInterface
24
{
25
    use \Robo\Common\ExecOneCommand;
26
27
    const BOOL_NO = 'no';
28
    const BOOL_YES = 'yes';
29
30
    /**
31
     * @var string
32
     */
33
    protected $command;
34
    protected $operation = 'generate';
35
36
    /**
37
     * @param null|string $pathToApiGen
38
     *
39
     * @throws \Robo\Exception\TaskException
40
     */
41
    public function __construct($pathToApiGen = null)
42
    {
43
        $this->command = $pathToApiGen;
44
        $command_parts = [];
45
        preg_match('/((?:.+)?apigen(?:\.phar)?) ?( \w+)? ?(.+)?/', $this->command, $command_parts);
46
        if (count($command_parts) === 3) {
47
            list(, $this->command, $this->operation) = $command_parts;
48
        }
49
        if (count($command_parts) === 4) {
50
            list(, $this->command, $this->operation, $arg) = $command_parts;
51
            $this->arg($arg);
52
        }
53
        if (!$this->command) {
54
            $this->command = $this->findExecutablePhar('apigen');
0 ignored issues
show
Documentation Bug introduced by Greg Anderson
It seems like $this->findExecutablePhar('apigen') can also be of type boolean. However, the property $command is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
55
        }
56
        if (!$this->command) {
57
            throw new TaskException(__CLASS__, "No apigen installation found");
58
        }
59
    }
60
61
    /**
62
     * Pass methods parameters as arguments to executable. Argument values
63
     * are automatically escaped.
64
     *
65
     * @param string|string[] $args
66
     *
67
     * @return $this
68
     */
69
    public function args($args)
70
    {
71
        if (!is_array($args)) {
72
            $args = func_get_args();
73
        }
74
        $args = array_map(function ($arg) {
75
            if (preg_match('/^\w+$/', trim($arg)) === 1) {
76
                $this->operation = $arg;
77
                return null;
78
            }
79
            return $arg;
80
        }, $args);
81
        $args = array_filter($args);
82
        $this->arguments .= ' ' . implode(' ', array_map('static::escape', $args));
83
        return $this;
84
    }
85
86
    /**
87
     * @param array|Traversable|string $arg a single object or something traversable
88
     *
89
     * @return array|Traversable the provided argument if it was already traversable, or the given
90
     *                           argument returned as a one-element array
91
     */
92
    protected static function forceTraversable($arg)
93
    {
94
        $traversable = $arg;
95
        if (!is_array($traversable) && !($traversable instanceof \Traversable)) {
96
            $traversable = array($traversable);
97
        }
98
        return $traversable;
99
    }
100
101
    /**
102
     * @param array|string $arg a single argument or an array of multiple string values
103
     *
104
     * @return string a comma-separated string of all of the provided arguments, suitable
105
     *                as a command-line "list" type argument for ApiGen
106
     */
107
    protected static function asList($arg)
108
    {
109
        $normalized = is_array($arg) ? $arg : array($arg);
110
        return implode(',', $normalized);
111
    }
112
113
    /**
114
     * @param bool|string $val an argument to be normalized
115
     * @param string $default one of self::BOOL_YES or self::BOOK_NO if the provided
116
     *               value could not deterministically be converted to a
117
     *               yes or no value
118
     *
119
     * @return string the given value as a command-line "yes|no" type of argument for ApiGen,
120
     *                or the default value if none could be determined
121
     */
122
    protected static function asTextBool($val, $default)
123
    {
124
        if ($val === self::BOOL_YES || $val === self::BOOL_NO) {
125
            return $val;
126
        }
127
        if (!$val) {
128
            return self::BOOL_NO;
129
        }
130
        if ($val === true) {
131
            return self::BOOL_YES;
132
        }
133
        if (is_numeric($val) && $val != 0) {
134
            return self::BOOL_YES;
135
        }
136
        if (strcasecmp($val[0], 'y') === 0) {
137
            return self::BOOL_YES;
138
        }
139
        if (strcasecmp($val[0], 'n') === 0) {
140
            return self::BOOL_NO;
141
        }
142
        // meh, good enough, let apigen sort it out
143
        return $default;
144
    }
145
146
    /**
147
     * @param string $config
148
     *
149
     * @return $this
150
     */
151
    public function config($config)
152
    {
153
        $this->option('config', $config);
154
        return $this;
155
    }
156
157
    /**
158
     * @param array|string|Traversable $src one or more source values
159
     *
160
     * @return $this
161
     */
162
    public function source($src)
163
    {
164
        foreach (self::forceTraversable($src) as $source) {
165
            $this->option('source', $source);
166
        }
167
        return $this;
168
    }
169
170
    /**
171
     * @param string $dest
172
     *
173
     * @return $this
174
     */
175
    public function destination($dest)
176
    {
177
        $this->option('destination', $dest);
178
        return $this;
179
    }
180
181
    /**
182
     * @param array|string $exts one or more extensions
183
     *
184
     * @return $this
185
     */
186
    public function extensions($exts)
187
    {
188
        $this->option('extensions', self::asList($exts));
189
        return $this;
190
    }
191
192
    /**
193
     * @param array|string $exclude one or more exclusions
194
     *
195
     * @return $this
196
     */
197
    public function exclude($exclude)
198
    {
199
        foreach (self::forceTraversable($exclude) as $excl) {
200
            $this->option('exclude', $excl);
201
        }
202
        return $this;
203
    }
204
205
    /**
206
     * @param array|string|Traversable $path one or more skip-doc-path values
207
     *
208
     * @return $this
209
     */
210
    public function skipDocPath($path)
211
    {
212
        foreach (self::forceTraversable($path) as $skip) {
213
            $this->option('skip-doc-path', $skip);
214
        }
215
        return $this;
216
    }
217
218
    /**
219
     * @param array|string|Traversable $prefix one or more skip-doc-prefix values
220
     *
221
     * @return $this
222
     */
223
    public function skipDocPrefix($prefix)
224
    {
225
        foreach (self::forceTraversable($prefix) as $skip) {
226
            $this->option('skip-doc-prefix', $skip);
227
        }
228
        return $this;
229
    }
230
231
    /**
232
     * @param array|string $charset one or more charsets
233
     *
234
     * @return $this
235
     */
236
    public function charset($charset)
237
    {
238
        $this->option('charset', self::asList($charset));
239
        return $this;
240
    }
241
242
    /**
243
     * @param string $name
244
     *
245
     * @return $this
246
     */
247
    public function mainProjectNamePrefix($name)
248
    {
249
        $this->option('main', $name);
250
        return $this;
251
    }
252
253
    /**
254
     * @param string $title
255
     *
256
     * @return $this
257
     */
258
    public function title($title)
259
    {
260
        $this->option('title', $title);
261
        return $this;
262
    }
263
264
    /**
265
     * @param string $baseUrl
266
     *
267
     * @return $this
268
     */
269
    public function baseUrl($baseUrl)
270
    {
271
        $this->option('base-url', $baseUrl);
272
        return $this;
273
    }
274
275
    /**
276
     * @param string $id
277
     *
278
     * @return $this
279
     */
280
    public function googleCseId($id)
281
    {
282
        $this->option('google-cse-id', $id);
283
        return $this;
284
    }
285
286
    /**
287
     * @param string $trackingCode
288
     *
289
     * @return $this
290
     */
291
    public function googleAnalytics($trackingCode)
292
    {
293
        $this->option('google-analytics', $trackingCode);
294
        return $this;
295
    }
296
297
    /**
298
     * @param mixed $templateConfig
299
     *
300
     * @return $this
301
     */
302
    public function templateConfig($templateConfig)
303
    {
304
        $this->option('template-config', $templateConfig);
305
        return $this;
306
    }
307
308
    /**
309
     * @param array|string $tags one or more supported html tags
310
     *
311
     * @return $this
312
     */
313
    public function allowedHtml($tags)
314
    {
315
        $this->option('allowed-html', self::asList($tags));
316
        return $this;
317
    }
318
319
    /**
320
     * @param string $groups
321
     *
322
     * @return $this
323
     */
324
    public function groups($groups)
325
    {
326
        $this->option('groups', $groups);
327
        return $this;
328
    }
329
330
    /**
331
     * @param array|string $types or more supported autocomplete types
332
     *
333
     * @return $this
334
     */
335
    public function autocomplete($types)
336
    {
337
        $this->option('autocomplete', self::asList($types));
338
        return $this;
339
    }
340
341
    /**
342
     * @param array|string $levels one or more access levels
343
     *
344
     * @return $this
345
     */
346
    public function accessLevels($levels)
347
    {
348
        $this->option('access-levels', self::asList($levels));
349
        return $this;
350
    }
351
352
    /**
353
     * @param boolean|string $internal 'yes' or true if internal, 'no' or false if not
354
     *
355
     * @return $this
356
     */
357
    public function internal($internal)
358
    {
359
        $this->option('internal', self::asTextBool($internal, self::BOOL_NO));
360
        return $this;
361
    }
362
363
    /**
364
     * @param boolean|string $php 'yes' or true to generate documentation for internal php classes,
365
     *                            'no' or false otherwise
366
     *
367
     * @return $this
368
     */
369
    public function php($php)
370
    {
371
        $this->option('php', self::asTextBool($php, self::BOOL_YES));
372
        return $this;
373
    }
374
375
    /**
376
     * @param bool|string $tree 'yes' or true to generate a tree view of classes, 'no' or false otherwise
377
     *
378
     * @return $this
379
     */
380
    public function tree($tree)
381
    {
382
        $this->option('tree', self::asTextBool($tree, self::BOOL_YES));
383
        return $this;
384
    }
385
386
    /**
387
     * @param bool|string $dep 'yes' or true to generate documentation for deprecated classes, 'no' or false otherwise
388
     *
389
     * @return $this
390
     */
391
    public function deprecated($dep)
392
    {
393
        $this->option('deprecated', self::asTextBool($dep, self::BOOL_NO));
394
        return $this;
395
    }
396
397
    /**
398
     * @param bool|string $todo 'yes' or true to document tasks, 'no' or false otherwise
399
     *
400
     * @return $this
401
     */
402
    public function todo($todo)
403
    {
404
        $this->option('todo', self::asTextBool($todo, self::BOOL_NO));
405
        return $this;
406
    }
407
408
    /**
409
     * @param bool|string $src 'yes' or true to generate highlighted source code, 'no' or false otherwise
410
     *
411
     * @return $this
412
     */
413
    public function sourceCode($src)
414
    {
415
        $this->option('source-code', self::asTextBool($src, self::BOOL_YES));
416
        return $this;
417
    }
418
419
    /**
420
     * @param bool|string $zipped 'yes' or true to generate downloadable documentation, 'no' or false otherwise
421
     *
422
     * @return $this
423
     */
424
    public function download($zipped)
425
    {
426
        $this->option('download', self::asTextBool($zipped, self::BOOL_NO));
427
        return $this;
428
    }
429
430
    public function report($path)
431
    {
432
        $this->option('report', $path);
433
        return $this;
434
    }
435
436
    /**
437
     * @param bool|string $wipeout 'yes' or true to clear out the destination directory, 'no' or false otherwise
438
     *
439
     * @return $this
440
     */
441
    public function wipeout($wipeout)
442
    {
443
        $this->option('wipeout', self::asTextBool($wipeout, self::BOOL_YES));
444
        return $this;
445
    }
446
447
    /**
448
     * @param bool|string $quiet 'yes' or true for quiet, 'no' or false otherwise
449
     *
450
     * @return $this
451
     */
452
    public function quiet($quiet)
453
    {
454
        $this->option('quiet', self::asTextBool($quiet, self::BOOL_NO));
455
        return $this;
456
    }
457
458
    /**
459
     * @param bool|string $bar 'yes' or true to display a progress bar, 'no' or false otherwise
460
     *
461
     * @return $this
462
     */
463
    public function progressbar($bar)
464
    {
465
        $this->option('progressbar', self::asTextBool($bar, self::BOOL_YES));
466
        return $this;
467
    }
468
469
    /**
470
     * @param bool|string $colors 'yes' or true colorize the output, 'no' or false otherwise
471
     *
472
     * @return $this
473
     */
474
    public function colors($colors)
475
    {
476
        $this->option('colors', self::asTextBool($colors, self::BOOL_YES));
477
        return $this;
478
    }
479
480
    /**
481
     * @param bool|string $check 'yes' or true to check for updates, 'no' or false otherwise
482
     *
483
     * @return $this
484
     */
485
    public function updateCheck($check)
486
    {
487
        $this->option('update-check', self::asTextBool($check, self::BOOL_YES));
488
        return $this;
489
    }
490
491
    /**
492
     * @param bool|string $debug 'yes' or true to enable debug mode, 'no' or false otherwise
493
     *
494
     * @return $this
495
     */
496
    public function debug($debug)
497
    {
498
        $this->option('debug', self::asTextBool($debug, self::BOOL_NO));
499
        return $this;
500
    }
501
502
    /**
503
     * {@inheritdoc}
504
     */
505
    public function getCommand()
506
    {
507
        return "$this->command $this->operation$this->arguments";
508
    }
509
510
    /**
511
     * {@inheritdoc}
512
     */
513
    public function run()
514
    {
515
        $this->printTaskInfo('Running ApiGen {args}', ['args' => $this->arguments]);
516
        return $this->executeCommand($this->getCommand());
517
    }
518
}
519