Completed
Push — master ( 3074e4...1f241a )
by Greg
02:20
created

CommandInfo::arguments()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
namespace Consolidation\AnnotatedCommand\Parser;
3
4
use Symfony\Component\Console\Input\InputOption;
5
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParser;
6
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParserFactory;
7
use Consolidation\AnnotatedCommand\AnnotationData;
8
9
/**
10
 * Given a class and method name, parse the annotations in the
11
 * DocBlock comment, and provide accessor methods for all of
12
 * the elements that are needed to create a Symfony Console Command.
13
 *
14
 * Note that the name of this class is now somewhat of a misnomer,
15
 * as we now use it to hold annotation data for hooks as well as commands.
16
 * It would probably be better to rename this to MethodInfo at some point.
17
 */
18
class CommandInfo
19
{
20
    /**
21
     * Serialization schema version. Incremented every time the serialization schema changes.
22
     */
23
    const SERIALIZATION_SCHEMA_VERSION = 1;
24
25
    /**
26
     * @var \ReflectionMethod
27
     */
28
    protected $reflection;
29
30
    /**
31
     * @var boolean
32
     * @var string
33
    */
34
    protected $docBlockIsParsed = false;
35
36
    /**
37
     * @var string
38
     */
39
    protected $name;
40
41
    /**
42
     * @var string
43
     */
44
    protected $description = '';
45
46
    /**
47
     * @var string
48
     */
49
    protected $help = '';
50
51
    /**
52
     * @var DefaultsWithDescriptions
53
     */
54
    protected $options;
55
56
    /**
57
     * @var DefaultsWithDescriptions
58
     */
59
    protected $arguments;
60
61
    /**
62
     * @var array
63
     */
64
    protected $exampleUsage = [];
65
66
    /**
67
     * @var AnnotationData
68
     */
69
    protected $otherAnnotations;
70
71
    /**
72
     * @var array
73
     */
74
    protected $aliases = [];
75
76
    /**
77
     * @var InputOption[]
78
     */
79
    protected $inputOptions;
80
81
    /**
82
     * @var string
83
     */
84
    protected $methodName;
85
86
    /**
87
     * @var string
88
     */
89
    protected $returnType;
90
91
    /**
92
     * Create a new CommandInfo class for a particular method of a class.
93
     *
94
     * @param string|mixed $classNameOrInstance The name of a class, or an
95
     *   instance of it, or an array of cached data.
96
     * @param string $methodName The name of the method to get info about.
97
     * @param array $cache Cached data
98
     * @deprecated Use CommandInfo::create() or CommandInfo::deserialize()
99
     *   instead. In the future, this constructor will be protected.
100
     */
101
    public function __construct($classNameOrInstance, $methodName, $cache = [])
102
    {
103
        $this->reflection = new \ReflectionMethod($classNameOrInstance, $methodName);
104
        $this->methodName = $methodName;
105
106
        // If the cache came from a newer version, ignore it and
107
        // regenerate the cached information.
108
        if (!empty($cache) && static::isValidSerializedData($cache) && !$this->cachedFileIsModified($cache)) {
109
            $this->constructFromCache($cache);
110
            $this->docBlockIsParsed = true;
111
        } else {
112
            $this->constructFromClassAndMethod($classNameOrInstance, $methodName);
113
        }
114
    }
115
116
    public static function create($classNameOrInstance, $methodName)
117
    {
118
        return new self($classNameOrInstance, $methodName);
119
    }
120
121
    public static function deserialize($cache)
122
    {
123
        $cache = (array)$cache;
124
125
        $classNameOrInstance = $cache['class'];
126
        $methodName = $cache['method_name'];
127
128
        return new self($classNameOrInstance, $methodName, $cache);
129
    }
130
131
    public static function isValidSerializedData($cache)
132
    {
133
        return
134
            isset($cache['schema']) &&
135
            isset($cache['method_name']) &&
136
            isset($cache['mtime']) &&
137
            ($cache['schema'] > 0) &&
138
            ($cache['schema'] <= self::SERIALIZATION_SCHEMA_VERSION);
139
    }
140
141
    public function cachedFileIsModified($cache)
142
    {
143
        $path = $this->reflection->getFileName();
144
        return filemtime($path) != $cache['mtime'];
145
    }
146
147
    protected function constructFromClassAndMethod($classNameOrInstance, $methodName)
0 ignored issues
show
Unused Code introduced by
The parameter $classNameOrInstance is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
148
    {
149
        $this->otherAnnotations = new AnnotationData();
150
        // Set up a default name for the command from the method name.
151
        // This can be overridden via @command or @name annotations.
152
        $this->name = $this->convertName($methodName);
153
        $this->options = new DefaultsWithDescriptions($this->determineOptionsFromParameters(), false);
154
        $this->arguments = $this->determineAgumentClassifications();
155
    }
156
157
    protected function constructFromCache($info_array)
158
    {
159
        $info_array += $this->defaultSerializationData();
160
161
        $this->name = $info_array['name'];
162
        $this->methodName = $info_array['method_name'];
163
        $this->otherAnnotations = new AnnotationData((array) $info_array['annotations']);
164
        $this->arguments = new DefaultsWithDescriptions();
165
        $this->options = new DefaultsWithDescriptions();
166
        $this->aliases = $info_array['aliases'];
167
        $this->help = $info_array['help'];
168
        $this->description = $info_array['description'];
169
        $this->exampleUsage = $info_array['example_usages'];
170
        $this->returnType = $info_array['return_type'];
0 ignored issues
show
Documentation Bug introduced by
It seems like $info_array['return_type'] of type array is incompatible with the declared type string of property $returnType.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
171
172 View Code Duplication
        foreach ((array)$info_array['arguments'] as $key => $info) {
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...
173
            $info = (array)$info;
174
            $this->arguments->add($key, $info['description']);
175
            if (array_key_exists('default', $info)) {
176
                $this->arguments->setDefaultValue($key, $info['default']);
177
            }
178
        }
179 View Code Duplication
        foreach ((array)$info_array['options'] as $key => $info) {
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...
180
            $info = (array)$info;
181
            $this->options->add($key, $info['description']);
182
            if (array_key_exists('default', $info)) {
183
                $this->options->setDefaultValue($key, $info['default']);
184
            }
185
        }
186
187
        $this->input_options = [];
0 ignored issues
show
Bug introduced by
The property input_options does not seem to exist. Did you mean options?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
188
        foreach ((array)$info_array['input_options'] as $i => $option) {
189
            $option = (array) $option;
190
            $this->inputOptions[$i] = new InputOption(
191
                $option['name'],
192
                $option['shortcut'],
193
                $option['mode'],
194
                $option['description'],
195
                $option['default']
196
            );
197
        }
198
    }
199
200
    public function serialize()
201
    {
202
        $path = $this->reflection->getFileName();
203
204
        $info = [
205
            'schema' => self::SERIALIZATION_SCHEMA_VERSION,
206
            'class' => $this->reflection->getDeclaringClass()->getName(),
207
            'method_name' => $this->getMethodName(),
208
            'name' => $this->getName(),
209
            'description' => $this->getDescription(),
210
            'help' => $this->getHelp(),
211
            'aliases' => $this->getAliases(),
212
            'annotations' => $this->getAnnotations()->getArrayCopy(),
213
            // Todo: Test This.
214
            'topics' => $this->getTopics(),
215
            'example_usages' => $this->getExampleUsages(),
216
            'return_type' => $this->getReturnType(),
217
            'mtime' => filemtime($path),
218
        ] + $this->defaultSerializationData();
219 View Code Duplication
        foreach ($this->arguments()->getValues() as $key => $val) {
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...
220
            $info['arguments'][$key] = [
221
                'description' => $this->arguments()->getDescription($key),
222
            ];
223
            if ($this->arguments()->hasDefault($key)) {
224
                $info['arguments'][$key]['default'] = $val;
225
            }
226
        }
227 View Code Duplication
        foreach ($this->options()->getValues() as $key => $val) {
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...
228
            $info['options'][$key] = [
229
                'description' => $this->options()->getDescription($key),
230
            ];
231
            if ($this->options()->hasDefault($key)) {
232
                $info['options'][$key]['default'] = $val;
233
            }
234
        }
235
        foreach ($this->getParameters() as $i => $parameter) {
0 ignored issues
show
Unused Code introduced by
This foreach statement is empty and can be removed.

This check looks for foreach loops that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

Consider removing the loop.

Loading history...
236
            // TODO: Also cache input/output params
237
        }
238
        foreach ($this->inputOptions() as $i => $option) {
239
            $mode = 0;
240
            if ($option->isValueRequired()) {
241
                $mode |= InputOption::VALUE_REQUIRED;
242
            }
243
            if ($option->isValueOptional()) {
244
                $mode |= InputOption::VALUE_OPTIONAL;
245
            }
246
            if ($option->isArray()) {
247
                $mode |= InputOption::VALUE_IS_ARRAY;
248
            }
249
            if (!$mode) {
250
                $mode = InputOption::VALUE_NONE;
251
            }
252
253
            $info['input_options'][$i] = [
254
                'name' => $option->getName(),
255
                'shortcut' => $option->getShortcut(),
256
                'mode' => $mode,
257
                'description' => $option->getDescription(),
258
                'default' => null,
259
            ];
260
            if ($option->isValueOptional()) {
261
                $info['input_options'][$i]['default'] = $option->getDefault();
262
            }
263
        }
264
        return $info;
265
    }
266
267
    /**
268
     * Default data for serialization.
269
     * @return array
270
     */
271
    protected function defaultSerializationData()
272
    {
273
        return [
274
            'description' => '',
275
            'help' => '',
276
            'aliases' => [],
277
            'annotations' => [],
278
            'topics' => [],
279
            'example_usages' => [],
280
            'return_type' => [],
281
            'parameters' => [],
282
            'arguments' => [],
283
            'arguments' => [],
284
            'options' => [],
285
            'input_options' => [],
286
            'mtime' => 0,
287
        ];
288
    }
289
290
    /**
291
     * Recover the method name provided to the constructor.
292
     *
293
     * @return string
294
     */
295
    public function getMethodName()
296
    {
297
        return $this->methodName;
298
    }
299
300
    /**
301
     * Return the primary name for this command.
302
     *
303
     * @return string
304
     */
305
    public function getName()
306
    {
307
        $this->parseDocBlock();
308
        return $this->name;
309
    }
310
311
    /**
312
     * Set the primary name for this command.
313
     *
314
     * @param string $name
315
     */
316
    public function setName($name)
317
    {
318
        $this->name = $name;
319
        return $this;
320
    }
321
322
    public function getReturnType()
323
    {
324
        $this->parseDocBlock();
325
        return $this->returnType;
326
    }
327
328
    public function setReturnType($returnType)
329
    {
330
        $this->returnType = $returnType;
331
        return $this;
332
    }
333
334
    /**
335
     * Get any annotations included in the docblock comment for the
336
     * implementation method of this command that are not already
337
     * handled by the primary methods of this class.
338
     *
339
     * @return AnnotationData
340
     */
341
    public function getRawAnnotations()
342
    {
343
        $this->parseDocBlock();
344
        return $this->otherAnnotations;
345
    }
346
347
    /**
348
     * Get any annotations included in the docblock comment,
349
     * also including default values such as @command.  We add
350
     * in the default @command annotation late, and only in a
351
     * copy of the annotation data because we use the existance
352
     * of a @command to indicate that this CommandInfo is
353
     * a command, and not a hook or anything else.
354
     *
355
     * @return AnnotationData
356
     */
357
    public function getAnnotations()
358
    {
359
        return new AnnotationData(
360
            $this->getRawAnnotations()->getArrayCopy() +
361
            [
362
                'command' => $this->getName(),
363
            ]
364
        );
365
    }
366
367
    /**
368
     * Return a specific named annotation for this command.
369
     *
370
     * @param string $annotation The name of the annotation.
371
     * @return string
372
     */
373
    public function getAnnotation($annotation)
374
    {
375
        // hasAnnotation parses the docblock
376
        if (!$this->hasAnnotation($annotation)) {
377
            return null;
378
        }
379
        return $this->otherAnnotations[$annotation];
380
    }
381
382
    /**
383
     * Check to see if the specified annotation exists for this command.
384
     *
385
     * @param string $annotation The name of the annotation.
386
     * @return boolean
387
     */
388
    public function hasAnnotation($annotation)
389
    {
390
        $this->parseDocBlock();
391
        return isset($this->otherAnnotations[$annotation]);
392
    }
393
394
    /**
395
     * Save any tag that we do not explicitly recognize in the
396
     * 'otherAnnotations' map.
397
     */
398
    public function addAnnotation($name, $content)
399
    {
400
        $this->otherAnnotations[$name] = $content;
401
    }
402
403
    /**
404
     * Remove an annotation that was previoudly set.
405
     */
406
    public function removeAnnotation($name)
407
    {
408
        unset($this->otherAnnotations[$name]);
409
    }
410
411
    /**
412
     * Get the synopsis of the command (~first line).
413
     *
414
     * @return string
415
     */
416
    public function getDescription()
417
    {
418
        $this->parseDocBlock();
419
        return $this->description;
420
    }
421
422
    /**
423
     * Set the command description.
424
     *
425
     * @param string $description The description to set.
426
     */
427
    public function setDescription($description)
428
    {
429
        $this->description = $description;
430
        return $this;
431
    }
432
433
    /**
434
     * Get the help text of the command (the description)
435
     */
436
    public function getHelp()
437
    {
438
        $this->parseDocBlock();
439
        return $this->help;
440
    }
441
    /**
442
     * Set the help text for this command.
443
     *
444
     * @param string $help The help text.
445
     */
446
    public function setHelp($help)
447
    {
448
        $this->help = $help;
449
        return $this;
450
    }
451
452
    /**
453
     * Return the list of aliases for this command.
454
     * @return string[]
455
     */
456
    public function getAliases()
457
    {
458
        $this->parseDocBlock();
459
        return $this->aliases;
460
    }
461
462
    /**
463
     * Set aliases that can be used in place of the command's primary name.
464
     *
465
     * @param string|string[] $aliases
466
     */
467
    public function setAliases($aliases)
468
    {
469
        if (is_string($aliases)) {
470
            $aliases = explode(',', static::convertListToCommaSeparated($aliases));
471
        }
472
        $this->aliases = array_filter($aliases);
473
        return $this;
474
    }
475
476
    /**
477
     * Return the examples for this command. This is @usage instead of
478
     * @example because the later is defined by the phpdoc standard to
479
     * be example method calls.
480
     *
481
     * @return string[]
482
     */
483
    public function getExampleUsages()
484
    {
485
        $this->parseDocBlock();
486
        return $this->exampleUsage;
487
    }
488
489
    /**
490
     * Add an example usage for this command.
491
     *
492
     * @param string $usage An example of the command, including the command
493
     *   name and all of its example arguments and options.
494
     * @param string $description An explanation of what the example does.
495
     */
496
    public function setExampleUsage($usage, $description)
497
    {
498
        $this->exampleUsage[$usage] = $description;
499
        return $this;
500
    }
501
502
    /**
503
     * Return the topics for this command.
504
     *
505
     * @return string[]
506
     */
507
    public function getTopics()
508
    {
509
        if (!$this->hasAnnotation('topics')) {
510
            return [];
511
        }
512
        $topics = $this->getAnnotation('topics');
513
        return explode(',', trim($topics));
514
    }
515
516
    /**
517
     * Return the list of refleaction parameters.
518
     *
519
     * @return ReflectionParameter[]
520
     */
521
    public function getParameters()
522
    {
523
        return $this->reflection->getParameters();
524
    }
525
526
    /**
527
     * Descriptions of commandline arguements for this command.
528
     *
529
     * @return DefaultsWithDescriptions
530
     */
531
    public function arguments()
532
    {
533
        return $this->arguments;
534
    }
535
536
    /**
537
     * Descriptions of commandline options for this command.
538
     *
539
     * @return DefaultsWithDescriptions
540
     */
541
    public function options()
542
    {
543
        return $this->options;
544
    }
545
546
    /**
547
     * Get the inputOptions for the options associated with this CommandInfo
548
     * object, e.g. via @option annotations, or from
549
     * $options = ['someoption' => 'defaultvalue'] in the command method
550
     * parameter list.
551
     *
552
     * @return InputOption[]
553
     */
554
    public function inputOptions()
555
    {
556
        if (!isset($this->inputOptions)) {
557
            $this->inputOptions = $this->createInputOptions();
558
        }
559
        return $this->inputOptions;
560
    }
561
562
    protected function createInputOptions()
563
    {
564
        $explicitOptions = [];
565
566
        $opts = $this->options()->getValues();
567
        foreach ($opts as $name => $defaultValue) {
568
            $description = $this->options()->getDescription($name);
569
570
            $fullName = $name;
571
            $shortcut = '';
572
            if (strpos($name, '|')) {
573
                list($fullName, $shortcut) = explode('|', $name, 2);
574
            }
575
576
            if (is_bool($defaultValue)) {
577
                $explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_NONE, $description);
578
            } elseif ($defaultValue === InputOption::VALUE_REQUIRED) {
579
                $explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_REQUIRED, $description);
580
            } else {
581
                $explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_OPTIONAL, $description, $defaultValue);
582
            }
