1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Minime\Annotations; |
4
|
|
|
|
5
|
|
|
use Minime\Annotations\Interfaces\ParserInterface; |
6
|
|
|
use Minime\Annotations\Types\DynamicType; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* An Annotations parser |
10
|
|
|
* |
11
|
|
|
* @package Annotations |
12
|
|
|
* @author Márcio Almada and the Minime Community |
13
|
|
|
* @license MIT |
14
|
|
|
* |
15
|
|
|
*/ |
16
|
|
|
class DynamicParser implements ParserInterface |
17
|
|
|
{ |
18
|
|
|
const TOKEN_ANNOTATION_IDENTIFIER = '@'; |
19
|
|
|
|
20
|
|
|
const TOKEN_ANNOTATION_NAME = '[a-zA-Z\_\-\\\][a-zA-Z0-9\_\-\.\\\]*'; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* The regex to extract data from a single line |
24
|
|
|
* |
25
|
|
|
* @var string |
26
|
|
|
*/ |
27
|
|
|
protected $dataPattern; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Parser constructor |
31
|
|
|
* |
32
|
|
|
*/ |
33
|
|
|
public function __construct() |
34
|
|
|
{ |
35
|
|
|
$this->dataPattern = '/(?<=\\'. self::TOKEN_ANNOTATION_IDENTIFIER .')(' |
36
|
|
|
. self::TOKEN_ANNOTATION_NAME |
37
|
|
|
.')(((?!\s\\'. self::TOKEN_ANNOTATION_IDENTIFIER .').)*)/s'; |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Parse a given docblock |
42
|
|
|
* |
43
|
|
|
* @param string $docblock |
44
|
|
|
* @return array |
45
|
|
|
*/ |
46
|
|
|
public function parse($docblock) |
47
|
|
|
{ |
48
|
|
|
$docblock = $this->getDocblockTagsSection($docblock); |
49
|
|
|
$annotations = $this->parseAnnotations($docblock); |
50
|
|
|
foreach ($annotations as &$value) { |
51
|
|
|
if (1 == count($value)) { |
52
|
|
|
$value = $value[0]; |
53
|
|
|
} |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
return $annotations; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Filters docblock tags section, removing unwanted long and short descriptions |
61
|
|
|
* |
62
|
|
|
* @param string $docblock A docblok string without delimiters |
63
|
|
|
* @return string Tag section from given docblock |
64
|
|
|
*/ |
65
|
|
|
protected function getDocblockTagsSection($docblock) |
66
|
|
|
{ |
67
|
|
|
$docblock = $this->sanitizeDocblock($docblock); |
68
|
|
|
preg_match('/^\s*\\'.self::TOKEN_ANNOTATION_IDENTIFIER.'/m', $docblock, $matches, PREG_OFFSET_CAPTURE); |
69
|
|
|
|
70
|
|
|
// return found docblock tag section or empty string |
71
|
|
|
return isset($matches[0]) ? substr($docblock, $matches[0][1]) : ''; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Filters docblock delimiters |
76
|
|
|
* |
77
|
|
|
* @param string $docblock A raw docblok string |
78
|
|
|
* @return string A docblok string without delimiters |
79
|
|
|
*/ |
80
|
|
|
protected function sanitizeDocblock($docblock) |
81
|
|
|
{ |
82
|
|
|
return preg_replace('/\s*\*\/$|^\s*\*\s{0,1}|^\/\*{1,2}/m', '', $docblock); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Creates raw [annotation => value, [...]] tree |
87
|
|
|
* |
88
|
|
|
* @param string $str |
89
|
|
|
* @return array |
90
|
|
|
*/ |
91
|
|
|
protected function parseAnnotations($str) |
92
|
|
|
{ |
93
|
|
|
$annotations = []; |
94
|
|
|
preg_match_all($this->dataPattern, $str, $found); |
95
|
|
|
foreach ($found[2] as $key => $value) { |
96
|
|
|
$annotations[ $this->sanitizeKey($found[1][$key]) ][] = $this->parseValue($value, $found[1][$key]); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
return $annotations; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Parse a single annotation value |
104
|
|
|
* |
105
|
|
|
* @param string $value |
106
|
|
|
* @param string $key |
107
|
|
|
* @return mixed |
108
|
|
|
*/ |
109
|
|
|
protected function parseValue($value, $key = null) |
110
|
|
|
{ |
111
|
|
|
return (new DynamicType)->parse(trim($value), $key); |
|
|
|
|
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Just a hook so derived parsers can transform annotation identifiers before they go to AST |
116
|
|
|
* |
117
|
|
|
* @param string $key |
118
|
|
|
* @return string |
119
|
|
|
*/ |
120
|
|
|
protected function sanitizeKey($key) |
121
|
|
|
{ |
122
|
|
|
return $key; |
123
|
|
|
} |
124
|
|
|
} |
125
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.