Completed
Push — master ( 29cde4...77c4c6 )
by Ori
03:02
created

BaseField::checkConstraints()   C

Complexity

Conditions 13
Paths 96

Size

Total Lines 61
Code Lines 42

Duplication

Lines 47
Ratio 77.05 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 47
loc 61
rs 6.2659
cc 13
eloc 42
nc 96
nop 1

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
namespace frictionlessdata\tableschema\Fields;
3
4
use frictionlessdata\tableschema\Exceptions\FieldValidationException;
5
use frictionlessdata\tableschema\SchemaValidationError;
6
7
abstract class BaseField
8
{
9
    public function __construct($descriptor=null)
10
    {
11
        $this->descriptor = empty($descriptor) ? (object)[] : $descriptor;
12
    }
13
14
    public function descriptor()
15
    {
16
        return $this->descriptor;
17
    }
18
19
    public function fullDescriptor()
20
    {
21
        $fullDescriptor = $this->descriptor();
22
        $fullDescriptor->format = $this->format();
23
        $fullDescriptor->type = $this->type();
24
        return $fullDescriptor;
25
    }
26
27
    public function name()
28
    {
29
        return $this->descriptor()->name;
30
    }
31
32
    public function format()
33
    {
34
        return isset($this->descriptor()->format) ? $this->descriptor()->format : "default";
35
    }
36
37
    public function constraints()
38
    {
39
        if (!$this->constraintsDisabled && isset($this->descriptor()->constraints)) {
40
            return $this->descriptor()->constraints;
41
        } else {
42
            return (object)[];
43
        }
44
    }
45
46
    public function required()
47
    {
48
        return (isset($this->constraints()->required) && $this->constraints()->required);
49
    }
50
51
    public function unique()
52
    {
53
        return (isset($this->constraints()->unique) && $this->constraints()->unique);
54
    }
55
56
    public function disableConstraints()
57
    {
58
        $this->constraintsDisabled = true;
59
        return $this;
60
    }
61
62
    public function enum()
63
    {
64
        if (isset($this->constraints()->enum) && !empty($this->constraints()->enum)) {
65
            return $this->constraints()->enum;
66
        } else {
67
            return [];
68
        }
69
    }
70
71
    /**
72
     * try to create a field object based on the descriptor
73
     * by default uses the type attribute
74
     * return the created field object or false if the descriptor does not match this field
75
     * @param object $descriptor
76
     * @return bool|BaseField
77
     */
78
    public static function inferDescriptor($descriptor)
79
    {
80
        if (isset($descriptor->type) && $descriptor->type == static::type()) {
81
            return new static($descriptor);
82
        } else {
83
            return false;
84
        }
85
    }
86
87
    /**
88
     * try to create a new field object based on the given value
89
     * @param mixed $val
90
     * @param null|object $descriptor
91
     * @param bool @lenient
92
     * @return bool|BaseField
93
     */
94
    public static function infer($val, $descriptor=null, $lenient=false)
95
    {
96
        $field = new static($descriptor);
97
        try {
98
            $field->castValue($val);
99
        } catch (FieldValidationException $e) {
100
            return false;
101
        }
102
        $field->inferProperties($val, $lenient);
103
        return $field;
104
    }
105
106
    public function inferProperties($val, $lenient)
107
    {
108
        // should be implemented by extending classes
109
        // allows adding / modfiying descriptor properties based on the given value
110
        $this->descriptor->type = $this->type();
111
    }
112
113
    /**
114
     * @param mixed $val
115
     * @return mixed
116
     * @throws \frictionlessdata\tableschema\Exceptions\FieldValidationException;
117
     */
118
    final public function castValue($val)
119
    {
120
        if ($this->isEmptyValue($val)) {
121
            if ($this->required()) throw $this->getValidationException("field is required", $val);
122
            return null;
123
        } else {
124
            return $this->validateCastValue($val);
125
        }
126
    }
127
128
    public function validateValue($val)
129
    {
130
        try {
131
            $this->castValue($val);
132
            return [];
133
        } catch (FieldValidationException $e) {
134
            return $e->validationErrors;
135
        }
136
    }
137
138
    /**
139
     * get a unique identifier for this field
140
     * used in the inferring process
141
     * this is usually the type, but can be modified to support more advanced inferring process
142
     * @param bool @lenient
143
     * @return string
144
     */
145
    public function getInferIdentifier($lenient=false)
146
    {
147
        return $this->type();
148
    }
149
150
    /**
151
     * should be implemented by extending classes to return the table schema type of this field
152
     * @return string
153
     */
154
    static public function type()
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
155
    {
156
        throw new \Exception("must be implemented by extending classes");
157
    }
158
159
    protected $descriptor;
160
    protected $constraintsDisabled = false;
161
162
    protected function getValidationException($errorMsg, $val=null)
163
    {
164
        return new FieldValidationException([
165
            new SchemaValidationError(SchemaValidationError::FIELD_VALIDATION, [
166
                "field" => $this->name(),
167
                "value" => $val,
168
                "error" => $errorMsg
169
            ])
170
        ]);
171
    }
172
173
    protected function isEmptyValue($val)
174
    {
175
        return is_null($val);
176
    }
177
178
    /**
179
     * @param mixed $val
180
     * @return mixed
181
     * @throws \frictionlessdata\tableschema\Exceptions\FieldValidationException;
182
     */
183
    protected function validateCastValue($val)
184
    {
185
        // extending classes should extend this method
186
        // value is guaranteed not to be an empty value, that is handled elsewhere
187
        // should raise FieldValidationException on any validation errors
188
        // can use getValidationException function to get a simple exception with single validation error message
189
        // you can also throw an exception with multiple validation errors manually
190
        if (!$this->constraintsDisabled) {
191
            $validationErrors = $this->checkConstraints($val);
192
            if (count($validationErrors) > 0) {
193
                throw new FieldValidationException($validationErrors);
194
            }
195
        }
196
        return $val;
197
    }
198
199
    protected function checkConstraints($val)
200
    {
201
        $validationErrors = [];
202
        $allowedValues = $this->getAllowedValues();
203
        if (!empty($allowedValues) && !in_array($val, $allowedValues)) {
204
            $validationErrors[] = new SchemaValidationError(SchemaValidationError::FIELD_VALIDATION, [
205
                "field" => $this->name(),
206
                "value" => $val,
207
                "error" => "value not in enum"
208
            ]);
209
        }
210
        $constraints = $this->constraints();
211 View Code Duplication
        if (isset($constraints->pattern)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
212
            if (!$this->checkPatternConstraint($val, $constraints->pattern)) {
213
                $validationErrors[] = new SchemaValidationError(SchemaValidationError::FIELD_VALIDATION, [
214
                    "field" => $this->name(),
215
                    "value" => $val,
216
                    "error" => "value does not match pattern"
217
                ]);
218
            }
219
        }
220 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
            isset($constraints->minimum)
222
            && !$this->checkMinimumConstraint($val, $this->castValueNoConstraints($constraints->minimum))
223
        ) {
224
            $validationErrors[] = new SchemaValidationError(SchemaValidationError::FIELD_VALIDATION, [
225
                "field" => $this->name(),
226
                "value" => $val,
227
                "error" => "value is below minimum"
228
            ]);
229
        }
230 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
231
            isset($constraints->maximum)
232
            && !$this->checkMaximumConstraint($val, $this->castValueNoConstraints($constraints->maximum))
233
        ) {
234
            $validationErrors[] = new SchemaValidationError(SchemaValidationError::FIELD_VALIDATION, [
235
                "field" => $this->name(),
236
                "value" => $val,
237
                "error" => "value is above maximum"
238
            ]);
239
        }
