ReflectionReader::filterArguments()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

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