Completed
Push — master ( 6a6c7b...f57853 )
by Sebastian
03:30
created

RuleInterpreter::strtonum()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 7
ccs 0
cts 6
cp 0
crap 6
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'],
24
        'number' => ['Number', 'boolean'],
25
        'date' => ['Date', 'boolean'],
26
        'email' => ['Email', 'boolean'],
27
        'min' => ['Min', 'number'],
28
        'max' => ['Max', 'number'],
29
        'between' => ['Between', 'number'],
30
        'length' => ['Length', 'number'],
31
        'maxlength' => ['MaxLength', 'number'],
32
        'minlength' => ['MinLength', 'number'],
33
        'datebefore' => ['DateBefore', 'string'],
34
        'dateafter' => ['DateAfter', 'string'],
35
        'datebetween' => ['DateBetween', 'string'],
36
        'use' => ['Use', 'string']
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);
0 ignored issues
show
Bug introduced by
The call to implode() has too few arguments starting with pieces. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

83
                $words[$word++] = /** @scrutinizer ignore-call */ implode($temp);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
$temp of type array is incompatible with the type string expected by parameter $glue of implode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

83
                $words[$word++] = implode(/** @scrutinizer ignore-type */ $temp);
Loading history...
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 = '';
118
        
119
        foreach ($words as $key => $word) {
120
            if ($key === 0) {
121
                $field = $word;
122
                $array[$field] = [];
123
                continue;
124
            }
125
126
            if (isset(self::$keywords[$word])) {
127
                $actualWord = $word;
128
                $array[$field][$word] = [];
129
                continue;
130
            }
131
132
            $array[$field][$actualWord][] = $word;
133
        }
134
135
        $words = $array;
136
    }
137
138
    /**
139
     * Organize rules' array.
140
     *
141
     * @param array $words
142
     */
143
    private function parserNormalizeParam(array &$words)
144
    {
145
        $field = array_keys($words)[0];
146
        $temp = [];
147
        
148
        foreach ($words[$field] as $key => $word) {
149
            if (count($word) === 0) {
150
                $words[$field][$key] = true;
151
            }
152
153
            if (count($word) === 1) {
154
                $words[$field][$key] = $word[0];
155
            }
156
            
157
            $temp[] = [$field, $key, self::$keywords[$key], $words[$field][$key]];
158
        }
159
        
160
        $words = $temp;
161
    }
162
163
    /**
164
     * Apply types to rules parameters.
165
     *
166
     * @param array $words
167
     *
168
     * @throws \InvalidArgumentException If unknow keyword is provided.
169
     */
170
    private function parserApplyTypes(array &$words)
171
    {
172
        $rules = &self::$keywords;
173
174
        foreach ($words as $key => $word) {
175
            $rule = &$words[$key][1];
176
            $params = &$words[$key][3];
177
            
178
            if (!isset($rules[$rule])) {
179
                throw new \InvalidArgumentException('Unknow rule keyword provided');
180
            }
181
            
182
            if (is_array($params)) {
183
                $this->parserTypeCastingArray($params, $rules[$rule][1]);
184
                continue;
185
            }
186
187
            $this->parserTypeCastingOthers($params, $rules[$rule][1]);
188
        }
189
    }
190
191
    /**
192
     * Apply types when there is more than one parameter.
193
     *
194
     * @param array $params
195
     * @param string $type
196
     */
197
    private function parserTypeCastingArray(array &$params, string &$type)
198
    {
199
        foreach ($params as $key => $value) {
200
            $this->parserTypeCastingOthers($params[$key], $type);
201
        }
202
    }
203
204
    /**
205
     * Apply types when there is one parameter.
206
     *
207
     * @param mixed $param
208
     * @param string $type
209
     *
210
     * @return void
211
     */
212
    private function parserTypeCastingOthers(&$param, string &$type)
213
    {
214
        if ($type === 'number') {
215
            settype($param, $this->strtonum($param));
216
            return;
217
        }
218
219
        settype($param, $type);
220
    }
221
222
    /**
223
     * Identify correct number type.
224
     *
225
     * @param string $number
226
     *
227
     * @return string
228
     */
229
    private function strtonum(string $number): string
230
    {
231
        if (fmod((float) $number, 1.0) === 0.0) {
232
            return 'integer';
233
        }
234
235
        return 'float';
236
    }
237
}
238