Parser   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 214
Duplicated Lines 0 %

Test Coverage

Coverage 95.16%

Importance

Changes 0
Metric Value
eloc 77
dl 0
loc 214
ccs 59
cts 62
cp 0.9516
rs 10
c 0
b 0
f 0
wmc 30

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getInConditions() 0 3 2
A getNamespace() 0 3 1
A getTypeDef() 0 3 1
A getOutConditions() 0 3 2
A getInHandlers() 0 3 2
A getOutHandlers() 0 3 2
A getReflection() 0 3 1
A processAccessModifier() 0 16 3
A getKeyword() 0 10 4
A validateStatements() 0 8 4
A replaceSignWithWord() 0 11 4
A getAlias() 0 10 3
A __construct() 0 6 1
1
<?php
2
/**
3
 * This file is a part of "Axessors" library.
4
 *
5
 * @author <[email protected]>
6
 * @license GPL
7
 */
8
9
namespace NoOne4rever\Axessors;
10
11
use NoOne4rever\Axessors\Exceptions\{
12
    InternalError,
13
    SyntaxError
14
};
15
16
/**
17
 * Class Parser.
18
 *
19
 * Analyses tokens from the Axessors comment.
20
 *
21
 * @package NoOne4rever\Axessors
22
 */
23
class Parser
24
{
25
    private const ACCESS_MODIFIER_1 = 1;
26
    private const KEYWORD_1 = 2;
27
    private const TYPE = 3;
28
    private const CONDITIONS_1 = 4;
29
    private const HANDLERS_1 = 6;
30
    private const ACCESS_MODIFIER_2 = 7;
31
    private const KEYWORD_2 = 8;
32
    private const CONDITIONS_2 = 9;
33
    private const HANDLERS_2 = 11;
34
    private const ALIAS = 13;
35
36
    private const F_WRITABLE = 'writable';
37
    private const S_WRITABLE = 'wrt';
38
    private const F_ACCESSIBLE = 'accessible';
39
    private const S_ACCESSIBLE = 'axs';
40
    private const F_READABLE = 'readable';
41
    private const S_READABLE = 'rdb';
42
    private const A_PUBLIC = '+';
43
    private const A_PROTECTED = '~';
44
    private const A_PRIVATE = '-';
45
    private const K_DEFAULT = 'default';
46
47
    /** @var string[] tokens from Axessors comment */
48
    private $tokens;
49
    /** @var \ReflectionProperty property's reflection */
50
    private $reflection;
51
    /** @var string[] access modifiers for getter and setter */
52
    private $accessModifiers;
53
    /** @var bool order of statements */
54
    private $readableFirst;
55
56
    /**
57
     * Parser constructor.
58
     *
59
     * @param \ReflectionProperty $reflection property's reflection
60
     * @param array $tokens tokens from the Axessors comment
61
     */
62 5
    public function __construct(\ReflectionProperty $reflection, array $tokens)
63
    {
64 5
        $this->reflection = $reflection;
65 5
        $this->tokens = $tokens;
66 5
        $this->readableFirst = (bool)preg_match('{^(rdb|readable)$}', $this->tokens[self::KEYWORD_1]);
67 5
        $this->validateStatements();
68 4
    }
69
70
    /**
71
     * Returns type declaration.
72
     * 
73
     * @return string type declaration
74
     */
75 4
    public function getTypeDef(): string 
76
    {
77 4
        return $this->tokens[self::TYPE] ?? '';
78
    }
79
80
    /**
81
     * Returns class namespace.
82
     * 
83
     * @return string class namespace
84
     */
85 4
    public function getNamespace(): string 
86
    {
87 4
        return $this->reflection->getDeclaringClass()->getNamespaceName();
88
    }
89
90
    /**
91
     * Getter for {@see Parser::$reflection}.
92
     * 
93
     * @return \ReflectionProperty property reflection
94
     */
95 4
    public function getReflection(): \ReflectionProperty
96
    {
97 4
        return $this->reflection;
98
    }
99
100
    /**
101
     * Returns conditions for setter.
102
     * 
103
     * @return string input conditions.
104
     */
105 2
    public function getInConditions(): string 
106
    {
107 2
        return $this->tokens[$this->readableFirst ? self::CONDITIONS_2 : self::CONDITIONS_1] ?? '';
108
    }
109
110
    /**
111
     * Returns conditions for getter.
112
     * 
113
     * @return string output conditions
114
     */
115 2
    public function getOutConditions(): string 
116
    {
117 2
        return $this->tokens[$this->readableFirst ? self::CONDITIONS_1 : self::CONDITIONS_2] ?? '';
118
    }
119
120
    /**
121
     * Returns handlers for setter.
122
     * 
123
     * @return string input handlers
124
     */
125 2
    public function getInHandlers(): string 
126
    {
127 2
        return $this->tokens[$this->readableFirst ? self::HANDLERS_2 : self::HANDLERS_1] ?? '';
128
    }
129
130
    /**
131
     * Returns handlers for getter.
132
     * 
133
     * @return string output handlers
134
     */
135 2
    public function getOutHandlers(): string
136
    {
137 2
        return $this->tokens[$this->readableFirst ? self::HANDLERS_1 : self::HANDLERS_2] ?? '';
138
    }
139
    
140
    /**
141
     * Returns property's alias.
142
     *
143
     * @return string property's alias
144
     */
145 4
    public function getAlias(): string
146
    {
147 4
        if (isset($this->tokens[self::ALIAS])) {
148 2
            if ($this->tokens[self::ALIAS] === self::K_DEFAULT) {
149 2
                return '';
150
            } else {
151 2
                return $this->tokens[self::ALIAS];
152
            }
153
        } else {
154 4
            return $this->reflection->name;
155
        }
156
    }
157
158
    /**
159
     * Processes access modifiers for getter and setter.
160
     *
161
     * @return string[] access modifiers
162
     */
163 4
    public function processAccessModifier(): array
164
    {
165 4
        $type = $this->getKeyword(self::KEYWORD_1);
166 4
        if ($type == 'access') {
167 2
            $this->accessModifiers = [
168 2
                'write' => $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_1]),
169 2
                'read' => $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_1])
