Issues (24)

src/Parsers/Annotations/Reader.php (9 issues)

1
<?php
2
3
namespace BultonFr\Annotation\Parsers\Annotations;
4
5
use Generator;
6
7
/**
8
 * Parse a docblock to find annotation, Instanciate Info object and sent all
9
 * info about the annotation to him.
10
 *
11
 * @package BultonFr\Annotation
12
 */
13
class Reader
14
{
15
    /**
16
     * @const REGEX_START_ANNOT The regex used to find the annotation's start
17
     *
18
     * @see README.md for code format
19
     */
20
    const REGEX_START_ANNOT = '/^\*( ?)@(\w+)(.*)/m';
21
22
    /**
23
     * @const REGEX_CUT_ANNOT The regex used to cut attributes into the
24
     * annotation value.
25
     *
26
     * @see README.md for code format
27
     */
28
    const REGEX_CUT_ANNOT = '/(\w+)=(.*)/m';
29
    
30
    /**
31
     * The list of Annotations\Info objects.
32
     * The format is an array, where keys are the annotations name, and values
33
     * are an numeric array which contains all Info object for this
34
     * annotation name.
35
     *
36
     * @var Info[<int>][<string>]
0 ignored issues
show
Documentation Bug introduced by
The doc comment Info[<int>][<string>] at position 1 could not be parsed: Expected ']' at position 1, but found '['.
Loading history...
37
     */
38
    protected $annotationList = [];
39
    
40
    /**
41
     * The list of all annotation to ignore.
42
     *
43
     * @var string[]
44
     */
45
    protected static $ignoredAnnotations = [
46
        'api',
47
        'author',
48
        'category',
49
        'copyright',
50
        'deprecated',
51
        'example',
52
        'filesource',
53
        'global',
54
        'ignore',
55
        'internal',
56
        'license',
57
        'link',
58
        'method',
59
        'package',
60
        'param',
61
        'property',
62
        'property-read',
63
        'property-write',
64
        'return',
65
        'see',
66
        'since',
67
        'source',
68
        'subpackage',
69
        'throws',
70
        'todo',
71
        'uses',
72
        'used-by',
73
        'var',
74
        'version'
75
    ];
76
    
77
    /**
78
     * Get the list of Annotations\Info objects.
79
     * The format is an array, where keys are the annotations name, and values
80
     * are an numeric array which contains all Info object for this
81
     * annotation name.
82
     *
83
     * @return array
84
     */
85
    public function getAnnotationList(): array
86
    {
87 1
        return $this->annotationList;
88
    }
89
90
    /**
91
     * Get the list of all annotation to ignore.
92
     *
93
     * @return array
94
     */
95
    public static function getIgnoredAnnotations(): array
96
    {
97 1
        return self::$ignoredAnnotations;
98
    }
99
    
100
    /**
101
     * Add a new annotation to ignore
102
     *
103
     * @param string $annotationName The annotation's name to ignore
104
     *  It's the part after the @.
105
     *
106
     * @return void
107
     */
108
    public static function addIgnoredAnnotation(string $annotationName)
109
    {
110 1
        static::$ignoredAnnotations[] = $annotationName;
111 1
    }
112
    
113
    /**
114
     * Find all annotations and parse them.
115
     *
116
     * @param string $docComment The docblock to parse
117
     *
118
     * @return void
119
     */
120
    public function parse(string $docComment)
121
    {
122 1
        $annotationFind = $this->findAnnotations($docComment);
123
        
124 1
        foreach ($annotationFind as $annotationObj) {
125 1
            $this->parseAnnotation($annotationObj);
0 ignored issues
show
It seems like $annotationObj can also be of type null; however, parameter $annotationObj of BultonFr\Annotation\Pars...ader::parseAnnotation() does only seem to accept BultonFr\Annotation\Parsers\Annotations\Info, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

125
            $this->parseAnnotation(/** @scrutinizer ignore-type */ $annotationObj);
Loading history...
126
        }
127 1
    }
128
    
129
    /**
130
     * Find all annotations into $docComment
131
     *
132
     * @param string $docComment The docblock to parse
133
     *
134
     * @return \Generator
135
     */
136 1
    protected function findAnnotations(string $docComment): Generator
137
    {
138 1
        $lines        = explode("\n", $docComment);
139 1
        $inAnnotation = false;
140 1
        $annotInfo    = null;
141
        
142 1
        foreach ($lines as $line) {
143 1
            $line = trim($line);
144
            
145
            //Docblock's end
146 1
            if ($line === '*/') {
147 1
                break;
148
            }
149
            
150 1
            $annotMatch   = [];
151 1
            $isAnnotStart = preg_match(self::REGEX_START_ANNOT, $line, $annotMatch);
152
            
153 1
            if ($inAnnotation === false && $isAnnotStart !== 1) {
154
                //Not complete an annotation, and not new annotation to read.
155 1
                continue;
156 1
            } elseif ($inAnnotation === false && $isAnnotStart == 1) {
157
                //The first annotation to read :)
158
159 1
                $inAnnotation = true;
160 1
                $annotInfo    = $this->newAnnotation($annotMatch);
161 1
            } elseif ($inAnnotation === true && $isAnnotStart !== 1) {
162
                //The next of a multi-line annotation
163
164 1
                $parsedLine = trim($line);
165 1
                if ($parsedLine[0] === '*') {
166 1
                    $parsedLine = substr($parsedLine, 1);
167
                }
168
169 1
                $annotInfo->concatValueStr($parsedLine);
170 1
            } elseif ($inAnnotation === true && $isAnnotStart === 1) {
171
                //A new annotation to read.
172
173 1
                yield $annotInfo;
174 1
                $annotInfo = $this->newAnnotation($annotMatch);
175
            }
176
        }
177
        
178
        //Send previously readed annotation.
179
        //(only send when a new annotation is find into the loop).
180 1
        if ($annotInfo !== null) {
181 1
            yield $annotInfo;
182
        }
183 1
    }
184
    
185
    /**
186
     * Instanciate a new Info object
187
     *
188
     * @param array $annotMatch Info extracted by preg_match
189
     *
190
     * @return Info
191
     */
192
    protected function newAnnotation(array $annotMatch): Info
193
    {
194 1
        return new Info($annotMatch[2], $annotMatch[3]);
195
    }
196
    
197
    /**
198
     * Parse the value of an annotation value and add it to the property
199
     * $annotationList
200
     *
201
     * @param Info $annotationObj
202
     *
203
     * @return void
204
     */
205
    protected function parseAnnotation(Info $annotationObj)
206
    {
207 1
        $annotName = $annotationObj->getName();
208
        
209 1
        if (in_array($annotName, static::$ignoredAnnotations)) {
210 1
            return;
211
        }
212
        
213 1
        if (array_key_exists($annotName, $this->annotationList) === false) {
214 1
            $this->annotationList[$annotName] = [];
215
        }
216
        
217 1
        $this->parseValue($annotationObj);
218 1
        $this->annotationList[$annotName][] = $annotationObj;
219 1
    }
220
    
221
    /**
222
     * Parse the value of an annotation
223
     *
224
     * @param Info $annotationObj
225
     *
226
     * @return void
227
     */
228
    protected function parseValue(Info $annotationObj)
229
    {
230 1
        if ($annotationObj->getValueStr()[0] === '(') {
231 1
            $this->parseValueObject($annotationObj);
232
        } else {
233 1
            $annotationObj->addValue(
234 1
                null,
235 1
                $this->parseValueData($annotationObj->getValueStr())
0 ignored issues
show
Are you sure the usage of $this->parseValueData($a...tionObj->getValueStr()) targeting BultonFr\Annotation\Pars...eader::parseValueData() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
236
            );
237
        }
238 1
    }
239
    
240
    /**
241
     * Parse an annotation value when it's a format (attribute=value, ...)
242
     *
243
     * @param Info $annotationObj
244
     *
245
     * @return void
246
     */
247
    protected function parseValueObject(Info $annotationObj)
248
    {
249
        //Remove parentheses
250 1
        $annotValueStr  = substr($annotationObj->getValueStr(), 1, -1);
251 1
        $annotValueList = explode(',', $annotValueStr);
252
        
253 1
        $prevAnnotValue = '';
254 1
        $valueName      = '';
255 1
        $valueData      = null;
256
        
257 1
        foreach ($annotValueList as $annotValue) {
258 1
            $annotValue = trim($annotValue);
259 1
            $matches    = [];
260
            
261
            //Cut the valueStr on the comma to obtain each attribute/value.
262 1
            $cutAnnot = preg_match(self::REGEX_CUT_ANNOT, $annotValue, $matches);
263
            
264 1
            if ($prevAnnotValue !== '') {
265
                //If the real value has been cuted before because contain comma
266 1
                $valueData = $prevAnnotValue.','.$annotValue;
267 1
            } elseif ($cutAnnot !== 1) {
268
                //No value has been found by preg_match
269 1
                continue;
270
            } else {
271
                //A new value has been found
272 1
                $valueName = $matches[1];
273 1
                $valueData = $matches[2];
274
            }
275
            
276
            //Check if we have all values (sometimes value can contain a comma)
277 1
            $lastPos = strlen($valueData) - 1;
278
            if (
279 1
                ($valueData[0] === '"' && $valueData[$lastPos] !== '"') ||
280 1
                ($valueData[0] === '\'' && $valueData[$lastPos] !== '\'')
281
            ) {
282 1
                $prevAnnotValue = $valueData;
283 1
                continue;
284
            }
285
            
286 1
            $parsedData = $this->parseValueData($valueData);
0 ignored issues
show
Are you sure the assignment to $parsedData is correct as $this->parseValueData($valueData) targeting BultonFr\Annotation\Pars...eader::parseValueData() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
287
            
288 1
            $annotationObj->addValue($valueName, $parsedData);
289 1
            $prevAnnotValue = '';
290
        }
291 1
    }
292
    
293
    /**
294
     * Remove quote (double and simple) if exist
295
     * Parse the value to obtain the correct php type for the value
296
     *
297
     * @param string $valueData The value to transform
298
     *
299
     * @return void
300
     */
301
    protected function parseValueData(string $valueData)
302
    {
303 1
        if ($valueData[0] === '"' || $valueData[0] === '\'') {
304 1
            return substr($valueData, 1, -1); //String
0 ignored issues
show
Bug Best Practice introduced by
The expression return substr($valueData, 1, -1) returns the type string which is incompatible with the documented return type void.
Loading history...
305
        }
306
        
307 1
        $valueData = strtolower($valueData);
308
309 1
        if ($valueData === 'null') {
310 1
            return null;
311 1
        } elseif ($valueData === 'true') {
312 1
            return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type void.
Loading history...
313 1
        } elseif ($valueData === 'false') {
314 1
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type void.
Loading history...
315 1
        } elseif (strpos($valueData, '.') !== false) {
316 1
            return (float) $valueData;
0 ignored issues
show
Bug Best Practice introduced by
The expression return (double)$valueData returns the type double which is incompatible with the documented return type void.
Loading history...
317
        } else {
318 1
            return (int) $valueData;
0 ignored issues
show
Bug Best Practice introduced by
The expression return (int)$valueData returns the type integer which is incompatible with the documented return type void.
Loading history...
319
        }
320
    }
321
}
322