Field   C
last analyzed

Complexity

Total Complexity 74

Size/Duplication

Total Lines 321
Duplicated Lines 12.46 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 14
Bugs 2 Features 2
Metric Value
wmc 74
c 14
b 2
f 2
lcom 1
cbo 2
dl 40
loc 321
rs 5.5244

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __get() 0 10 4
A __isset() 0 3 2
A isJSON() 0 3 1
A isEntity() 0 3 1
A isCollection() 0 3 1
A isUnique() 0 3 1
A __construct() 0 17 1
A toArray() 0 11 1
A isNumeric() 0 6 1
A isTemporal() 8 8 1
A isText() 8 8 1
A isObject() 0 6 1
C cast() 0 30 13
B validate() 0 16 6
A getDefault() 0 7 2
C validateType() 0 39 7
B validateRules() 0 17 5
B validateRange() 4 11 7
B validateLength() 4 11 5
A validateRegex() 8 8 3
A validateValues() 8 8 3
C processRules() 0 50 7

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 Field 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 Field, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * This file is part of Yolk - Gamer Network's PHP Framework.
4
 *
5
 * Copyright (c) 2015 Gamer Network Ltd.
6
 *
7
 * Distributed under the MIT License, a copy of which is available in the
8
 * LICENSE file that was bundled with this package, or online at:
9
 * https://github.com/gamernetwork/yolk-support
10
 */
11
12
namespace yolk\support;
13
14
use yolk\contracts\support\Error;
15
use yolk\contracts\support\Type;
16
17
/**
18
 * Definition of an field.
19
 */