170
            ];
171 2
            return $this->accessModifiers;
172
        }
173 4
        $this->accessModifiers[$type] = $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_1]);
174 4
        if (isset($this->tokens[self::KEYWORD_2])) {
175 2
            $type = $this->getKeyword(self::KEYWORD_2);
176 2
            $this->accessModifiers[$type] = $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_2]);
177
        }
178 4
        return $this->accessModifiers;
179
    }
180
181
    /**
182
     * Turns short style of access modifier to the full keyword.
183
     *
184
     * @param string $sign access modifier sign
185
     * @return string access modifier
186
     * @throws InternalError if access modifier is invalid
187
     */
188 4
    private function replaceSignWithWord(string $sign): string
189
    {
190
        switch ($sign) {
191 4
            case self::A_PUBLIC:
192 4
                return 'public';
193 2
            case self::A_PROTECTED:
194 2
                return 'protected';
195 2
            case self::A_PRIVATE:
196 2
                return 'private';
197
            default:
198
                throw new InternalError('not a valid access modifier given');
199
        }
200
    }
201
202
    /**
203
     * Validates order of statements in Axessors comment.
204
     *
205
     * @throws SyntaxError if the statements go in incorrect order
206
     */
207 5
    private function validateStatements(): void
208
    {
209 5
        if (isset($this->tokens[self::KEYWORD_2])) {
210 3
            if ($this->tokens[self::KEYWORD_1] == $this->tokens[self::KEYWORD_2]) {
211
                throw new SyntaxError("the same statements in {$this->reflection->getDeclaringClass()->name}::\${$this->reflection->name} Axessors comment");
212 3
            } elseif (preg_match('{^(wrt|writable)$}', $this->tokens[self::KEYWORD_2])) {
213 1
                throw new SyntaxError(
214 1
                    "\"writable\" statement must be the first in {$this->reflection->getDeclaringClass()->name}::\${$this->reflection->name} Axessors comment\""
215
                );
216
            }
217
        }
218 4
    }
219
220
    /**
221
     * Returns normalized keyword with type of access.
222
     *
223
     * @param int $token token
224
     * @return string keyword
225
     * @throws InternalError if the token with keyword is not valid
226
     */
227 4
    private function getKeyword(int $token): string
228
    {
229 4
        if (preg_match(sprintf('{^(%s|%s)$}', self::F_ACCESSIBLE, self::S_ACCESSIBLE), $this->tokens[$token])) {
230 2
            return 'access';
231 4
        } elseif (preg_match(sprintf('{^(%s|%s)$}', self::F_WRITABLE, self::S_WRITABLE), $this->tokens[$token])) {
232 4
            return 'write';
233 2
        } elseif (preg_match(sprintf('{^(%s|%s)$}', self::F_READABLE, self::S_READABLE), $this->tokens[$token])) {
234 2
            return 'read';
235
        } else {
236
            throw new InternalError('not a valid keyword token given');
237
        }
238
    }
239
}
240