Completed
Push — master ( d72465...43d9d4 )
by Igor
02:07
created

Reader::parseLine()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 6
nc 1
nop 1
crap 2
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Leaditin\Annotations;
6
7
use Leaditin\Annotations\Exception\ReaderException;
8
9
/**
10
 * Class Reader
11
 *
12
 * @package Leaditin\Annotations
13
 * @author Igor Vuckovic <[email protected]>
14
 */
15
class Reader
16
{
17
    /** @var \ReflectionClass */
18
    protected $reflectionClass;
19
20
    /** @var Tokenizer */
21
    protected $tokenizer;
22
23
    /**
24
     * @param string $class
25
     * @return Reflection
26
     * @throws ReaderException
27
     */
28 7
    public function parse(string $class) : Reflection
29
    {
30
        try {
31 7
            $this->reflectionClass = new \ReflectionClass($class);
32 6
            $this->tokenizer = new Tokenizer($this->reflectionClass);
33
34 6
            return new Reflection(
35 6
                $this->readAnnotationsFromClass(),
36 6
                $this->readAnnotationsFromMethods(),
37 6
                $this->readAnnotationsFromProperties()
38
            );
39 1
        } catch (\ReflectionException $ex) {
40 1
            throw ReaderException::unableToReadClass($class, $ex);
41
        }
42
    }
43
44
    /**
45
     * @return Collection
46
     */
47 6
    protected function readAnnotationsFromClass() : Collection
48
    {
49 6
        $collection = $this->parseComment($this->reflectionClass->getDocComment());
50
51 6
        return new Collection(...$collection);
52
    }
53
54
    /**
55
     * @return array|Collection[]
56
     */
57 6 View Code Duplication
    protected function readAnnotationsFromMethods() : array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
58
    {
59 6
        $annotationsFromMethods = [];
60 6
        foreach ($this->reflectionClass->getMethods() as $method) {
61 6
            $methodCollection = $this->parseComment($method->getDocComment());
62 6
            if (empty($methodCollection)) {
63 6
                continue;
64
            }
65
66 6
            $annotationsFromMethods[$method->name] = new Collection(...$methodCollection);
67
        }
68
69 6
        return $annotationsFromMethods;
70
    }
71
72
    /**
73
     * @return array|Collection[]
74
     */
75 6 View Code Duplication
    protected function readAnnotationsFromProperties() : array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
76
    {
77 6
        $annotationsFromProperties = [];
78 6
        foreach ($this->reflectionClass->getProperties() as $property) {
79 6
            $propertyCollection = $this->parseComment($property->getDocComment());
80 6
            if (empty($propertyCollection)) {
81 6
                continue;
82
            }
83
84 6
            $annotationsFromProperties[$property->name] = new Collection(...$propertyCollection);
85
        }
86
87 6
        return $annotationsFromProperties;
88
    }
89
90
    /**
91
     * @param string|bool $comment
92
     * @return array|Annotation[]
93
     */
94 6
    protected function parseComment($comment = false) : array
95
    {
96 6
        $annotations = [];
97
98 6
        if ($comment === false) {
99 6
            return $annotations;
100
        }
101
102 6
        preg_match_all('/@([^*]+)/', $comment, $matches);
103 6
        foreach ($matches[1] as $match) {
104 6
            $annotations[] = $this->parseLine($match);
105
        }
106
107 6
        return $annotations;
108
    }
109
110
    /**
111
     * @param string $line
112
     * @return Annotation
113
     */
114 6
    protected function parseLine(string $line) : Annotation
115
    {
116 6
        preg_match('/(\w+)[\s|\(]*([^\)]*)/', $line, $matches);
117 6
        $name = $matches[1];
118 6
        $arguments = $this->parseArgumentsFromLine($matches[2] ?: '');
119 6
        $this->filterArguments($name, $arguments);
120
121 6
        return new Annotation($name, $arguments);
122
    }
123
124
    /**
125
     * @param string $line
126
     * @return array
127
     */
128 6
    protected function parseArgumentsFromLine(string $line) : array
129
    {
130 6
        $line = trim($line);
131 6
        $arguments = [];
132
133 6
        if ($line === '') {
134 6
            return $arguments;
135
        }
136
137 6
        $data = $this->exportArgumentsFromLine($line);
138 6
        foreach ($data as $index => $property) {
139 6
            $properties = explode('=', $property, 2);
140 6
            if (count($properties) === 1) {
141 6
                $arguments[$index] = $this->filterValue($property);
142
            } else {
143 6
                $arguments[$this->filterName($properties[0])] = $this->filterValue($properties[1]);
144
            }
145
        }
146
147 6
        return $arguments;
148
    }
149
150
    /**
151
     * @param string $line
152
     * @return array
153
     */
154 6
    protected function exportArgumentsFromLine(string $line) : array
155
    {
156 6
        if (false !== strpos($line, '|')) {
157 6
            return explode('|', $line);
158
        }
159
160 6
        return explode(',', $line);
161
    }
162
163
    /**
164
     * @param string $name
165
     * @return string
166
     */
167
    protected function filterName(string $name) : string
168
    {
169 6
        return preg_replace_callback('/^"([^"]*)"$|^\'([^\']*)\'$/', function ($matches) {
170 6
            return $matches[2] ?? $matches[1];
171 6
        }, trim($name));
172
    }
173
174
    /**
175
     * @param string $value
176
     * @return int|string|bool|float|array
177
     */
178 6
    protected function filterValue(string $value)
179
    {
180 6
        $value = $this->filterName($value);
181 6
        $data = json_decode(str_replace('\'', '"', $value), true);
182
183 6
        return $data ?? $value;
184
    }
185
186
    /**
187
     * @param string $name
188
     * @param array $arguments
189
     */
190 6
    protected function filterArguments(string $name, array &$arguments)
191
    {
192
        $allowed = [
193 6
            'var', 'param', 'property', 'method', 'return', 'throws',
194
        ];
195
196 6
        if (!in_array($name, $allowed, false)) {
197 6
            return;
198
        }
199
200 6
        array_walk($arguments, function (&$val) {
201 6
            $val = preg_replace('/(\$\S+)/', '', $val);
202 6
            $val = trim($val);
203 6
            $val = $this->tokenizer->getFullyQualifiedClassName($val);
204 6
        });
205
    }
206
}
207