AnnotationParser   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 229
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 32
lcom 1
cbo 2
dl 0
loc 229
ccs 94
cts 94
cp 1
rs 9.6
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A setAnnotationPrefix() 0 5 1
A setAnnotationWhitelist() 0 4 1
A parse() 0 12 4
A getAnnotationsMatches() 0 14 1
A getAnnotationList() 0 12 3
A isAnnotationInWhitelist() 0 3 1
A getAnnotationValues() 0 5 1
A getParameterValues() 0 10 4
A attachParameterValues() 0 8 4
A convertValue() 0 10 2
A transformScalar() 0 5 1
A transformIfIsNumeric() 0 5 3
A transformIfIsBoolean() 0 5 3
A convertAnnotations() 0 7 2
1
<?php
2
3
/*
4
 * Copyright (c) 2011-2015, Celestino Diaz <[email protected]>
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
 * THE SOFTWARE.
23
 */
24
25
namespace Brickoo\Component\Annotation;
26
27
use Brickoo\Component\Common\Assert;
28
29
/**
30
 * AnnotationParser
31
 *
32
 * Implements an annotation parser based on the
33
 * documentation block of a class, method or property.
34
 * @author Celestino Diaz <[email protected]>
35
 */
36
class AnnotationParser {
37
38
    /** @const regular expressions templates */
39
    const REGEX_ANNOTATION = "~[^\"]%s(?<%s>[\\w]+)[^%s(]*\\(\\s*(?<%s>[^\\)\\(]*)\\s*\\)~";
40
    const REGEX_PARAMETER = "~((?<%s>\\w+)\\s*=)?\\s*(?<%s>(\"[^\"]*\")|[0-9\\.]+|true|false)~";
41
    const REGEX_VALUE = "~((?<%s>\\w+)\\s*=)?\\s*(?<%s>('[^']*')|[0-9\\.]+|true|false)~";
42
    const REGEX_ARRAY = "~^\\[\\[.+\\]\\]$~";
43
44
    /** @const regular expressions capture groups  */
45
    const REGEX_CAPTURE_ANNOTATION = "annotation";
46
    const REGEX_CAPTURE_VALUES = "values";
47
    const REGEX_CAPTURE_PARAM = "param";
48
    const REGEX_CAPTURE_VALUE = "value";
49
50
    /** @var string */
51
    private $annotationPrefix;
52
53
    /** @var array */
54
    private $annotationWhitelist;
55
56
    /**
57
     * Class constructor.
58
     * @param string $annotationPrefix
59
     */
60 1
    public function __construct($annotationPrefix = "@") {
61 1
        Assert::isString($annotationPrefix);
62 1
        $this->annotationPrefix = $annotationPrefix;
63 1
        $this->annotationWhitelist = [];
64 1
    }
65
66
    /**
67
     * Changes the annotation prefix.
68
     * @param string $annotationPrefix
69
     * @return \Brickoo\Component\Annotation\AnnotationParser
70
     */
71 1
    public function setAnnotationPrefix($annotationPrefix) {
72 1
        Assert::isString($annotationPrefix);
73 1
        $this->annotationPrefix = $annotationPrefix;
74 1
        return $this;
75
    }
76
77
    /**
78
     * Changes the annotations whitelist.
79
     * @param array $whitelist
80
     * @return \Brickoo\Component\Annotation\AnnotationParser
81
     */
82 1
    public function setAnnotationWhitelist(array $whitelist) {
83 1
        $this->annotationWhitelist = $whitelist;
84 1
        return $this;
85
    }
86
87
    /**
88
     * Parse the document comment to extract annotations.
89
     * @param string $target
90
     * @param string $targetLocation
91
     * @param string $docComment
92
     * @throws \InvalidArgumentException
93
     * @return \Brickoo\Component\Annotation\Annotation[]
94
     */
95 1
    public function parse($target, $targetLocation, $docComment) {
96 1
        Assert::isInteger($target);
97 1
        Assert::isString($targetLocation);
98 1
        Assert::isString($docComment);
99
100 1
        $annotations = null;
101 1
        if (($annotationsMatches = $this->getAnnotationsMatches($this->annotationPrefix, $docComment))
102 1
            && (!empty($annotationsMatches[self::REGEX_CAPTURE_ANNOTATION]))) {
103 1
                $annotations = $this->convertAnnotations($target, $targetLocation, $this->getAnnotationList($annotationsMatches));
104 1
        }
105 1
        return $annotations ?: [];
106
    }
107
108
    /**
109
     * Returns the matches containing annotations.
110
     * @param string $annotationPrefix
111
     * @param string $docComment
112
     * @return array
113
     */
114 1
    private function getAnnotationsMatches($annotationPrefix, $docComment) {
115 1
        $matches = [];
116 1
        preg_match_all(
117 1
            sprintf(self::REGEX_ANNOTATION,
118 1
                preg_quote($annotationPrefix, "~"),
119 1
                self::REGEX_CAPTURE_ANNOTATION,
120 1
                preg_quote($annotationPrefix, "~"),
121
                self::REGEX_CAPTURE_VALUES
122 1
            ),
123 1
            $docComment,
124
            $matches
125 1
        );
126 1
        return $matches;
127
    }
128
129
    /**
130
     * Returns a list containing the annotation name and values.
131
     * @param array $annotationsMatches
132
     * @return array
133
     */
134 1
    private function getAnnotationList(array $annotationsMatches) {
135 1
        $annotationList = [];
136 1
        foreach ($annotationsMatches[self::REGEX_CAPTURE_ANNOTATION] as $currentIndex => $annotation) {
137 1
            if ($this->isAnnotationInWhitelist($annotation)) {
138 1
                $annotationList[] = [
139 1
                    "name" => $annotation,
140 1
                    "values" => $this->getAnnotationValues($currentIndex, $annotationsMatches)
141 1
                ];
142 1
            }
143 1
        }
144 1
        return $annotationList;
145
    }
146
147
    /**
148
     * Checks if the annotation is in the whitelist.
149
     * @param string $annotation
150
     * @return boolean
151
     */
152 1
    private function isAnnotationInWhitelist($annotation) {
153 1
        return in_array($annotation, $this->annotationWhitelist);
154
    }
155
156
    /**
157
     * Returns the annotations values.
158
     * @param string $annotationIndex
159
     * @param array $annotationsMatches
160
     * @return array
161
     */
162 1
    private function getAnnotationValues($annotationIndex, array $annotationsMatches) {
163 1
        $valuesString = $annotationsMatches[self::REGEX_CAPTURE_VALUES][$annotationIndex];
164 1
        $valuesRegex = sprintf(self::REGEX_PARAMETER, self::REGEX_CAPTURE_PARAM, self::REGEX_CAPTURE_VALUE);
165 1
        return $this->getParameterValues($valuesString, $valuesRegex);
166
    }
167
168
    /**
169
     * Returns the  parameters values pairs.
170
     * @param string $valuesString
171
     * @param string $valuesRegex
172
     * @return array
173
     */
174 1
    private function getParameterValues($valuesString, $valuesRegex) {
175 1
        $values = [];
176 1
        $parameterValues = [];
177 1
        if ((!empty($valuesString))
178 1
            && preg_match_all($valuesRegex, $valuesString, $values) !== false
179 1
            && $values !== null) {
180 1
                $this->attachParameterValues($parameterValues, $values);
181 1
        }
182 1
        return $parameterValues;
183
    }
184
185
    /**
186
     * Attach the extracted parameters values.
187
     * @param array &$parameterValues
188
     * @param array $values
189
     * @return void
190
     */
191 1
    private function attachParameterValues(&$parameterValues, array $values) {
192 1
        if (isset($values[self::REGEX_CAPTURE_PARAM])) {
193 1
            foreach ($values[self::REGEX_CAPTURE_PARAM] as $currentIndex => $param) {
194 1
                $param = $param ?: $currentIndex;
195 1
                $parameterValues[$param] = $this->convertValue($values[self::REGEX_CAPTURE_VALUE][$currentIndex]);
196 1
            }
197 1
        }
198 1
    }
199
200
    /**
201
     * Converts the value to appropriate type.
202
     * @param string $value
203
     * @return mixed
204
     */
205 1
    private function convertValue($value) {
206 1
        $value = trim($value, "\"'");
207 1
        if (preg_match(self::REGEX_ARRAY, $value) == 0) {
208 1
            return $this->transformScalar($value);
209
        }
210
211 1
        $valuesString = trim($value, "[]");
212 1
        $valuesRegex = sprintf(self::REGEX_VALUE, self::REGEX_CAPTURE_PARAM, self::REGEX_CAPTURE_VALUE);
213 1
        return $this->getParameterValues($valuesString, $valuesRegex);
214
    }
215
216
    /**
217
     * Transforms the string to appropriate scalar.
218
     * @param string $value
219
     * @return string|float|integer|boolean transformed value
220
     */
221 1
    private function transformScalar($value) {
222 1
        $this->transformIfIsNumeric($value);
223 1
        $this->transformIfIsBoolean($value);
224 1
        return $value;
225
    }
226
227
    /**
228
     * Transform value if is numeric.
229
     * @param string $value
230
     * @return void
231
     */
232 1
    private function transformIfIsNumeric(&$value) {
233 1
        if (is_numeric($value)) {
234 1
            $value = strpos($value, ".") ? floatval($value) : intval($value);
235 1
        }
236 1
    }
237
238
    /**
239
     * Transform value if is boolean.
240
     * @param string $value
241
     * @return void
242
     */
243 1
    private function transformIfIsBoolean(&$value) {
244 1
        if ($value === "true" || $value === "false") {
245 1
            $value = $value === "true";
246 1
        }
247 1
    }
248
249
    /**
250
     * Converts annotationList into an array of annotation objects.
251
     * @param integer $target
252
     * @param string $targetLocation
253
     * @param array $annotationList
254
     * @return \Brickoo\Component\Annotation\Annotation[]
255
     */
256 1
    private function convertAnnotations($target, $targetLocation, array $annotationList) {
257 1
        $annotations = [];
258 1
        foreach ($annotationList as $annotation) {
259 1
            $annotations[] = new Annotation($target, $targetLocation, $annotation["name"], $annotation["values"]);
260 1
        }
261 1
        return $annotations;
262
    }
263
264
}
265