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