583
        }
584
585
        return $explicitOptions;
586
    }
587
588
    /**
589
     * An option might have a name such as 'silent|s'. In this
590
     * instance, we will allow the @option or @default tag to
591
     * reference the option only by name (e.g. 'silent' or 's'
592
     * instead of 'silent|s').
593
     *
594
     * @param string $optionName
595
     * @return string
596
     */
597
    public function findMatchingOption($optionName)
598
    {
599
        // Exit fast if there's an exact match
600
        if ($this->options->exists($optionName)) {
601
            return $optionName;
602
        }
603
        $existingOptionName = $this->findExistingOption($optionName);
604
        if (isset($existingOptionName)) {
605
            return $existingOptionName;
606
        }
607
        return $this->findOptionAmongAlternatives($optionName);
608
    }
609
610
    /**
611
     * @param string $optionName
612
     * @return string
613
     */
614
    protected function findOptionAmongAlternatives($optionName)
615
    {
616
        // Check the other direction: if the annotation contains @silent|s
617
        // and the options array has 'silent|s'.
618
        $checkMatching = explode('|', $optionName);
619
        if (count($checkMatching) > 1) {
620
            foreach ($checkMatching as $checkName) {
621
                if ($this->options->exists($checkName)) {
622
                    $this->options->rename($checkName, $optionName);
623
                    return $optionName;
624
                }
625
            }
626
        }
627
        return $optionName;
628
    }
