Passed
Push — master ( 6216b3...524f0f )
by Maxim
04:50 queued 01:46
created

Parser::processAlias()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
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 string alias of property */
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
    public function __construct(\ReflectionProperty $reflection, array $tokens)
62
    {
63
        $this->reflection = $reflection;
64
        $this->tokens = $tokens;
65
        $this->readableFirst = (bool)preg_match('{^(rdb|readable)$}', $this->tokens[self::KEYWORD_1]);
0 ignored issues
show
Documentation Bug introduced by
The property $readableFirst was declared of type string, but (bool)preg_match('{^(rdb...okens[self::KEYWORD_1]) is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
66
        $this->validateStatements();
67
    }
68
69
    /**
70
     * Returns type declaration.
71
     * 
72
     * @return string type declaration
73
     */
74
    public function getTypeDef(): string 
75
    {
76
        return $this->tokens[self::TYPE] ?? '';
77
    }
78
79
    /**
80
     * Returns class namespace.
81
     * 
82
     * @return string class namespace
83
     */
84
    public function getNamespace(): string 
85
    {
86
        return $this->reflection->getDeclaringClass()->getNamespaceName();
87
    }
88
89
    /**
90
     * Getter for {@see Parser::$reflection}.
91
     * 
92
     * @return \ReflectionProperty property reflection
93
     */
94
    public function getReflection(): \ReflectionProperty
95
    {
96
        return $this->reflection;
97
    }
98
99
    /**
100
     * Returns conditions for setter.
101
     * 
102
     * @return string input conditions.
103
     */
104
    public function getInConditions(): string 
105
    {
106
        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
    public function getOutConditions(): string 
115
    {
116
        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
    public function getInHandlers(): string 
125
    {
126
        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
    public function getOutHandlers(): string
135
    {
136
        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
    public function getAlias(): string
145
    {
146
        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
    public function processAccessModifier(): array
155
    {
156
        $type = $this->getKeyword(self::KEYWORD_1);
157
        if ($type == 'access') {
158
            $this->accessModifiers = [
159
                'write' => $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_1]),
160
                'read' => $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_1])
161
            ];
162
            return $this->accessModifiers;
163
        }
164
        $this->accessModifiers[$type] = $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_1]);
165
        if (isset($this->tokens[self::KEYWORD_2])) {
166
            $type = $this->getKeyword(self::KEYWORD_2);
167
            $this->accessModifiers[$type] = $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_2]);
168
        }
169
        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
    private function replaceSignWithWord(string $sign): string
180
    {
181
        switch ($sign) {
182
            case self::A_PUBLIC:
183
                return 'public';
184
            case self::A_PROTECTED:
185
                return 'protected';
186
            case self::A_PRIVATE:
187
                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
    private function validateStatements(): void
199
    {
200
        if (isset($this->tokens[self::KEYWORD_2])) {
201
            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
            } elseif (preg_match('{^(wrt|writable)$}', $this->tokens[self::KEYWORD_2])) {
204
                throw new SyntaxError(
205
                    "\"writable\" statement must be the first in {$this->reflection->getDeclaringClass()->name}::\${$this->reflection->name} Axessors comment\""
206
                );
207
            }
208
        }
209
    }
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
    private function getKeyword(int $token): string
219
    {
220
        if (preg_match(sprintf('{^(%s|%s)$}', self::F_ACCESSIBLE, self::S_ACCESSIBLE), $this->tokens[$token])) {
221
            return 'access';
222
        } elseif (preg_match(sprintf('{^(%s|%s)$}', self::F_WRITABLE, self::S_WRITABLE), $this->tokens[$token])) {
223
            return 'write';
224
        } elseif (preg_match(sprintf('{^(%s|%s)$}', self::F_READABLE, self::S_READABLE), $this->tokens[$token])) {
225
            return 'read';
226
        } else {
227
            throw new InternalError('not a valid keyword token given');
228
        }
229
    }
230
}
231