Passed
Push — master ( df670c...4732d3 )
by Maxim
03:11
created

Parser::makeHandlersList()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 2
nop 1
dl 0
loc 15
ccs 0
cts 12
cp 0
crap 6
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
     * Generates list of methods for property.
120
     *
121
     * @param array $typeTree type tree
122
     * 
123
     * @return string[] methods' names
124
     */
125
    public function processMethods(array $typeTree): array
126
    {
127
        $methods = [];
128
        $name = $this->alias ?? $this->reflection->name;
129
130
        if (isset($this->accessModifiers['read'])) {
131
            $methods[$this->accessModifiers['read']][] = 'get' . ucfirst($name);
132
        }
133
        if (isset($this->accessModifiers['write'])) {
134
            $methods[$this->accessModifiers['write']][] = 'set' . ucfirst($name);
135
        }
136
137
        foreach ($typeTree as $index => $type) {
138
            $class = is_int($index) ? $type : $index;
139
            try {
140
                class_exists($class);
141
            } catch (InternalError $error) {
142
                continue;
143
            }
144
            foreach ((new \ReflectionClass($class))->getMethods() as $method) {
145
                $isAccessible = $method->isStatic() && $method->isPublic() && !$method->isAbstract();
146
                if ($isAccessible && preg_match('{^m_(in|out)_.*?PROPERTY.*}', $method->name)) {
147
                    if (substr($method->name, 0, 5) == 'm_in_' && isset($this->accessModifiers['write'])) {
148
                        $methods[$this->accessModifiers['write']][] = str_replace('PROPERTY', ucfirst($name),
149
                            substr($method->name, 5));
150
                    } elseif (substr($method->name, 0, 6) == 'm_out_' && isset($this->accessModifiers['read'])) {
151
                        $methods[$this->accessModifiers['read']][] = str_replace('PROPERTY', ucfirst($name),
152
                            substr($method->name, 6));
153
                    }
154
                }
155
            }
156
        }
157
        return $methods;
158
    }
159
160
    /**
161
     * Processes access modifiers for getter and setter.
162
     *
163
     * @return string[] access modifiers
164
     */
165
    public function processAccessModifier(): array
166
    {
167
        $type = $this->getKeyword(self::KEYWORD_1);
168
        if ($type == 'access') {
169
            $this->accessModifiers = [
170
                'write' => $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_1]),
171
                'read' => $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_1])
172
            ];
173
            return $this->accessModifiers;
174
        }
175
        $this->accessModifiers[$type] = $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_1]);
176
        if (isset($this->tokens[self::KEYWORD_2])) {
177
            $type = $this->getKeyword(self::KEYWORD_2);
178
            $this->accessModifiers[$type] = $this->replaceSignWithWord($this->tokens[self::ACCESS_MODIFIER_2]);
179
        }
180
        return $this->accessModifiers;
181
    }
182
183
    /**
184
     * Processes property's alias.
185
     */
186
    private function processAlias(): void
187
    {
188
        $this->alias = $this->tokens[self::ALIAS] ?? $this->reflection->name;
189
    }
190
191
    /**
192
     * Turns short style of access modifier to the full keyword.
193
     *
194
     * @param string $sign access modifier sign
195
     * @return string access modifier
196
     * @throws InternalError if access modifier is invalid
197
     */
198
    private function replaceSignWithWord(string $sign): string
199
    {
200
        switch ($sign) {
201
            case self::A_PUBLIC:
202
                return 'public';
203
            case self::A_PROTECTED:
204
                return 'protected';
205
            case self::A_PRIVATE:
206
                return 'private';
207
            default:
208
                throw new InternalError('not a valid access modifier given');
209
        }
210
    }
211
212
    /**
213
     * Validates order of statements in Axessors comment.
214
     *
215
     * @throws SyntaxError if the statements go in incorrect order
216
     */
217
    private function validateStatements(): void
218
    {
219
        if (isset($this->tokens[self::KEYWORD_2])) {
220
            if ($this->tokens[self::KEYWORD_1] == $this->tokens[self::KEYWORD_2]) {
221
                throw new SyntaxError("the same statements in {$this->reflection->getDeclaringClass()->name}::\${$this->reflection->name} Axessors comment");
222
            } elseif (preg_match('{^(wrt|writable)$}', $this->tokens[self::KEYWORD_2])) {
223
                throw new SyntaxError(
224
                    "\"writable\" statement must be the first in {$this->reflection->getDeclaringClass()->name}::\${$this->reflection->name} Axessors comment\""
225
                );
226
            }
227
        }
228
    }
229
230
    /**
231
     * Returns normalized keyword with type of access.
232
     *
233
     * @param int $token token
234
     * @return string keyword
235
     * @throws InternalError if the token with keyword is not valid
236
     */
237
    private function getKeyword(int $token): string
238
    {
239
        if (preg_match(sprintf('{^(%s|%s)$}', self::F_ACCESSIBLE, self::S_ACCESSIBLE), $this->tokens[$token])) {
240
            return 'access';
241
        } elseif (preg_match(sprintf('{^(%s|%s)$}', self::F_WRITABLE, self::S_WRITABLE), $this->tokens[$token])) {
242
            return 'write';
243
        } elseif (preg_match(sprintf('{^(%s|%s)$}', self::F_READABLE, self::S_READABLE), $this->tokens[$token])) {
244
            return 'read';
245
        } else {
246
            throw new InternalError('not a valid keyword token given');
247
        }
248
    }
249
}
250