629
630
    /**
631
     * @param string $optionName
632
     * @return string|null
633
     */
634
    protected function findExistingOption($optionName)
635
    {
636
        // Check to see if we can find the option name in an existing option,
637
        // e.g. if the options array has 'silent|s' => false, and the annotation
638
        // is @silent.
639
        foreach ($this->options()->getValues() as $name => $default) {
640
            if (in_array($optionName, explode('|', $name))) {
641
                return $name;
642
            }
643
        }
644
    }
645
646
    /**
647
     * Examine the parameters of the method for this command, and
648
     * build a list of commandline arguements for them.
649
     *
650
     * @return array
651
     */
652
    protected function determineAgumentClassifications()
653
    {
654
        $result = new DefaultsWithDescriptions();
655
        $params = $this->reflection->getParameters();
656
        $optionsFromParameters = $this->determineOptionsFromParameters();
657
        if (!empty($optionsFromParameters)) {
658
            array_pop($params);
659
        }
660
        foreach ($params as $param) {
661
            $this->addParameterToResult($result, $param);
662
        }
663
        return $result;
664
    }
665
666
    /**
667
     * Examine the provided parameter, and determine whether it
668
     * is a parameter that will be filled in with a positional
669
     * commandline argument.
670
     */
671
    protected function addParameterToResult($result, $param)
