Completed
Push — master ( 6b8876...a73bf0 )
by Maxim
04:13 queued 01:11
created

Parser::validateStatements()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.0466

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 2
nop 0
dl 0
loc 8
ccs 6
cts 7
cp 0.8571
crap 4.0466
rs 9.2
c 0
b 0
f 0
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
46
    /** @var string[] tokens from Axessors comment */
47
    private $tokens;
48
    /** @var \ReflectionProperty property's reflection */
49
    private $reflection;
50
    /** @var string[] access modifiers for getter and setter */
51
    private $accessModifiers;
52
    /** @var bool order of statements */
53
    private $readableFirst;
54
55
    /**
56
     * Parser constructor.
57
     *
58
     * @param \ReflectionProperty $reflection property's reflection
59
     * @param array $tokens tokens from the Axessors comment
60
     */
61 4
    public function __construct(\ReflectionProperty $reflection, array $tokens)
62
    {
63 4
        $this->reflection = $reflection;
64 4
        $this->tokens = $tokens;
65 4
        $this->readableFirst = (bool)preg_match('{^(rdb|readable)$}', $this->tokens[self::KEYWORD_1]);
66 4
        $this->validateStatements();
67 3
    }
68
69
    /**
70
     * Returns type declaration.
71
     * 
72
     * @return string type declaration
73
     */
74 3
    public function getTypeDef(): string 
75
    {
76 3
        return $this->tokens[self::TYPE] ?? '';
77
    }
78
79
    /**
80
     * Returns class namespace.
81
     * 
82
     * @return string class namespace
83
     */
84 3
    public function getNamespace(): string 
85
    {
86 3
        return $this->reflection->getDeclaringClass()->getNamespaceName();
87
    }
88
89
    /**
90
     * Getter for {@see Parser::$reflection}.
91
     * 
92
     * @return \ReflectionProperty property reflection
93
     */
94 3
    public function getReflection(): \ReflectionProperty
95
    {
96 3
        return $this->reflection;
97
    }
98
99
    /**
100
     * Returns conditions for setter.
101
     * 
102
     * @return string input conditions.
103
     */
104 1
    public function getInConditions(): string 
105
    {
106 1
        return $this->tokens[$this->readableFirst ? self::CONDITIONS_2 : self::CONDITIONS_1] ?? '';
107
    }
108
109
    /**
110
     * Returns conditions for getter.
111
     * 
112
     * @return string output conditions
113
     */
114 1
    public function getOutConditions(): string 
115
    {
116 1
        return $this->tokens[$this->readableFirst ? self::CONDITIONS_1 : self::CONDITIONS_2] ?? '';
117
    }
118
119
    /**
120
     * Returns handlers for setter.
121
     * 
122
     * @return string input handlers
123
     */
124 1
    public function getInHandlers(): string 
125
    {
126 1
        return $this->tokens[$this->readableFirst ? self::HANDLERS_2 : self::HANDLERS_1] ?? '';
127
    }
128
129
    /**
130
     * Returns handlers for getter.
131
     * 
132
     * @return string output handlers
133
     */
134 1
    public function getOutHandlers(): string
135
    {
136 1
        return $this->tokens[$this->readableFirst ? self::HANDLERS_1 : self::HANDLERS_2] ?? '';
137
    }
138
    
139
    /**
140
     * Returns property's alias.
141
     *
142
     * @return string property's alias
143
     */
144 3
    public function getAlias(): string
145
    {
146 3
        return $this->tokens[self::ALIAS] ?? $this->reflection->name;
147
    }
148
149
    /**
150
     * Processes access modifiers for getter and setter.
151
     *
152
     * @return string[] access modifiers
153
     */
154 3
    public function processAccessModifier(): array
155
    {
156 3
        $type = $this->getKeyword(self::KEYWORD_1);
157 3
        if ($type == 'access') {
158 1
            $this->accessModifiers = [
159 1
                'write' => $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_1]),
160 1
                'read' => $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_1])
161
            ];
162 1
            return $this->accessModifiers;
163
        }
164 3
        $this->accessModifiers[$type] = $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_1]);
165 3
        if (isset($this->tokens[self::KEYWORD_2])) {
166 1
            $type = $this->getKeyword(self::KEYWORD_2);
167 1
            $this->accessModifiers[$type] = $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_2]);
168
        }
169 3
        return $this->accessModifiers;
170
    }
171
172
    /**
173
     * Turns short style of access modifier to the full keyword.
174
     *
175
     * @param string $sign access modifier sign
176
     * @return string access modifier
177
     * @throws InternalError if access modifier is invalid
178
     */
179 3
    private function replaceSignWithWord(string $sign): string
180
    {
181
        switch ($sign) {
182 3
            case self::A_PUBLIC:
183 3
                return 'public';
184 1
            case self::A_PROTECTED:
185 1
                return 'protected';
186 1
            case self::A_PRIVATE:
187 1
                return 'private';
188
            default:
189
                throw new InternalError('not a valid access modifier given');
190
        }
191
    }
192
193
    /**
194
     * Validates order of statements in Axessors comment.
195
     *
196
     * @throws SyntaxError if the statements go in incorrect order
197
     */
198 4
    private function validateStatements(): void
199
    {
200 4
        if (isset($this->tokens[self::KEYWORD_2])) {
201 2
            if ($this->tokens[self::KEYWORD_1] == $this->tokens[self::KEYWORD_2]) {
202
                throw new SyntaxError("the same statements in {$this->reflection->getDeclaringClass()->name}::\${$this->reflection->name} Axessors comment");
203 2
            } elseif (preg_match('{^(wrt|writable)$}', $this->tokens[self::KEYWORD_2])) {
204 1
                throw new SyntaxError(
205 1
                    "\"writable\" statement must be the first in {$this->reflection->getDeclaringClass()->name}::\${$this->reflection->name} Axessors comment\""
206
                );
207
            }
208
        }
209 3
    }
210
211
    /**
212
     * Returns normalized keyword with type of access.
213
     *
214
     * @param int $token token
215
     * @return string keyword
216
     * @throws InternalError if the token with keyword is not valid
217
     */
218 3
    private function getKeyword(int $token): string
219
    {
220 3
        if (preg_match(sprintf('{^(%s|%s)$}', self::F_ACCESSIBLE, self::S_ACCESSIBLE), $this->tokens[$token])) {
221 1
            return 'access';
222 3
        } elseif (preg_match(sprintf('{^(%s|%s)$}', self::F_WRITABLE, self::S_WRITABLE), $this->tokens[$token])) {
223 3
            return 'write';
224 1
        } elseif (preg_match(sprintf('{^(%s|%s)$}', self::F_READABLE, self::S_READABLE), $this->tokens[$token])) {
225 1
            return 'read';
226
        } else {
227
            throw new InternalError('not a valid keyword token given');
228
        }
229
    }
230
}
231