space   B
last analyzed

Complexity

Total Complexity 51

Size/Duplication

Total Lines 376
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 51
lcom 1
cbo 2
dl 0
loc 376
rs 8.3206
c 0
b 0
f 0

26 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A name() 0 3 1
A export() 0 9 3
A _import_input() 0 7 2
A _check_specs() 0 16 3
A _check_spec_name() 0 16 3
A _check_spec_minmax() 0 16 4
A _check_spec_overflow() 0 5 3
A _run_spec() 0 6 3
A _spec_range() 0 16 4
A _overflow_loop() 0 6 3
A _overflow_limit() 0 3 1
A jsonSerialize() 0 3 1
A offsetSet() 0 12 2
A offsetUnset() 0 9 2
A exchangeArray() 0 8 3
A _assoc_keys() 0 13 3
A append() 0 1 1
A asort() 0 1 1
A ksort() 0 1 1
A uasort() 0 1 1
A uksort() 0 1 1
A natcasesort() 0 1 1
A natsort() 0 1 1
A setFlags() 0 1 1
A setIteratorClass() 0 1 1

How to fix   Complexity   

Complex Class

Complex classes like space 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 space, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Color Space Data Model
4
 * ======================
5
 * This class acts a momentary data model cache. This is so once a conversion is
6
 * done once for a color it won't need to be done again. (as long as the color
7
 * doesn't change)
8
 */
9
10
namespace projectcleverweb\color\data\color;
11
12
use \projectcleverweb\color\error;
13
use \projectcleverweb\color\validate;
14
15
/**
16
 * Color Space Data Model
17
 * ======================
18
 * This class acts a momentary data model cache. This is so once a conversion is
19
 * done once for a color it won't need to be done again. (as long as the color
20
 * doesn't change)
21
 */