672
    {
673
        // Commandline arguments must be strings, so ignore any
674
        // parameter that is typehinted to any non-primative class.
675
        if ($param->getClass() != null) {
676
            return;
677
        }
678
        $result->add($param->name);
679
        if ($param->isDefaultValueAvailable()) {
680
            $defaultValue = $param->getDefaultValue();
681
            if (!$this->isAssoc($defaultValue)) {
682
                $result->setDefaultValue($param->name, $defaultValue);
683
            }
684
        } elseif ($param->isArray()) {
685
            $result->setDefaultValue($param->name, []);
686
        }
687
    }
688
689
    /**
690
     * Examine the parameters of the method for this command, and determine
691
     * the disposition of the options from them.
692
     *
693
     * @return array
694
     */
695
    protected function determineOptionsFromParameters()
696
    {
697
        $params = $this->reflection->getParameters();
698
        if (empty($params)) {
699
            return [];
700
        }
701
        $param = end($params);
702
        if (!$param->isDefaultValueAvailable()) {
703
            return [];
704
        }
705
        if (!$this->isAssoc($param->getDefaultValue())) {
706
            return [];
707
        }
708
        return $param->getDefaultValue();
709
    }
710
711
    /**
712
     * Helper; determine if an array is associative or not. An array
713
     * is not associative if its keys are numeric, and numbered sequentially
714
     * from zero. All other arrays are considered to be associative.
715
     *
716
     * @param arrau $arr The array
717
     * @return boolean
718
     */
