TMap::offsetUnset()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
1
<?php
2
3
/**
4
 * TMap class
5
 *
6
 * @author Qiang Xue <[email protected]>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 */
10
11
namespace Prado\Collections;
12
13
use Prado\Exceptions\TInvalidDataTypeException;
14
use Prado\Exceptions\TInvalidOperationException;
15
use Prado\Prado;
16
use Prado\TPropertyValue;
17
use Traversable;
18
19
/**
20
 * TMap class
21
 *
22
 * TMap implements a collection that takes key-value pairs.
23
 *
24
 * You can access, add or remove an item with a key by using
25
 * {@see itemAt}, {@see add}, and {@see remove}.
26
 * To get the number of the items in the map, use {@see getCount}.
27
 * TMap can also be used like a regular array as follows,
28
 * ```php
29
 * $map[$key]=$value; // add a key-value pair
30
 * unset($map[$key]); // remove the value with the specified key
31
 * if(isset($map[$key])) // if the map contains the key
32
 * foreach($map as $key=>$value) // traverse the items in the map
33
 * $n=count($map);  // returns the number of items in the map
34
 * ```
35
 *
36
 * @author Qiang Xue <[email protected]>
37
 * @since 3.0
38
 * @method void dyAddItem(mixed $key, mixed $value)
39
 * @method void dyRemoveItem(mixed $key, mixed $value)
40
 * @method mixed dyNoItem(mixed $returnValue, mixed $key)
41
 */
42
class TMap extends \Prado\TComponent implements \IteratorAggregate, \ArrayAccess, \Countable
43
{
44
	/**
45
	 * @var array<int|string, mixed> internal data storage
46
	 */
47
	protected array $_d = [];
48
	/**
49
	 * @var ?bool whether this list is read-only
50
	 */
51
	private ?bool $_r = null;
52
53
	/**
54
	 * Constructor.
55
	 * Initializes the list with an array or an iterable object.
56
	 * @param null|array|\Iterator $data the intial data. Default is null, meaning no initialization.
57
	 * @param ?bool $readOnly whether the list is read-only, default null.
58
	 * @throws TInvalidDataTypeException If data is not null and neither an array nor an iterator.
59
	 */
60
	public function __construct($data = null, $readOnly = null)
61
	{
62
		parent::__construct();
63
		if ($data !== null) {
64
			$this->copyFrom($data);
65
			$readOnly = (bool) $readOnly;
66
		}
67
		$this->setReadOnly($readOnly);
68
	}
69
70
	/**
71
	 * @return bool whether this map is read-only or not. Defaults to false.
72
	 */
73
	public function getReadOnly(): bool
74
	{
75 203
		return (bool) $this->_r;
76
	}
77 203
78 8
	/**
79
	 * @param null|bool|string $value whether this list is read-only or not
80 203
	 */
81 203
	public function setReadOnly($value)
82
	{
83
		if ($value === null) {
84
			return;
85
		}
86 2
		if ($this->_r === null || Prado::isCallingSelf()) {
87
			$this->_r = TPropertyValue::ensureBoolean($value);
88 2
		} else {
89
			throw new TInvalidOperationException('map_readonly_set', $this::class);
90
		}
91
	}
92
93
	/**
94 256
	 * This sets the read only property.
95
	 */
96 256
	protected function collapseReadOnly(): void
97 256
	{
98
		$this->_r = (bool) $this->_r;
99
	}
100
101
	/**
102
	 * Returns an iterator for traversing the items in the list.
103
	 * This method is required by the interface \IteratorAggregate.
104 93
	 * @return \Iterator an iterator for traversing the items in the list.
105
	 */
106 93
	public function getIterator(): \Iterator
107
	{
108
		return new \ArrayIterator($this->_d);
109
	}
110
111
	/**
112
	 * Returns the number of items in the map.
113
	 * This method is required by \Countable interface.
114 32
	 * @return int number of items in the map.
115
	 */
116 32
	public function count(): int
117
	{
118
		return $this->getCount();
119
	}
120
121
	/**
122 72
	 * @return int the number of items in the map
123
	 */
124 72
	public function getCount(): int
125
	{
126
		return count($this->_d);
127
	}
128
129
	/**
130 5
	 * @return array<int|string> the key list
131
	 */
132 5
	public function getKeys(): array
133
	{
134
		return array_keys($this->_d);
135
	}
136
137
	/**
138
	 * Returns the item with the specified key.
139
	 * This method is exactly the same as {@see offsetGet}.
140
	 * @param mixed $key the key
141 185
	 * @return mixed the element at the offset, null if no element is found at the offset
142
	 */
143 185
	public function itemAt($key)
144
	{
145
		return (isset($this->_d[$key]) || array_key_exists($key, $this->_d)) ? $this->_d[$key] : $this->dyNoItem(null, $key);
146
	}
147
148
	/**
149
	 * Adds an item into the map.
150
	 * Note, if the specified key already exists, the old value will be overwritten.
151
	 * @param mixed $key
152
	 * @param mixed $value
153 102
	 * @throws TInvalidOperationException if the map is read-only
154
	 * @return mixed The key of the item, which is calculated when the $key is null.
155 102
	 */
156 101
	public function add($key, $value): mixed
157
	{
158 2
		$this->collapseReadOnly();
159
		if (!$this->_r) {
160 101
			if ($key === null) {
161
				$this->_d[] = $value;
162
				$key = array_key_last($this->_d);
163
			} else {
164
				$this->_d[$key] = $value;
165
			}
166
			$this->dyAddItem($key, $value);
167
			return $key;
168 12
		} else {
169
			throw new TInvalidOperationException('map_readonly', $this::class);
170 12
		}
171 11
	}
172 10
173 10
	/**
174 10
	 * Removes an item from the map by its key.
175
	 * @param mixed $key the key of the item to be removed
176 2
	 * @throws TInvalidOperationException if the map is read-only
177
	 * @return mixed the removed value, null if no such key exists.
178
	 */
179 1
	public function remove($key)
180
	{
181
		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...
182
			if (isset($this->_d[$key]) || array_key_exists($key, $this->_d)) {
183
				$value = $this->_d[$key];
184
				unset($this->_d[$key]);
185
				$this->dyRemoveItem($key, $value);
186 14
				return $value;
187
			} else {
188 14
				return null;
189 5
			}
190
		} else {
191 14
			throw new TInvalidOperationException('map_readonly', $this::class);
192
		}
193
	}
