pradosoft /
prado
| 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
|
|||
| 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
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 $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 |
If an expression can have both
false, andnullas possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.