240 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
241
            isset($constraints->minLength) && !$this->checkMinLengthConstraint($val, $constraints->minLength)
242
        ) {
243
            $validationErrors[] = new SchemaValidationError(SchemaValidationError::FIELD_VALIDATION, [
244
                "field" => $this->name(),
245
                "value" => $val,
246
                "error" => "value is below minimum length"
247
            ]);
248
        }
249 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
250
            isset($constraints->maxLength) && !$this->checkMaxLengthConstraint($val, $constraints->maxLength)
251
        ) {
252
            $validationErrors[] = new SchemaValidationError(SchemaValidationError::FIELD_VALIDATION, [
253
                "field" => $this->name(),
254
                "value" => $val,
255
                "error" => "value is above maximum length"
256
            ]);
257
        }
258
        return $validationErrors;
259
    }
260
261
    protected function checkPatternConstraint($val, $pattern)
262
    {
263
        return preg_match("/^".$pattern."\$/", $val) === 1;
264
    }
265
266
    protected function checkMinimumConstraint($val, $minConstraint)
267
    {
268
        return $val >= $minConstraint;
269
    }
270
271
    protected function checkMaximumConstraint($val, $maxConstraint)
272
    {
273
        return $val <= $maxConstraint;
274
    }
275
276
    protected function checkMinLengthConstraint($val, $minLength)
277
    {
278
        return strlen($val) >= $minLength;
279
    }
280
281
    protected function checkMaxLengthConstraint($val, $maxLength)
282
    {
283
        return strlen($val) <= $maxLength;
284
    }
285
286
    protected function getAllowedValues()
287
    {
288
        $allowedValues = [];
289
        foreach ($this->enum() as $val) {
290
            $allowedValues[] = $this->castValueNoConstraints($val);
291
        }
292
        return $allowedValues;
293
    }
294
295
    protected function castValueNoConstraints($val)
296
    {
297
        $this->disableConstraints();
298
        $val = $this->castValue($val);
299
        $this->constraintsDisabled = false;
300
        return $val;
301
    }
302
}