ApiGen   F
last analyzed

Complexity

Total Complexity 60

Size/Duplication

Total Lines 538
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

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

39 Methods

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