Completed
Push — master ( 8fb73b...c73d67 )
by Mehmet
03:10
created

ModelUtils::fit_doc_to_model()   C

Complexity

Conditions 8
Paths 6

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 2 Features 2
Metric Value
c 6
b 2
f 2
dl 0
loc 26
rs 5.3846
cc 8
eloc 14
nc 6
nop 2
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
            // Does doc has a array that does not exist in model definition .
31
            if (!isset($my_model[$key])) {
32
                if ($my_key !== null) {
33
                    $my_key = strval($my_key)." . ".strval($key);
34
                } else {
35
                    $my_key = $key;
36
                }
37
                throw new \Exception("Error for key '".$my_key."' that does not exist in the model");
38
            } // Is the value of the array[key] again another array? .
39
            elseif (ModelUtils::getType($my_doc[$key]) == "array") {
40
                if ($my_key !== null) {
41
                    $my_key = strval($my_key)." . ".strval($key);
42
                } else {
43
                    $my_key = $key;
44
                }
45
                // Validate this array too .
46
                $my_doc[$key] = ModelUtils::validateDoc($my_model[$key], $my_doc[$key], $my_key);
47
                if (ModelUtils::getType($my_doc[$key]) != "array") {
48
                    return $my_doc[$key];
49
                }
50
            } // Is the value of the array[key] have same variable type
51
              //that stated in the definition of the model array.
52
            elseif (ModelUtils::getType($my_doc[$key]) != $my_model[$key]['_type']) {
53
                if ($my_key !== null) {
54
                    $my_key = $my_key." . ".$key;
55
                } else {
56
                    $my_key = $key;
57
                }
58
                throw new \Exception("Error for key '".$my_key."'".", ".ModelUtils::getType($my_doc[$key]).
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] = ModelUtils::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 (ModelUtils::getType($my_doc[$key]) == "array" && !isset($my_model[$key]['_type'])) {
87
                $my_doc[$key] = ModelUtils::fitDocToModel($my_model[$key], $my_doc[$key]);
88
                // If returned value is not an array, return it .
89
                if (ModelUtils::getType($my_doc[$key]) != "array") {
90
                    return $my_doc[$key];
91
                }
92
            } elseif (ModelUtils::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] = ModelUtils::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 settingModelDefaults($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 = ModelUtils::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] = ModelUtils::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] = ModelUtils::settingModelDefaults($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 (ModelUtils::getType($value) != $type) {
193
            return false;
194
        }
195
        if ($input_type !== null) {
196
            switch ($input_type) {
197
                case 'mail':
198
                    $filter_check = filter_var($value, FILTER_VALIDATE_EMAIL);
199
                    if ($filter_check === false) {
200
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
201
                            "validation: INVALID_EMAIL_ADDRESS ");
202
                    }
203
                    break;
204
                case 'bool':
205
                    $filter_check = filter_var($value, FILTER_VALIDATE_BOOLEAN);
206
                    if ($filter_check === false) {
207
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
208
                            "validation: INVALID_BOOLEAN_VALUE ");
209
                    }
210
                    break;
211
                case 'url':
212
                    $filter_check = filter_var($value, FILTER_VALIDATE_URL);
213
                    if ($filter_check === false) {
214
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
215
                            "validation: INVALID_URL ");
216
                    }
217
                    break;
218
                case 'ip':
219
                    $filter_check = filter_var($value, FILTER_VALIDATE_IP);
220
                    if ($filter_check === false) {
221
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
222
                            "validation: INVALID_IP_ADDRESS ");
223
                    }
224
                    break;
225
                case 'mac_address':
226
                    $filter_check = filter_var($value, FILTER_VALIDATE_MAC);
227
                    if ($filter_check === false) {
228
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
229
                            "validation: INVALID_MAC_ADDRESS ");
230
                    }
231
                    break;
232
                case 'date':
233
                    $regex = "/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/";
234
                    $options = array("options"=>array("regexp"=> $regex));
235
                    $filter_check = filter_var($value, FILTER_VALIDATE_REGEXP, $options);
236
                    if ($filter_check === false) {
237
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
238
                            "validation: INVALID_FORMAT ");
239
                    }
240
                    break;
241
                case 'time':
242
                    $regex = "/^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])$/";
243
                    $options = array("options"=>array("regexp"=> $regex));
244
                    $filter_check = filter_var($value, FILTER_VALIDATE_REGEXP, $options);
245
                    if ($filter_check === false) {
246
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
247
                            "validation: INVALID_FORMAT ");
248
                    }
249
                    break;
250
                case 'datetime':
251
                    $date_part = "[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])";
252
                    $time_part = "([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])";
253
                    $regex = "/^".$date_part." ".$time_part."$/";
254
                    $options = array("options"=>array("regexp"=> $regex));
