Completed
Push — master ( 5b30c6...b57bf8 )
by Mehmet
03:06
created

ModelUtils::setMaxMin()   D

Complexity

Conditions 9
Paths 21

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 25
rs 4.909
cc 9
eloc 17
nc 21
nop 4
1
<?php
2
/**
3
 * ModelUtils: A simple PHP class for validating variable types, fixing, sanitising and setting default values for a
4
 * model definition encoded as an array .
5
 * *
6
 *  @TODO: A doc item can be array that has multiple values,
7
 *  implement validation and sanitization for the situations like this.
8
 *  @TODO: Detailed documentation is needed
9
 */
10
11
namespace ModelUtils;
12
13
use Crisu83\ShortId\ShortId;
14
15
class ModelUtils
16
{
17
    /**
18
     * Validate given documents
19
     *
20
     * @param array     $my_model
21
     * @param array     $my_doc
22
     * @param string    $my_key
23
     * @return array
24
     * @throws \Exception
25
     */
26
    public static function validateDoc($my_model, $my_doc, $my_key = null)
27
    {
28
        $my_keys = array_keys($my_doc);
29
        foreach ($my_keys as $key) {
30
            
31
            $my_doc_key_type = self::getType($my_doc[$key]);
32
            
33
            // Does doc has a array that does not exist in model definition .
34
            if (!isset($my_model[$key])) {
35
                $v_key = $key;
36
                if ($my_key !== null) {
37
                    $v_key = strval($my_key).".".strval($key);
38
                }
39
                throw new \Exception("Error for key '".$v_key."' that does not exist in the model");
40
            } // Is the value of the array[key] again another array? .
41
            elseif ($my_doc_key_type == "array") {
42
                $v_key = $key;
43
                if ($my_key !== null) {
44
                    $v_key = strval($my_key).".".strval($key);
45
                }
46
                // Validate this array too .
47
                $my_doc[$key] = self::validateDoc($my_model[$key], $my_doc[$key], $v_key);
48
                if (self::getType($my_doc[$key]) != "array") {
49
                    return $my_doc[$key];
50
                }
51
            } // Is the value of the array[key] have same variable type
52
              //that stated in the definition of the model array.
53
            elseif ($my_doc_key_type != $my_model[$key]['_type']) {
54
                $v_key = $key;
55
                if ($my_key !== null) {
56
                    $v_key = $my_key." . ".$key;
57
                }
58
                throw new \Exception("Error for key '".$v_key."'".", ".$my_doc_key_type.
59
                    " given but it must be ".$my_model[$key]['_type']);
60
            } else {
61
                $v_key = $key;
62
                if ($my_key !== null) {
63
                    $v_key = $my_key." . ".$key;
64
                }
65
                $my_doc[$key] = self::validateDocItem($my_doc[$key], $my_model[$key], $v_key);
66
            }
67
        }
68
        return $my_doc;
69
    }
70
71
    /**
72
     * Fit document to given Model
73
     *
74
     * @param array     $my_model
75
     * @param array     $my_doc
76
     * @return array
77
     */
78
    public static function fitDocToModel($my_model, $my_doc)
79
    {
80
        $my_keys = array_keys($my_doc);
81
        foreach ($my_keys as $key) {
82
            // If array has a key that is not presented in the model definition, unset it .
83
            if (!isset($my_model[$key])) {
84
                unset($my_doc[$key]);
85
            } // If array[$key] is again an array, recursively fit this array too .
86
            elseif (self::getType($my_doc[$key]) == "array" && !isset($my_model[$key]['_type'])) {
87
                $my_doc[$key] = self::fitDocToModel($my_model[$key], $my_doc[$key]);
88
                // If returned value is not an array, return it .
89
                if (self::getType($my_doc[$key]) != "array") {
90
                    return $my_doc[$key];
91
                }
92
            } elseif (self::getType($my_doc[$key]) == "array" && $my_model[$key]['_type'] != "array") {
93
                $my_doc[$key] = $my_model[$key]['_default'];
94
            } // If array[key] is not an array and not has same variable type that stated in the model definition .
95
            else {
96
                $my_doc[$key] = self::sanitizeDocItem($my_doc[$key], $my_model[$key]);
97
            }
98
        }
99
100
        return $my_doc;
101
    }
102
103
    /**
104
     * @param array     $my_model
105
     * @param array     $my_doc
106
     *
107
     * @return array
108
     */
109
    public static function setModelDefaults($my_model, $my_doc)
110
    {
111
        $my_keys = array_keys($my_model);
112
        $new_doc = [];
113
        foreach ($my_keys as $key) {
114
            $item_keys = array_keys($my_model[$key]);
115
            // If one of the keys of $my_model[$key] is _type this is a definition, not a defined key
116
            if (in_array("_type", $item_keys)) {
117
                // If array does not have this key, set the default value .
118
                if (!isset($my_doc[$key])) {
119
                    if (isset($my_model[$key]['_input_type'])) {
120
                        switch ($my_model[$key]['_input_type']) {
121
                            case 'uid':
122
                                    $shortid = ShortId::create();
123
                                    $new_doc[$key] = $shortid->generate();
124
                                break;
125
                            case 'date':
126
                                if ($my_model[$key]['_default'] == 'today') {
127
                                    $new_doc[$key] = date("Y-m-d");
128
                                } else {
129
                                    $new_doc[$key] = $my_model[$key]['_default'];
130
                                }
131
                                break;
132
                            case 'timestamp':
133
                                $model_default = $my_model[$key]['_default'];
134
                                $model_type = $my_model[$key]['_type'];
135
                                if (($model_default == "now") && ($model_type == "integer")) {
136
                                    $new_doc[$key] = time();
137
                                } elseif ($model_default == "now" && ($model_type == "string")) {
138
                                    $new_doc[$key] = date("Y-m-d H:i:s");
139
                                } else {
140
                                    $new_doc[$key] = $model_default;
141
                                }
142
                                break;
143
                    
144
                            default:
145
                                $new_doc[$key] = $my_model[$key]['_default'];
146
                        }
147
                    } else {
148
                        $new_doc[$key] = $my_model[$key]['_default'];
149
                    }
150
                } // If array has this key
151
                else {
152
                    // If model definition stated this key's default value is not Null
153
                    // and has a wrong variable type, fix it.
154
                    if ($my_model[$key]['_default'] !== null) {
155
                        $key_type = self::getType($my_doc[$key]);
156
                        if ($key_type != $my_model[$key]['_type'] && $key_type == "array") {
157
                            $my_doc[$key] = $my_model[$key]['_default'];
158
                        }
159
                        settype($my_doc[$key], $my_model[$key]['_type']);
160
                    }
161
                    $new_doc[$key] = $my_doc[$key];
162
                }
163
                $new_doc[$key] = self::sanitizeDocItem($new_doc[$key], $my_model[$key]);
164
            } // If one of the keys is not _type, this is a defined key, recursively get sub keys .
165
            else {
166
                if (!isset($my_doc[$key])) {
167
                    $my_doc[$key] = "";
168
                }
169
                $new_doc[$key] = self::setModelDefaults($my_model[$key], $my_doc[$key]);
170
            }
171
        }
172
        return $new_doc;
173
    }
174
175
    /**
176
     * @param mixed     $value
177
     * @param array     $my_model
178
     * @param string    $key
179
     *
180
     * @return mixed
181
     * @throws \Exception
182
     */
183
    public static function validateDocItem($value, $my_model, $key)
184
    {
185
        $type        = isset($my_model['_type']) ? $my_model['_type'] : 'string';
186
        $input_type  = isset($my_model['_input_type']) ? $my_model['_input_type'] : 'string';
187
        $format      = isset($my_model['_input_format']) ? $my_model['_input_format'] : "";
188
        $min_length  = isset($my_model['_min_length']) ? $my_model['_min_length'] : null;
189
        $max_length  = isset($my_model['_max_length']) ? $my_model['_max_length'] : null;
190
        $in_options  = isset($my_model['_in_options']) ? $my_model['_in_options'] : null;
191
192
        if (self::getType($value) != $type) {
193
            return false;
194
        }
195
        if ($input_type !== null) {
196
            self::filterValidate($input_type, $key, $value, $format);
197
        }
198
        switch ($type) {
199
            case 'integer':
200
            case 'float':
201
                if ($min_length !== null) {
202
                    if ($value<$min_length) {
203
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
204
                            "validation: Must be bigger than ".$min_length."  ");
205
                    }
206
                }
207
                if ($max_length !== null) {
208
                    if ($value>$max_length) {
209
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
210
                            "validation: Must be smallerr than ".$max_length."  ");
211
                    }
212
                }
213
                break;
214
            default:
215
                if ($max_length !== null) {
216
                    if (strlen($value)>$max_length) {
217
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the " .
218
                            "validation: It's length must be smaller than ".$max_length."  ");
219
                    }
220
                }