719
    protected function isAssoc($arr)
720
    {
721
        if (!is_array($arr)) {
722
            return false;
723
        }
724
        return array_keys($arr) !== range(0, count($arr) - 1);
725
    }
726
727
    /**
728
     * Convert from a method name to the corresponding command name. A
729
     * method 'fooBar' will become 'foo:bar', and 'fooBarBazBoz' will
730
     * become 'foo:bar-baz-boz'.
731
     *
732
     * @param string $camel method name.
733
     * @return string
734
     */
735
    protected function convertName($camel)
736
    {
737
        $splitter="-";
738
        $camel=preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '$0', preg_replace('/(?!^)[[:upper:]]+/', $splitter.'$0', $camel));
739
        $camel = preg_replace("/$splitter/", ':', $camel, 1);
740
        return strtolower($camel);
741
    }
742
743
    /**
744
     * Parse the docBlock comment for this command, and set the
745
     * fields of this class with the data thereby obtained.
746
     */
747
    protected function parseDocBlock()
748
    {
749
        if (!$this->docBlockIsParsed) {
750
            // The parse function will insert data from the provided method
751
            // into this object, using our accessors.
752
            CommandDocBlockParserFactory::parse($this, $this->reflection);
753
            $this->docBlockIsParsed = true;
754
        }
755
    }
756
757
    /**
758
     * Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c',
759
     * convert the data into the last of these forms.
760
     */
761
    protected static function convertListToCommaSeparated($text)
762
    {
763
        return preg_replace('#[ \t\n\r,]+#', ',', $text);
764
    }
765
}
766