Completed
Push — master ( 8930a4...8cf557 )
by Mehmet
02:52
created

ModelUtils::setModelDefaults()   C

Complexity

Conditions 17
Paths 14

Size

Total Lines 65
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 65
rs 5.9044
cc 17
eloc 46
nc 14
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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