Passed
Push — master ( 52c22c...f73620 )
by Maxim
03:05 queued 11s
created

Parser::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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