221
                if ($min_length !== null) {
222
                    if (strlen($value)<$min_length) {
223
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
224
                            "validation: It's length must be longer than ".$min_length."  ");
225
                    }
226
                }
227
                break;
228
        }
229
        if ($in_options !== null) {
230
            if (!in_array($value, $in_options)) {
231
                throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the validation: ".
232
                    "It's length must be one of the these values: ".implode(", ", $in_options)."  ");
233
            }
234
        }
235
236
        return $value;
237
    }
238
    
239
    private static function filterValidate($input_type, $key, $value, $format)
240
    {
241
        $filter_check = null;
242
        $validation = null;
243
        switch ($input_type) {
244
            case 'mail':
245
                $filter_check = filter_var($value, FILTER_VALIDATE_EMAIL);
246
                $validation = 'INVALID_EMAIL_ADDRESS';
247
248
                break;
249
            case 'bool':
250
                $filter_check = filter_var($value, FILTER_VALIDATE_BOOLEAN);
251
                $validation = 'INVALID_BOOLEAN_VALUE';
252
                break;
253
            case 'url':
254
                $filter_check = filter_var($value, FILTER_VALIDATE_URL);
255
                $validation = 'INVALID_URL';
256
                break;
257
            case 'ip':
258
                $filter_check = filter_var($value, FILTER_VALIDATE_IP);
259
                $validation = 'INVALID_IP_ADDRESS';
260
                break;
261
            case 'mac_address':
262
                $filter_check = filter_var($value, FILTER_VALIDATE_MAC);
263
                $validation = 'INVALID_MAC_ADDRESS';
264
                break;
265
            case 'date':
266
                $regex = "/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/";
267
                $options = array("options"=>array("regexp"=> $regex));
268
                $filter_check = filter_var($value, FILTER_VALIDATE_REGEXP, $options);
269
                $validation = 'INVALID_DATE_FORMAT';
270
                break;
271
            case 'time':
272
                $regex = "/^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])$/";
273
                $options = array("options"=>array("regexp"=> $regex));
274
                $filter_check = filter_var($value, FILTER_VALIDATE_REGEXP, $options);
275
                $validation = 'INVALID_TIME_FORMAT';
276
                break;
277
            case 'datetime':
278
                $date_part = "[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])";
279
                $time_part = "([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])";
280
                $regex = "/^".$date_part." ".$time_part."$/";
281
                $options = array("options"=>array("regexp"=> $regex));
282
                $filter_check = filter_var($value, FILTER_VALIDATE_REGEXP, $options);
283
                $validation = 'INVALID_DATETIME_FORMAT';
284
                break;
285
            case 'regex':
286
                $regex = "/^".$format."$/";
287
                $options = array("options"=>array("regexp"=> $regex));
288
                $filter_check = filter_var($value, FILTER_VALIDATE_REGEXP, $options);
289
                $validation = 'INVALID_FORMAT';
290
                break;
291
        }
292
        if ($filter_check === false) {
293
            throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
294
                "validation: ".$validation);
295
        }