22
abstract class space extends \ArrayObject implements \JsonSerializable {
23
	
24
	/**
25
	 * The name of the current color space
26
	 * @var string
27
	 */
28
	protected static $name = '';
29
	
30
	/**
31
	 * The specification for each key of the color space
32
	 * @var array
33
	 */
34
	protected static $specs = array();
35
	
36
	/**
37
	 * Import an array as the current color space
38
	 * 
39
	 * @param array $input The color array to import
40
	 */
41
	public function __construct(array $input) {
42
		parent::__construct(array(), \ArrayObject::STD_PROP_LIST | \ArrayObject::ARRAY_AS_PROPS);
43
		static::_check_specs(static::$specs);
44
		$this->exchangeArray($input);
45
	}
46
	
47
	/**
48
	 * Get the name of the current color space
49
	 * 
50
	 * @return string The current color space
51
	 */
52
	public static function name() :string {
53
		return static::$name;
54
	}
55
	
56
	/**
57
	 * Export the array in its intended format
58
	 * 
59
	 * @return array The formatted color array
60
	 */
61
	public function export() :array {
62
		$data = $this->getArrayCopy();
63
		foreach ($data as $key => &$value) {
64
			if (!static::$spec[$key]['allow_float']) {
65
				$value = round($value);
66
			}
67
		}
68
		return $data;
69
	}
70
	
71
	/**
72
	 * Import a value according to its specification
73
	 * 
74
	 * @param  array  $value The value to import
0 ignored issues
show
Documentation introduced by
Should the type for parameter $value not be double?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
75
	 * @param  string $key   The key being imported
76
	 * @param  array  $spec  The specification to follow
77
	 * @return void
78
	 */
79
	protected function _import_input(float $value, string $key, array $spec) {
80
		if (isset($value)) {
81
			parent::offsetSet($key, static::_run_spec($value, $key, $spec));
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (offsetSet() instead of _import_input()). Are you sure this is correct? If so, you might want to change this to $this->offsetSet().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
82
		} else {
83
			parent::offsetSet($key, $spec['default']);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (offsetSet() instead of _import_input()). Are you sure this is correct? If so, you might want to change this to $this->offsetSet().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
84
		}
85
	}
86
	
87
	/**
88
	 * Check that the color space child class was correctly configured
89
	 * 
90
	 * @param  array &$specs The specification array to check
91
	 * @return void
92
	 */
93
	protected static function _check_specs(&$specs) {
94
		if (empty($specs['checked'])) {
95
			foreach ($specs as $key => &$spec) {
96
				static::_check_spec_name($spec['name'] ?? NULL, $key);
97
				static::_check_spec_minmax($spec['min'] ?? NULL, $spec['max'] ?? NULL, $key);
98
				static::_check_spec_overflow($spec['overflow_method'], $key);
99
				$spec['min']            = (float) $spec['min'];
100
				$spec['max']            = (float) $spec['max'];
101
				$spec['default']        = (float) $spec['default'];
102
				$spec['allow_negative'] = (bool) ($spec['allow_negative'] ?? TRUE);
103
				$spec['allow_float']    = (bool) ($spec['allow_float'] ?? TRUE);
104
				static::_spec_range($spec['default'], 'default', $spec);
105
			}
106
			$specs['checked'] = TRUE;
107
		}
108
	}
109
	
110
	/**
111
	 * Check that the name of a specification is correctly formatted
112
	 * 
113
	 * @param  mixed  $name The name value to check
114
	 * @param  string $key  The key being checked (for debug)
115
	 * @return void
116
	 */
117
	protected static function _check_spec_name($name, string $key) {
118
		if (!is_string($name)) {
119
			error::trigger(error::INVALID_CONFIG, sprintf(
120
				"The 'name' of the key '%s' must be a string in '%s'",
121
				$key,
122
				get_called_class()
123
			));
124
		}
125
		if (empty($name)) {
126
			error::trigger(error::INVALID_CONFIG, sprintf(
127
				"The 'name' of the key '%s' cannot be empty in '%s'",
128
				$key,
129
				get_called_class()
130
			));
131
		}
132
	}
133
	
134
	/**
135
	 * Check that the name of a specification is correctly formatted
136
	 * 
137
	 * @param  mixed  $min The min value to check
138
	 * @param  mixed  $max The max value to check
139
	 * @param  string $key The key being checked (for debug)
140
	 * @return void
141
	 */
142
	protected static function _check_spec_minmax($min, $max, string $key) {
143
		if (!is_numeric($min) || !is_numeric($max)) {
144
			error::trigger(error::INVALID_CONFIG, sprintf(
145
				"The 'min' and 'max' of the key '%s' must be numeric in '%s'",
146
				$key,
147
				get_called_class()
148
			));
149
		}
150
		if ((float) $min >= (float) $max) {
151
			error::trigger(error::INVALID_CONFIG, sprintf(
152
				"The 'min' must be greater than the 'max' of the key '%s' in '%s'",
153
				$key,
154
				get_called_class()
155
			));
156
		}
157
	}
158
	
159
	/**
160
	 * Check that the overflow value is valid
161
	 * 
162
	 * @param  mixed  &$overflow The overflow value to check
163
	 * @param  string $key       The key being checked (for debug)
164
	 * @return void
165
	 */
166
	protected static function _check_spec_overflow(&$overflow, string $key) {
167
		if (!is_string($overflow) || !is_callable(array(get_called_class(), '_overflow_'.$overflow))) {
168
			$overflow = 'error';
169
		}
170
	}
171
	
172
	/**
173
	 * Run the specification check on a key's value
174
	 * 
175
	 * @param  float  $value The value to check
176
	 * @param  string $key   The key to check against
177
	 * @param  array  $spec  The specification to use
178
	 * @return float         The value after applying the specification
179
	 */
180
	protected static function _run_spec(float $value, string $key, array $spec) :float {
181
		if (!$spec['allow_negative'] && $value < 0) {
182
			$value = abs($value);
183
		}
184
		return static::_spec_range($value, $key, $spec);
185
	}
186
	
187
	/**
188
	 * Force a value into the specification's range
189
	 * 
190
	 * @param  float  $value The value to check
191
	 * @param  string $key   The key to check against
192
	 * @param  array  $spec  The specification to use
193
	 * @return float         The valid value
194
	 */
195
	protected static function _spec_range(float $value, string $key, array $spec) :float {
196
		$in_range = validate::in_range($value, $spec['min'], $spec['max']);
197
		if (!$in_range && is_callable($callback = array(get_called_class(), '_overflow_'.$spec['overflow_method']))) {
198
			$value = call_user_func($callback, $value, $spec);
199
		} elseif (!$in_range) {
200
			error::trigger(error::INVALID_VALUE, sprintf(
201
				"Key '%s' equaled '%s' but must be in range: %s through %s",
202
				$key,
203
				$value,
204
				$spec['min'],
205
				$spec['max']
206
			));
207
			$value = $spec['default'];
208
		}
209
		return $value;
210
	}
211
	
212
	/**
213
	 * Force a value into the specification's range by looping back around
214
	 * 
215
	 * @param  float  $value The value to check
216
	 * @param  array  $spec  The specification to use
217
	 * @return float         The valid value
218
	 */
219
	protected static function _overflow_loop(float $value, array $spec) :float {
220
		if ($spac['min'] == 0 && $spec['max'] > 0) {
0 ignored issues
show
Bug introduced by
The variable $spac does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
221
			return fmod($value, $spec['max']);
222
		}
223
		return fmod($value + abs(0 - $spac['min']), abs($spac['min'] - $spec['max'])) + $spac['min'];
224
	}
225
	
226
	/**
227
	 * Force a value into the specification's range by limiting to the nearest
228
	 * minimum/maximum
229
	 *  
230
	 * @param  float  $value The value to check
231
	 * @param  array  $spec  The specification to use
232
	 * @return float         The valid value
233
	 */
234
	protected static function _overflow_limit(float $value, array $spec) :float {
235
		return max($spac['min'], min($value, $spec['max']));
0 ignored issues
show
Bug introduced by
The variable $spac does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
236
	}
237
	
238
	/**
239
	 * Get the raw values to be serialized by JSON
240
	 * 
241
	 * @return array The current color array
242
	 */
243
	public function jsonSerialize() {
244
		return $this->getArrayCopy();
245
	}
246
	
247
	/**
248
	 * Set an existing offset according to its specification
249
	 * 
250
	 * @param  string $key   The key to set
251
	 * @param  mixed  $value The value to check and set
252
	 * @return float         The value that was set
0 ignored issues
show
Documentation introduced by
Should the return type not be double|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
253
	 */
254
	public function offsetSet($key, $value) {
255
		settype($key, 'string');
256
		if (isset(static::$specs[$key])) {
257
			$value = static::_run_spec($value, $key, static::$specs[$key]);
258
			parent::offsetSet($key, $value);
259
			return $value;
260
		}
261
		error::trigger(error::INVALID_ARGUMENT, sprintf(
262
			"Cannot set key '%s'",
263
			$key
264
		));
265
	}
266
	
267
	/**
268
	 * Set a value to its minimum value
269
	 * 
270
	 * @param  string $key   The key to set
271
	 * @return void
272
	 */
273
	public function offsetUnset($key) {
274
		if (isset(static::$specs[$key])) {
275
			$this->offsetSet($key, static::$specs[$key]['min']);
276
		}
277
		error::trigger(error::INVALID_ARGUMENT, sprintf(
278
			"Cannot unset key '%s'",
279
			$key
280
		));
281
	}
282
	
283
	/**
284
	 * Replace the current color array with another array of the same type
285
	 * 
286
	 * @param  array $input The array to import
287
	 * @return void
288
	 */
289
	public function exchangeArray($input) {
290
		static::_assoc_keys(static::$specs, $input);
291
		foreach (static::$specs as $key => $spec) {
292
			if ($key != 'checked') {
293
				$this->_import_input($input[$key], $key, $spec);
294
			}
295
		}
296
	}
297
	
298
	/**
299
	 * Associate expected keys when working with numerically indexed arrays
300
	 * 
301
	 * @param  array  $specs  The specification to follow
302
	 * @param  array  &$input The input array
303
	 * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be null|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
304
	 */
305
	protected static function _assoc_keys(array $specs, array &$input) {
306
		$is_assoc = FALSE;
0 ignored issues
show
Unused Code introduced by
$is_assoc is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
307
		foreach ($input as $key => $value) {
308
			if (!is_numeric($key)) {
309
				return;
310
			}
311
		}
312
		$target_length = count(static::$specs);
313
		return array_combine(
314
			array_keys(static::$specs),
315
			array_slice(array_pad($input, $target_length, 0.0), 0, $target_length)
316
		);
317
	}
318
	
319
	/*** DISABLED METHODS ***/
320
	
321
	/**
322
	 * DISABLED
323
	 * 
324
	 * @param  mixed $disabled Disabled
0 ignored issues
show
Bug introduced by
There is no parameter named $disabled. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
325
	 * @return void
326
	 * @codeCoverageIgnore
327
	 */
328
	public function append($disbaled) {}
329
	
330
	/**
331
	 * DISABLED
332
	 * 
333
	 * @return void
334
	 * @codeCoverageIgnore
335
	 */
336
	public function asort() {}
337
	
338
	/**
339
	 * DISABLED
340
	 * 
341
	 * @return void
342
	 * @codeCoverageIgnore
343
	 */
344
	public function ksort() {}
345
	
346
	/**
347
	 * DISABLED
348
	 * 
349
	 * @param  mixed $disabled Disabled
350
	 * @return void
351
	 * @codeCoverageIgnore
352
	 */
353
	public function uasort($disabled) {}
354
	
355
	/**
356
	 * DISABLED
357
	 * 
358
	 * @param  mixed $disabled Disabled
359
	 * @return void
360
	 * @codeCoverageIgnore
361
	 */
362
	public function uksort($disabled) {}
363
	
364
	/**
365
	 * DISABLED
366
	 * 
367
	 * @return void
368
	 * @codeCoverageIgnore
369
	 */
370
	public function natcasesort() {}
371
	
372
	/**
373
	 * DISABLED
374
	 * 
375
	 * @return void
376
	 * @codeCoverageIgnore
377
	 */
378
	public function natsort() {}
379
	
380
	/**
381
	 * DISABLED
382
	 * 
383
	 * @param  mixed $disabled Disabled
384
	 * @return void
385
	 * @codeCoverageIgnore
386
	 */
387
	public function setFlags($disabled) {}
388
	
389
	/**
390
	 * DISABLED
391
	 * 
392
	 * @param  mixed $disabled Disabled
393
	 * @return void
394
	 * @codeCoverageIgnore
395
	 */
396
	public function setIteratorClass($disabled) {}
397
}
398