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

BaseField   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 296
Duplicated Lines 15.88 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 57
c 1
b 0
f 0
lcom 1
cbo 2
dl 47
loc 296
rs 6.433

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 2
A descriptor() 0 4 1
A fullDescriptor() 0 7 1
A name() 0 4 1
A format() 0 4 2
A constraints() 0 8 3
A required() 0 4 2
A unique() 0 4 2
A disableConstraints() 0 5 1
A enum() 0 8 3
A inferDescriptor() 0 8 3
A infer() 0 11 2
A inferProperties() 0 6 1
A castValue() 0 9 3
A validateValue() 0 9 2
A getInferIdentifier() 0 4 1
A type() 0 4 1
A getValidationException() 0 10 1
A isEmptyValue() 0 4 1
A validateCastValue() 0 15 3
C checkConstraints() 47 61 13
A checkPatternConstraint() 0 4 1
A checkMinimumConstraint() 0 4 1
A checkMaximumConstraint() 0 4 1
A checkMinLengthConstraint() 0 4 1
A checkMaxLengthConstraint() 0 4 1
A getAllowedValues() 0 8 2
A castValueNoConstraints() 0 7 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like BaseField often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BaseField, and based on these observations, apply Extract Interface, too.

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
}