296
        return $filter_check;
297
    }
298
299
    /**
300
     * @param mixed     $value
301
     * @param array     $my_model
302
     *
303
     * @return mixed
304
     */
305
    public static function sanitizeDocItem($value, $my_model)
306
    {
307
        $type = isset($my_model['_type']) ? $my_model['_type'] : 'string';
308
        $input_type = isset($my_model['_input_type']) ? $my_model['_input_type'] : null;
309
        $min_length = isset($my_model['_min_length']) ? $my_model['_min_length'] : null;
310
        $max_length = isset($my_model['_max_length']) ? $my_model['_max_length'] : null;
311
        $in_options = isset($my_model['_in_options']) ? $my_model['_in_options'] : null;
312
313
        if ($input_type !== null) {
314
            switch ($input_type) {
315
                case 'timestamp':
316
                    if ($value == 'now') {
317
                        $value = time();
318
                    }
319
                    break;
320
                default:
321
                    $value = filter_var($value, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
322
                    break;
323
            }
324
        }
325
        settype($value, $type);
326
        
327
        $value = self::setMaxMin($type, $value, $min_length, $max_length);
328
329
        if ($in_options !== null) {
330
            if (!in_array($value, $in_options)) {
331
                $value = $in_options[0]; // First value of the in_options array is assumed to be the default value .
332
            }
333
        }
334
335
        return $value;
336
    }
337
    private static function setMaxMin($type, $value, $min_length, $max_length){
338
        switch ($type) {
339
            case 'integer':
340
            case 'float':
341
                if ($min_length !== null) {
342
                    if ($value<$min_length) {
343
                        $value = $min_length;
344
                    }
345
                }
346
                if ($max_length !== null) {
347
                    if ($value>$max_length) {
348
                        $value = $max_length;
349
                    }
350
                }
351
                break;
352
            default:
353
                if ($max_length !== null) {
354
                    if (strlen($value)>$max_length) {
355
                        $value = substr($value, 0, $max_length);
356
                    }
357
                }
358
                break;
359
        }
360
        return $value;
361
    }
362
    /**
363
     * A Note:
364
     * Since the built-in php function gettype returns "double" variabe type, here is the workaround function
365
     * See http://php . net/manual/en/function . gettype . php => Possible values for the returned string are:
366
     * "double" (for historical reasons "double" is returned in case of a float, and not simply "float")
367
     *
368
     * @param mixed     $value
369
     * @return string
370
     */
371
    public static function getType($value)
372
    {
373
        return [
374
            'boolean' => 'boolean',
375
            'string' => 'string',
376
            'integer' => 'integer',
377
            'long' => 'integer',
378
            'double' => 'float',
379
            'float' => 'float',
380
            'array' => 'array',
381
            'object' => 'object',
382
            'resource' => 'resource',
383
			'null' => 'null'
384
        ][strtolower(gettype($value))];
385
    }
386
}
387