255
                    $filter_check = filter_var($value, FILTER_VALIDATE_REGEXP, $options);
256
                    if ($filter_check === false) {
257
                        $exception_message="Error for value '".$value."' for '".$key."' 
258
                                            couldn't pass the validation: INVALID_FORMAT";
259
                        throw new \Exception($exception_message);
260
                    }
261
                    break;
262
                case 'regex':
263
                    $regex = "/^".$format."$/";
264
                    $options = array("options"=>array("regexp"=> $regex));
265
                    $filter_check = filter_var($value, FILTER_VALIDATE_REGEXP, $options);
266
                    if ($filter_check === false) {
267
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
268
                            "validation: INVALID_FORMAT ");
269
                    }
270
                    break;
271
            }
272
        }
273
        switch ($type) {
274
            case 'integer':
275
            case 'float':
276
                if ($min_length !== null) {
277
                    if ($value<$min_length) {
278
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
279
                            "validation: Must be bigger than ".$min_length."  ");
280
                    }
281
                }
282
                if ($max_length !== null) {
283
                    if ($value>$max_length) {
284
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
285
                            "validation: Must be smallerr than ".$max_length."  ");
286
                    }
287
                }
288
                break;
289
            default:
290
                if ($max_length !== null) {
291
                    if (strlen($value)>$max_length) {
292
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the " .
293
                            "validation: It's length must be smaller than ".$max_length."  ");
294
                    }
295
                }
296
                if ($min_length !== null) {
297
                    if (strlen($value)<$min_length) {
298
                        throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the ".
299
                            "validation: It's length must be longer than ".$min_length."  ");
300
                    }
301
                }
302
                break;
303
        }
304
        if ($in_options !== null) {
305
            if (!in_array($value, $in_options)) {
306
                throw new \Exception("Error for value '".$value."' for '".$key."' couldn't pass the validation: ".
307
                    "It's length must be one of the these values: ".implode(", ", $in_options)."  ");
308
            }
309
        }
310
311
        return $value;
312
    }
313
314
    /**
315
     * @param mixed     $value
316
     * @param array     $my_model
317
     *
318
     * @return mixed
319
     */
320
    public static function sanitizeDocItem($value, $my_model)
321
    {
322
        $type = isset($my_model['_type']) ? $my_model['_type'] : 'string';
323
        $input_type = isset($my_model['_input_type']) ? $my_model['_input_type'] : null;
324
        $min_length = isset($my_model['_min_length']) ? $my_model['_min_length'] : null;
325
        $max_length = isset($my_model['_max_length']) ? $my_model['_max_length'] : null;
326
        $in_options = isset($my_model['_in_options']) ? $my_model['_in_options'] : null;
327
328
        if ($input_type !== null) {
329
            switch ($input_type) {
330
                case 'timestamp':
331
                    if ($value == 'now') {
332
                        $value = time();
333
                    }
334
                    break;
335
                default:
336
                    $value = filter_var($value, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
337
                    break;
338
            }
339
        }
340
        settype($value, $type);
341
        switch ($type) {
342
            case 'integer':
343
            case 'float':
344
                if ($min_length !== null) {
345
                    if ($value<$min_length) {
346
                        $value = $min_length;
347
                    }
348
                }
349
                if ($max_length !== null) {
350
                    if ($value>$max_length) {
351
                        $value = $max_length;
352
                    }
353
                }
354
                break;
355
            default:
356
                if ($max_length !== null) {
357
                    if (strlen($value)>$max_length) {
358
                        $value = substr($value, 0, $max_length);
359
                    }
360
                }
361
                break;
362
        }
363
        if ($in_options !== null) {
364
            if (!in_array($value, $in_options)) {
365
                $value = $in_options[0]; // First value of the in_options array is assumed to be the default value .
366
            }
367
        }
368
369
        return $value;
370
    }
371
372
    /**
373
     * A Note:
374
     * Since the built-in php function gettype returns "double" variabe type, here is the workaround function
375
     * See http://php . net/manual/en/function . gettype . php => Possible values for the returned string are:
376
     * "double" (for historical reasons "double" is returned in case of a float, and not simply "float")
377
     *
378
     * @param mixed     $value
379
     * @return string
380
     */
381
    public static function getType($value)
382
    {
383
        if (is_bool($value)) {
384
            return "boolean";
385
        } elseif (is_string($value)) {
386
            return "string";
387
        } elseif (is_int($value)) {
388
            return "integer";
389
        } elseif (is_float($value)) {
390
            return "float";
391
        } elseif (is_array($value)) {
392
            return "array";
393
        } elseif (is_null($value)) {
394
            return "null";
395
        } elseif (is_object($value)) {
396
            return "object";
397
        } elseif (is_resource($value)) {
398
            return "resource";
399
        } else {
400
            return "NA";
401
        }
402
    }
403
}
404