194
195
	/**
196
	 * Removes an item from the map.  This removes all of an item from the map.
197 133
	 * @param mixed $item the item to be removed
198
	 * @throws TInvalidOperationException if the map is read-only
199 133
	 * @return array The array of keys and the item removed.
200
	 * since 4.3.0
201
	 */
202
	public function removeItem(mixed $item): array
203
	{
204
		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...
205 34
			$return = [];
206
			foreach ($this->toArray() as $key => $value) {
207 34
				if ($item === $value) {
208
					$return[$key] = $this->remove($key);
209
				}
210
			}
211
			return $return;
212
		} else {
213
			throw new TInvalidOperationException('map_readonly', $this::class);
214
		}
215
	}
216 62
217
	/**
218 62
	 * Removes all items in the map.
219 62
	 */
220 1
	public function clear(): void
221
	{
222 62
		foreach (array_keys($this->_d) as $key) {
223 62
			$this->remove($key);
224
		}
225 1
	}
226 1
227
	/**
228 62
	 * @param mixed $key the key
229
	 * @return bool whether the map contains an item with the specified key
230
	 */
231
	public function contains($key): bool
232
	{
233
		return isset($this->_d[$key]) || array_key_exists($key, $this->_d);
234
	}
235
236 1
	/**
237
	 * @param mixed $item the item
238 1
	 * @param bool $multiple Return an array of all the keys. Default true.
239 1
	 * @return false|mixed the key of the item in the map, false if not found.
240 1
	 * since 4.3.0
241
	 */