20
class Field {
21
22
	protected $name;
23
24
	protected $type;
25
26
	protected $required;
27
28
	protected $nullable;
29
30
	protected $default;
31
32
    protected $label;
33
34
	protected $rules;
35
36
	public function __construct( $name, $type = Type::TEXT, array $rules = [] ) {
37
38
		$this->name = $name;
39
		$this->type = $type;
40
41
		// default rules
42
		$defaults = [
43
			'required' => false,
44
			'nullable' => false,
45
			'default'  => '',
46
            'label'    => $name,
47
		];
48
		$rules += $defaults;
49
50
		$this->processRules($rules);
51
52
	}
53
54
	public function __get( $key ) {
55
		if( $key == 'default' )
56
			return $this->getDefault();
57
		elseif( isset($this->rules[$key]) )
58
			return $this->rules[$key];
59
		elseif( isset($this->$key) )
60
			return $this->$key;
61
		else
62
			return null;
63
	}
64
65
	public function __isset( $key ) {
66
		return isset($this->$key) || isset($this->rules[$key]);
67
	}
68
69
    public function toArray() {
70
        return [
71
            'name'     => $this->name,
72
            'type'     => $this->type,
73
            'required' => $this->required,
74
            'nullable' => $this->nullable,
75
            'default'  => $this->default,
76
            'label'    => $this->label,
77
            'rules'    => $this->rules,
78
        ];
79
    }
80
81
	public function isNumeric() {
82
		return in_array($this->type, [
83
			Type::INTEGER,
84
			Type::FLOAT
85
		]);
86
	}
87
88 View Code Duplication
	public function isTemporal() {
89
		return in_array($this->type, [
90
			Type::DATETIME,
91
			Type::DATE,
92
			Type::TIME,
93
			Type::YEAR,
94
		]);
95
	}
96
97 View Code Duplication
	public function isText() {
98
		return in_array($this->type, [
99
			Type::TEXT,
100
			Type::IP,
101
			Type::EMAIL,
102
			Type::URL,
103
		]);
104
	}
105
106
	public function isJSON() {
107
		return $this->type == Type::JSON;
108
	}
109
110
	public function isObject() {
111
		return in_array($this->type, [
112
			Type::OBJECT,
113
			Type::ENTITY,
114
		]);
115
	}
116
117
	public function isEntity() {
118
		return $this->type == Type::ENTITY;
119
	}
120
121
	public function isCollection() {
122
		return $this->type == Type::COLLECTION;
123
	}
124
125
	public function isUnique() {
126
		return !empty($this->rules['unique']);
127
	}
128
129
	public function cast( $v ) {
130
131
		switch( $this->type ) {
132
			case Type::INTEGER:
133
			case Type::TIMESTAMP:
134
			case Type::YEAR:
135
				return (int) $v;
136
137
			case Type::FLOAT:
138
				return (float) $v;
139
140
			case Type::BOOLEAN:
141
				return (bool) $v;
142
143
			case Type::DATETIME:
144
			case Type::DATE:
145
				return preg_match('/0000-00-00/', $v) ? '' : $v;
146
147
			case Type::JSON:
148
				if( !$v )
149
					$v = [];
150
				elseif( !is_array($v) && is_array($arr = json_decode($v, true)) )
151
					$v = $arr;
152
				return $v;
153
154
			default:
155
				return $v;
156
		}
157
158
	}
159
160
	public function validate( $v ) {
161
162
		if( $this->required && Validator::isEmpty($v, $this->type) )
163
			return [$v, Error::REQUIRED];
164
165
		elseif( !$this->nullable && ($v === null) )
166
			return [$v, Error::NULL];
167
168
		list($clean, $error) = $this->validateType($v, $this->type);
169
170
		if( $error )
171
			return [$v, $error];
172
		else
173
			return $this->validateRules($clean);
174
175
	}
176
177
	/**
178
	 * Return the fields default value.
179
	 * @return mixed
180
	 */
181
	protected function getDefault() {
182
		$default = $this->default;
183
		if( $default instanceof \Closure )
184
			return $default();
185
		else
186
			return $this->cast($default);
187
	}
188
189
	protected function validateType( $v, $type ) {
190
191
		// Innocent until proven guilty
192
		$error = Error::NONE;
193
		$clean = $v;
194
195
		$validators = [
196
			Type::TEXT     => 'validateText',
197
			Type::INTEGER  => 'validateInteger',
198
			Type::FLOAT    => 'validateFloat',
199
			Type::BOOLEAN  => 'validateBoolean',
200
			Type::DATETIME => 'validateDateTime',
201
			Type::DATE     => 'validateDate',
202
			Type::TIME     => 'validateTime',
203
			Type::YEAR     => 'validateYear',
204
			Type::EMAIL    => 'validateEmail',
205
			Type::URL      => 'validateURL',
206
			Type::IP       => 'validateIP',
207
			Type::JSON     => 'validateJSON',
208
		];
209
210
		if( isset($validators[$type]) ) {
211
			$method = $validators[$type];
212
			$clean = Validator::$method($v);
213
		}
214
		elseif( in_array($type, [Type::OBJECT, Type::ENTITY]) ){
215
			$clean = Validator::validateObject($v, $this->rules['class'], $this->nullable);
216
		}
217
		elseif( $type == Type::BINARY ) {
218
			$clean = (string) $v;
219
		}
220
221
		// boolean fields will be null on error
222
		if( ($clean === false) || (($type == Type::BOOLEAN) && ($clean === null)) )
223
			$error = Error::getTypeError($type);
224
225
		return [$clean, $error];
226
227
	}
228
229
	protected function validateRules( $v ) {
230
231
		if( $error = $this->validateRange($v) )
232
			return [$v, $error];
233
234
		elseif( $error = $this->validateLength($v) )
235
			return [$v, $error];
236
237
		elseif( $error = $this->validateValues($v) )
238
			return [$v, $error];
239
240
		elseif( $error = $this->validateRegex($v) )
241
			return [$v, $error];
242
243
		return [$v, $error];
244
245
	}
246
247
	protected function validateRange( $v ) {
248
249 View Code Duplication
		if( isset($this->rules['min']) && $v && ($v < $this->rules['min']) )
250
			return Error::MIN;
251
252 View Code Duplication
		if( isset($this->rules['max']) && $v && ($v > $this->rules['max']) )
253
			return Error::MAX;
254
255
		return Error::NONE;
256
257
	}
258
259
	protected function validateLength( $v ) {
260
261 View Code Duplication
		if( isset($this->rules['min_length']) && mb_strlen($v) < $this->rules['min_length'] )
262
			return Error::TOO_SHORT;
263
264 View Code Duplication
		if( isset($this->rules['max_length']) && mb_strlen($v) > $this->rules['max_length'] )
265
			return Error::TOO_LONG;
266
267
		return Error::NONE;
268
269
	}
270
271 View Code Duplication
	protected function validateRegex( $v ) {
272
273
		if( isset($this->rules['regex']) && !preg_match($this->rules['regex'], $v) )
274
			return Error::REGEX;
275
276
		return Error::NONE;
277
278
	}
279
280 View Code Duplication
	protected function validateValues( $v ) {
281
282
		if( isset($this->rules['values']) && !in_array($v, $this->rules['values']) )
283
			return Error::VALUE;
284
285
		return Error::NONE;
286
287
	}
288
289
	protected function processRules( array $rules, $container = 'array' ) {
290
291
		$this->required = $rules['required'];
292
		$this->nullable = $rules['nullable'];
293
		$this->default  = $rules['default'];
294
        $this->label    = $rules['label'];
295
296
		unset(
297
			$rules['required'],
298
			$rules['nullable'],
299
			$rules['default'],
300
			$rules['label']
301
		);
302
303
		if( $this->isObject() ) {
304
305
			// object fields must specify a class
306
			if( empty($rules['class']) )
307
				throw new \LogicException("Missing class name for item: {$this->name}");
308
309
			// object fields are nullable by default
310
			$this->nullable = true;
311
			$this->default  = null;
312
313
		}
314
		elseif( $this->isCollection() ) {
315
316
			// collection fields must specify a class
317
			if( empty($rules['class']) )
318
				throw new \LogicException("Missing item class for collection: {$this->name}");
319
320
			$rules['container'] = empty($rules['container']) ? $container : $rules['container'];
321
322
			// replace default with closure to generate a new collection
323
			$this->default = function() use ($rules) {
324
325
				$container = $rules['container'];
326
327
				if( $container == 'array' )
328
					return [];
329
				else
330
					return new $container($rules['class']);
331
332
			};
333
334
		}
335
336
		$this->rules = $rules;
337
338
	}
339
340
}
341