Passed
Push — master ( 4fc6c5...494471 )
by Fabio
07:54 queued 03:10
created

TMap   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 272
Duplicated Lines 0 %

Test Coverage

Coverage 90.54%

Importance

Changes 0
Metric Value
eloc 65
dl 0
loc 272
rs 8.8798
c 0
b 0
f 0
ccs 67
cts 74
cp 0.9054
wmc 44

21 Methods

Rating   Name   Duplication   Size   Complexity  
A getKeys() 0 3 1
A getIterator() 0 3 1
A getCount() 0 3 1
A count() 0 3 1
A offsetUnset() 0 3 1
A offsetSet() 0 3 1
A copyFrom() 0 11 6
A itemAt() 0 3 2
A setReadOnly() 0 9 4
A contains() 0 3 2
A remove() 0 13 4
A mergeWith() 0 8 5
A offsetGet() 0 3 1
A offsetExists() 0 3 1
A toArray() 0 3 1
A clear() 0 4 2
A add() 0 12 3
A _getZappableSleepProps() 0 8 3
A __construct() 0 8 2
A getReadOnly() 0 3 1
A collapseReadOnly() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like TMap 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.

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

1
<?php
2
/**
3
 * TMap class
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 */
9
10
namespace Prado\Collections;
11
12
use Prado\Exceptions\TInvalidDataTypeException;
13
use Prado\Exceptions\TInvalidOperationException;
14
use Prado\Prado;
15
use Prado\TPropertyValue;
16
use Traversable;
17
18
/**
19
 * TMap class
20
 *
21
 * TMap implements a collection that takes key-value pairs.
22
 *
23
 * You can access, add or remove an item with a key by using
24
 * {@link itemAt}, {@link add}, and {@link remove}.
25
 * To get the number of the items in the map, use {@link getCount}.
26
 * TMap can also be used like a regular array as follows,
27
 * <code>
28
 * $map[$key]=$value; // add a key-value pair
29
 * unset($map[$key]); // remove the value with the specified key
30
 * if(isset($map[$key])) // if the map contains the key
31
 * foreach($map as $key=>$value) // traverse the items in the map
32
 * $n=count($map);  // returns the number of items in the map
33
 * </code>
34
 *
35
 * @author Qiang Xue <[email protected]>
36
 * @since 3.0
37
 * @method void dyAddItem(mixed $key, mixed $value)
38
 * @method void dyRemoveItem(mixed $key, mixed $value)
39
 * @method mixed dyNoItem(mixed $returnValue, mixed $key)
40
 */
41
class TMap extends \Prado\TComponent implements \IteratorAggregate, \ArrayAccess, \Countable
42
{
43
	/**
44
	 * @var array<int|string, mixed> internal data storage
45
	 */
46
	protected array $_d = [];
47
	/**
48
	 * @var ?bool whether this list is read-only
49
	 */
50
	private ?bool $_r = null;
51
52
	/**
53
	 * Constructor.
54
	 * Initializes the list with an array or an iterable object.
55
	 * @param null|array|\Iterator $data the intial data. Default is null, meaning no initialization.
56
	 * @param ?bool $readOnly whether the list is read-only, default null.
57
	 * @throws TInvalidDataTypeException If data is not null and neither an array nor an iterator.
58
	 */
59
	public function __construct($data = null, $readOnly = null)
60
	{
61
		parent::__construct();
62
		if ($data !== null) {
63
			$this->copyFrom($data);
64
			$readOnly = (bool) $readOnly;
65
		}
66
		$this->setReadOnly($readOnly);
67
	}
68
69
	/**
70
	 * @return bool whether this map is read-only or not. Defaults to false.
71
	 */
72
	public function getReadOnly(): bool
73
	{
74
		return (bool) $this->_r;
75 203
	}
76
77 203
	/**
78 8
	 * @param null|bool|string $value whether this list is read-only or not
79
	 */
80 203
	public function setReadOnly($value)
81 203
	{
82
		if ($value === null) {
83
			return;
84
		}
85
		if($this->_r === null || Prado::isCallingSelf()) {
86 2
			$this->_r = TPropertyValue::ensureBoolean($value);
87
		} else {
88 2
			throw new TInvalidOperationException('map_readonly_set', $this::class);
89
		}
90
	}
91
92
	/**
93
	 * This sets the read only property.
94 256
	 */
95
	protected function collapseReadOnly(): void
96 256
	{
97 256
		$this->_r = (bool) $this->_r;
98
	}
99
100
	/**
101
	 * Returns an iterator for traversing the items in the list.
102
	 * This method is required by the interface \IteratorAggregate.
103
	 * @return \Iterator an iterator for traversing the items in the list.
104 93
	 */
105
	public function getIterator(): \Iterator
106 93
	{
107
		return new \ArrayIterator($this->_d);
108
	}
109
110
	/**
111
	 * Returns the number of items in the map.
112
	 * This method is required by \Countable interface.
113
	 * @return int number of items in the map.
114 32
	 */
115
	public function count(): int
116 32
	{
117
		return $this->getCount();
118
	}
119
120
	/**
121
	 * @return int the number of items in the map
122 72
	 */
123
	public function getCount(): int
124 72
	{
125
		return count($this->_d);
126
	}
127
128
	/**
129
	 * @return array<int|string> the key list
130 5
	 */
131
	public function getKeys(): array
132 5
	{
133
		return array_keys($this->_d);
134
	}
135
136
	/**
137
	 * Returns the item with the specified key.
138
	 * This method is exactly the same as {@link offsetGet}.
139
	 * @param mixed $key the key
140
	 * @return mixed the element at the offset, null if no element is found at the offset
141 185
	 */
142
	public function itemAt($key)
143 185
	{
144
		return array_key_exists($key, $this->_d) ? $this->_d[$key] : $this->dyNoItem(null, $key);
145
	}
146
147
	/**
148
	 * Adds an item into the map.
149
	 * Note, if the specified key already exists, the old value will be overwritten.
150
	 * @param mixed $key
151
	 * @param mixed $value
152
	 * @throws TInvalidOperationException if the map is read-only
153 102
	 */
154
	public function add($key, $value)
155 102
	{
156 101
		$this->collapseReadOnly();
157
		if (!$this->_r) {
158 2
			if ($key === null) {
159
				$this->_d[] = $value;
160 101
			} else {
161
				$this->_d[$key] = $value;
162
			}
163
			$this->dyAddItem($key, $value);
164
		} else {
165
			throw new TInvalidOperationException('map_readonly', $this::class);
166
		}
167
	}
168 12
169
	/**
170 12
	 * Removes an item from the map by its key.
171 11
	 * @param mixed $key the key of the item to be removed
172 10
	 * @throws TInvalidOperationException if the map is read-only
173 10
	 * @return mixed the removed value, null if no such key exists.
174 10
	 */
175
	public function remove($key)
176 2
	{
177
		if (!$this->_r) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_r of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
178
			if (isset($this->_d[$key]) || array_key_exists($key, $this->_d)) {
179 1
				$value = $this->_d[$key];
180
				unset($this->_d[$key]);
181
				$this->dyRemoveItem($key, $value);
182
				return $value;
183
			} else {
184
				return null;
185
			}
186 14
		} else {
187
			throw new TInvalidOperationException('map_readonly', $this::class);
188 14
		}
189 5
	}
