Completed
Push — master ( 119996...aeb75f )
by Mehmet
03:18
created

ModelUtils::validateDocItem()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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