Completed
Push — master ( 63117e...c0048b )
by Sebastian
02:30
created

RuleInterpreter::parserExtractParams()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 12
nc 3
nop 1
dl 0
loc 20
ccs 0
cts 16
cp 0
crap 12
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Linna Filter
5
 *
6
 * @author Sebastian Rapetti <[email protected]>
7
 * @copyright (c) 2017, Sebastian Rapetti
8
 * @license http://opensource.org/licenses/MIT MIT License
9
 */
10
declare(strict_types = 1);
11
12
namespace Linna\Filter;
13
14
/**
15
 * Translate rules from phrase to array.
16
 */
17
class RuleInterpreter
18
{
19
    /**
20
     * @var array Accepted rules.
21
     */
22
    private static $keywords = [
23
        'required' => ['Required', 'boolean', 0],
24
        'number' => ['Number', 'boolean', 0],
25
        'email' => ['Email', 'boolean', 0],
26
        'min' => ['Min', 'number', 1],
27
        'max' => ['Max', 'number', 1],
28
        'between' => ['Between', 'number', 2],
29
        'length' => ['Length', 'number', 1],
30
        'maxlength' => ['MaxLength', 'number', 1],
31
        'minlength' => ['MinLength', 'number', 1],
32
        'date' => ['Date', 'string', 1],
33
        'datebefore' => ['DateBefore', 'string', 1],
34
        'dateafter' => ['DateAfter', 'string', 1],
35
        'datebetween' => ['DateBetween', 'string', 2],
36
        'use' => ['Use', 'string', 1]
37
    ];
38
    
39
    /**
40
     * @var string Phrase to be interpreted.
41
     */
42
    private $phrase;
43
44
    /**
45
     * Class contructor.
46
     *
47
     * @param string $phrase
48
     */
49
    public function __construct(string $phrase)
50
    {
51
        $this->phrase = $phrase;
52
    }
53
54
    /**
55
     * Return interpreted rules.
56
     *
57
     * @return array
58
     */
59
    public function get(): array
60
    {
61
        $words = $this->lexer($this->phrase);
62
63
        $this->parser($words);
64
        
65
        return $words;
66
    }
67
68
    /**
69
     * Lexer.
70
     *
71
     * @param string $period
72
     * @return array
73
     */
74
    private function lexer(string $period) : array
75
    {
76
        $chars = str_split(rtrim(ltrim($period)));
77
        
78
        $words = $temp = [];
79
        $word = 0;
80
81
        foreach ($chars as $char) {
82
            if (in_array(ord($char), [32, 44, 58, 59])) {
83
                $words[$word++] = implode('', $temp);
84
                $temp = [];
85
                continue;
86
            }
87
88
            $temp[] = $char;
89
        }
90
91
        $words[$word] = implode('', $temp);
92
93
        return array_values(array_filter($words, 'trim'));
94
    }
95
96
    /**
97
     * Parser.
98
     *
99
     * @param array $array
100
     */
101
    private function parser(array &$array)
102
    {
103
        $this->parserExtractParams($array);
104
        $this->parserNormalizeParam($array);
105
        $this->parserApplyTypes($array);
106
    }
107
    
108
    /**
109
     * Separate keywords from parameters.
110
     *
111
     * @param array $words
112
     */
113
    private function parserExtractParams(array &$words)
114
    {
115
        $array = [];
116
        $actualWord = '';
117
        $field = $words[0];
118
        $count = count($words);
119
        
120
        for ($i = 1; $i < $count; $i++) {
121
            $word = $words[$i];
122
            
123
            if (isset(self::$keywords[$word])) {
124
                $actualWord = $word;
125
                $array[$field][$word] = [];
126
                continue;
127
            }
128
129
            $array[$field][$actualWord][] = $word;
130
        }
131
        
132
        $words = $array;
133
    }
134
135
    /**
136
     * Organize rules' array.
137
     *
138
     * @param array $words
139
     */
140
    private function parserNormalizeParam(array &$words)
141
    {
142
        $field = array_keys($words)[0];
143
        $temp = [];
144
        
145
        foreach ($words[$field] as $key => $word) {
146
            if (count($word) === 0) {
147
                $words[$field][$key] = true;
148
            }
149
150
            if (count($word) === 1) {
151
                $words[$field][$key] = $word[0];
152
            }
153
            
154
            $temp[] = [$field, $key, self::$keywords[$key], $words[$field][$key]];
155
        }
156
        
157
        $words = $temp;
158
    }
159
160
    /**
161
     * Apply types to rules parameters.
162
     *
163
     * @param array $words
164
     *
165
     * @throws \InvalidArgumentException If unknow keyword is provided.
166
     */
167
    private function parserApplyTypes(array &$words)
168
    {
169
        $rules = &self::$keywords;
170
171
        foreach ($words as $key => $word) {
172
            $rule = &$words[$key][1];
173
            $params = &$words[$key][3];
174
            
175
            if (!isset($rules[$rule])) {
176
                throw new \InvalidArgumentException('Unknow rule keyword provided');
177
            }
178
            
179
            if (is_array($params)) {
180
                $this->parserTypeCastingArray($params, $rules[$rule][1]);
181
                continue;
182
            }
183
184
            $this->parserTypeCastingOthers($params, $rules[$rule][1]);
185
        }
186
    }
187
188
    /**
189
     * Apply types when there is more than one parameter.
190
     *
191
     * @param array $params
192
     * @param string $type
193
     */
194
    private function parserTypeCastingArray(array &$params, string &$type)
195
    {
196
        foreach ($params as $key => $value) {
197
            $this->parserTypeCastingOthers($params[$key], $type);
198
        }
199
    }
200
201
    /**
202
     * Apply types when there is one parameter.
203
     *
204
     * @param mixed $param
205
     * @param string $type
206
     *
207
     * @return void
208
     */
209
    private function parserTypeCastingOthers(&$param, string &$type)
210
    {
211
        if ($type === 'number') {
212
            settype($param, $this->strtonum($param));
213
            return;
214
        }
215
216
        settype($param, $type);
217
    }
218
219
    /**
220
     * Identify correct number type.
221
     *
222
     * @param string $number
223
     *
224
     * @return string
225
     */
226
    private function strtonum(string $number): string
227
    {
228
        if (fmod((float) $number, 1.0) === 0.0) {
229
            return 'integer';
230
        }
231
232
        return 'float';
233
    }
234
}
235