190
191 14
	/**
192
	 * Removes all items in the map.
193
	 */
194
	public function clear(): void
195
	{
196
		foreach (array_keys($this->_d) as $key) {
197 133
			$this->remove($key);
198
		}
199 133
	}
200
201
	/**
202
	 * @param mixed $key the key
203
	 * @return bool whether the map contains an item with the specified key
204
	 */
205 34
	public function contains($key): bool
206
	{
207 34
		return isset($this->_d[$key]) || array_key_exists($key, $this->_d);
208
	}
209
210
	/**
211
	 * @return array<int|string, mixed> the list of items in array
212
	 */
213
	public function toArray(): array
214
	{
215
		return $this->_d;
216 62
	}
217
218 62
	/**
219 62
	 * Copies iterable data into the map.
220 1
	 * Note, existing data in the map will be cleared first.
221
	 * @param mixed $data the data to be copied from, must be an array or object implementing Traversable
222 62
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
223 62
	 */
224
	public function copyFrom($data): void
225 1
	{
226 1
		if (is_array($data) || $data instanceof Traversable) {
227
			if ($this->getCount() > 0) {
228 62
				$this->clear();
229
			}
230
			foreach ($data as $key => $value) {
231
				$this->add($key, $value);
232
			}
233
		} elseif ($data !== null) {
234
			throw new TInvalidDataTypeException('map_data_not_iterable');
235
		}
236 1
	}
237
238 1
	/**
239 1
	 * Merges iterable data into the map.
240 1
	 * Existing data in the map will be kept and overwritten if the keys are the same.
241
	 * @param mixed $data the data to be merged with, must be an array or object implementing Traversable
242 1
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
243 1
	 */
244
	public function mergeWith($data): void
245 1
	{
246
		if (is_array($data) || $data instanceof Traversable) {
247
			foreach ($data as $key => $value) {
248
				$this->add($key, $value);
249
			}
250
		} elseif ($data !== null) {
251
			throw new TInvalidDataTypeException('map_data_not_iterable');
252
		}
253 29
	}
254
255 29
	/**
256
	 * Returns whether there is an element at the specified offset.
257
	 * This method is required by the interface \ArrayAccess.
258
	 * @param mixed $offset the offset to check on
259
	 * @return bool
260
	 */
261
	public function offsetExists($offset): bool
262
	{
263
		return $this->contains($offset);
264 157
	}
265
266 157
	/**
267
	 * Returns the element at the specified offset.
268
	 * This method is required by the interface \ArrayAccess.
269
	 * @param mixed $offset the offset to retrieve element.
270
	 * @return mixed the element at the offset, null if no element is found at the offset
271
	 */
272
	public function offsetGet($offset): mixed
273
	{
274
		return $this->itemAt($offset);
275 21
	}
276
277 21
	/**
278 21
	 * Sets the element at the specified offset.
279
	 * This method is required by the interface \ArrayAccess.
280
	 * @param mixed $offset the offset to set element
281
	 * @param mixed $item the element value
282
	 */
283
	public function offsetSet($offset, $item): void
284
	{
285 2
		$this->add($offset, $item);
286
	}
287 2
288 2
	/**
289
	 * Unsets the element at the specified offset.
290
	 * This method is required by the interface \ArrayAccess.
291
	 * @param mixed $offset the offset to unset element
292
	 */
293
	public function offsetUnset($offset): void
294
	{
295
		$this->remove($offset);
296
	}
297
298
	/**
299
	 * Returns an array with the names of all variables of this object that should NOT be serialized
300
	 * because their value is the default one or useless to be cached for the next page loads.
301
	 * Reimplement in derived classes to add new variables, but remember to  also to call the parent
302
	 * implementation first.
303
	 * @param array $exprops by reference
304
	 */
305
	protected function _getZappableSleepProps(&$exprops)
306
	{
307
		parent::_getZappableSleepProps($exprops);
308
		if (empty($this->_d)) {
309
			$exprops[] = "\0*\0_d";
310
		}
311
		if ($this->_r === null) {
312
			$exprops[] = "\0" . __CLASS__ . "\0_r";
313
		}
314
	}
315
}
316