Passed
Push — master ( 494471...7bbba0 )
by Fabio
06:36 queued 01:48
created

TMap::getReadOnly()   A

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 0
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
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
	 * @param mixed $item the item
212
	 * @return false|mixed the key of the item in the map, false if not found.
213
	 */
214
	public function keyOf($item)
215
	{
216 62
		return array_search($item, $this->_d, true);
217
	}
218 62
219 62
	/**
220 1
	 * @return array<int|string, mixed> the list of items in array
221
	 */
222 62
	public function toArray(): array
223 62
	{
224
		return $this->_d;
225 1
	}
226 1
227
	/**
228 62
	 * Copies iterable data into the map.
229
	 * Note, existing data in the map will be cleared first.
230
	 * @param mixed $data the data to be copied from, must be an array or object implementing Traversable
231
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
232
	 */
233
	public function copyFrom($data): void
234
	{
235
		if (is_array($data) || $data instanceof Traversable) {
236 1
			if ($this->getCount() > 0) {
237
				$this->clear();
238 1
			}
239 1
			foreach ($data as $key => $value) {
240 1
				$this->add($key, $value);
241
			}
242 1
		} elseif ($data !== null) {
243 1
			throw new TInvalidDataTypeException('map_data_not_iterable');
244
		}
245 1
	}
246
247
	/**
248
	 * Merges iterable data into the map.
249
	 * Existing data in the map will be kept and overwritten if the keys are the same.
250
	 * @param mixed $data the data to be merged with, must be an array or object implementing Traversable
251
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
252
	 */
253 29
	public function mergeWith($data): void
254
	{
255 29
		if (is_array($data) || $data instanceof Traversable) {
256
			foreach ($data as $key => $value) {
257
				$this->add($key, $value);
258
			}
259
		} elseif ($data !== null) {
260
			throw new TInvalidDataTypeException('map_data_not_iterable');
261
		}
262
	}
263
264 157
	/**
265
	 * Returns whether there is an element at the specified offset.
266 157
	 * This method is required by the interface \ArrayAccess.
267
	 * @param mixed $offset the offset to check on
268
	 * @return bool
269
	 */
270
	public function offsetExists($offset): bool
271
	{
272
		return $this->contains($offset);
273
	}
274
275 21
	/**
276
	 * Returns the element at the specified offset.
277 21
	 * This method is required by the interface \ArrayAccess.
278 21
	 * @param mixed $offset the offset to retrieve element.
279
	 * @return mixed the element at the offset, null if no element is found at the offset
280
	 */
281
	public function offsetGet($offset): mixed
282
	{
283
		return $this->itemAt($offset);
284
	}
285 2
286
	/**
287 2
	 * Sets the element at the specified offset.
288 2
	 * This method is required by the interface \ArrayAccess.
289
	 * @param mixed $offset the offset to set element
290
	 * @param mixed $item the element value
291
	 */
292
	public function offsetSet($offset, $item): void
293
	{
294
		$this->add($offset, $item);
295
	}
296
297
	/**
298
	 * Unsets the element at the specified offset.
299
	 * This method is required by the interface \ArrayAccess.
300
	 * @param mixed $offset the offset to unset element
301
	 */
302
	public function offsetUnset($offset): void
303
	{
304
		$this->remove($offset);
305
	}
306
307
	/**
308
	 * Returns an array with the names of all variables of this object that should NOT be serialized
309
	 * because their value is the default one or useless to be cached for the next page loads.
310
	 * Reimplement in derived classes to add new variables, but remember to  also to call the parent
311
	 * implementation first.
312
	 * @param array $exprops by reference
313
	 */
314
	protected function _getZappableSleepProps(&$exprops)
315
	{
316
		parent::_getZappableSleepProps($exprops);
317
		if (empty($this->_d)) {
318
			$exprops[] = "\0*\0_d";
319
		}
320
		if ($this->_r === null) {
321
			$exprops[] = "\0" . __CLASS__ . "\0_r";
322
		}
323
	}
324
}
325