242 1
	public function keyOf($item, bool $multiple = true): mixed
243 1
	{
244
		if ($multiple) {
245 1
			$return = [];
246
			foreach ($this->toArray() as $key => $value) {
247
				if ($item === $value) {
248
					$return[$key] = $item;
249
				}
250
			}
251
			return $return;
252
		} else {
253 29
			return array_search($item, $this->_d, true);
254
		}
255 29
	}
256
257
	/**
258
	 * @return array<int|string, mixed> the list of items in array
259
	 */
260
	public function toArray(): array
261
	{
262
		return $this->_d;
263
	}
264 157
265
	/**
266 157
	 * Copies iterable data into the map.
267
	 * Note, existing data in the map will be cleared first.
268
	 * @param mixed $data the data to be copied from, must be an array or object implementing Traversable
269
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
270
	 */
271
	public function copyFrom($data): void
272
	{
273
		if (is_array($data) || $data instanceof Traversable) {
274
			if ($this->getCount() > 0) {
275 21
				$this->clear();
276
			}
277 21
			foreach ($data as $key => $value) {
278 21
				$this->add($key, $value);
279
			}
280
		} elseif ($data !== null) {
281
			throw new TInvalidDataTypeException('map_data_not_iterable');
282
		}
283
	}
284
285 2
	/**
286
	 * Merges iterable data into the map.
287 2
	 * Existing data in the map will be kept and overwritten if the keys are the same.
288 2
	 * @param mixed $data the data to be merged with, must be an array or object implementing Traversable
289
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
290
	 */
291
	public function mergeWith($data): void
292
	{
293
		if (is_array($data) || $data instanceof Traversable) {
294
			foreach ($data as $key => $value) {
295
				$this->add($key, $value);
296
			}
297
		} elseif ($data !== null) {
298
			throw new TInvalidDataTypeException('map_data_not_iterable');
299
		}
300
	}
301
302
	/**
303
	 * Returns whether there is an element at the specified offset.
304
	 * This method is required by the interface \ArrayAccess.
305
	 * @param mixed $offset the offset to check on
306
	 * @return bool
307
	 */
308
	public function offsetExists($offset): bool
309
	{
310
		return $this->contains($offset);
311
	}
312
313
	/**
314
	 * Returns the element at the specified offset.
315
	 * This method is required by the interface \ArrayAccess.
316
	 * @param mixed $offset the offset to retrieve element.
317
	 * @return mixed the element at the offset, null if no element is found at the offset
318
	 */
319
	public function offsetGet($offset): mixed
320
	{
321
		return $this->itemAt($offset);
322
	}
323
324
	/**
325
	 * Sets the element at the specified offset.
326
	 * This method is required by the interface \ArrayAccess.
327
	 * @param mixed $offset the offset to set element
328
	 * @param mixed $item the element value
329
	 */
330
	public function offsetSet($offset, $item): void
331
	{
332
		$this->add($offset, $item);
333
	}
334
335
	/**
336
	 * Unsets the element at the specified offset.
337
	 * This method is required by the interface \ArrayAccess.
338
	 * @param mixed $offset the offset to unset element
339
	 */
340
	public function offsetUnset($offset): void
341
	{
342
		$this->remove($offset);
343
	}
344
345
	/**
346
	 * Returns an array with the names of all variables of this object that should NOT be serialized
347
	 * because their value is the default one or useless to be cached for the next page loads.
348
	 * Reimplement in derived classes to add new variables, but remember to  also to call the parent
349
	 * implementation first.
350
	 * @param array $exprops by reference
351
	 */
352
	protected function _getZappableSleepProps(&$exprops)
353
	{
354
		parent::_getZappableSleepProps($exprops);
355
		if (empty($this->_d)) {
356
			$exprops[] = "\0*\0_d";
357
		}
358
		if ($this->_r === null) {
359
			$exprops[] = "\0" . __CLASS__ . "\0_r";
360
		}
361
	}
362
}
363