Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Collection; 4 5 use Generator; 6 use PhpOffice\PhpSpreadsheet\Cell\Cell; 7 use PhpOffice\PhpSpreadsheet\Cell\Coordinate; 8 use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; 9 use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; 10 use Psr\SimpleCache\CacheInterface; 11 12 class Cells 13 { 14 /** 15 * @var CacheInterface 16 */ 17 private $cache; 18 19 /** 20 * Parent worksheet. 21 * 22 * @var null|Worksheet 23 */ 24 private $parent; 25 26 /** 27 * The currently active Cell. 28 * 29 * @var null|Cell 30 */ 31 private $currentCell; 32 33 /** 34 * Coordinate of the currently active Cell. 35 * 36 * @var null|string 37 */ 38 private $currentCoordinate; 39 40 /** 41 * Flag indicating whether the currently active Cell requires saving. 42 * 43 * @var bool 44 */ 45 private $currentCellIsDirty = false; 46 47 /** 48 * An index of existing cells. Booleans indexed by their coordinate. 49 * 50 * @var bool[] 51 */ 52 private $index = []; 53 54 /** 55 * Prefix used to uniquely identify cache data for this worksheet. 56 * 57 * @var string 58 */ 59 private $cachePrefix; 60 61 /** 62 * Initialise this new cell collection. 63 * 64 * @param Worksheet $parent The worksheet for this cell collection 65 */ 66 public function __construct(Worksheet $parent, CacheInterface $cache) 67 { 68 // Set our parent worksheet. 69 // This is maintained here to facilitate re-attaching it to Cell objects when 70 // they are woken from a serialized state 71 $this->parent = $parent; 72 $this->cache = $cache; 73 $this->cachePrefix = $this->getUniqueID(); 74 } 75 76 /** 77 * Return the parent worksheet for this cell collection. 78 * 79 * @return null|Worksheet 80 */ 81 public function getParent() 82 { 83 return $this->parent; 84 } 85 86 /** 87 * Whether the collection holds a cell for the given coordinate. 88 * 89 * @param string $cellCoordinate Coordinate of the cell to check 90 * 91 * @return bool 92 */ 93 public function has($cellCoordinate) 94 { 95 if ($cellCoordinate === $this->currentCoordinate) { 96 return true; 97 } 98 99 // Check if the requested entry exists in the index 100 return isset($this->index[$cellCoordinate]); 101 } 102 103 /** 104 * Add or update a cell in the collection. 105 * 106 * @param Cell $cell Cell to update 107 * 108 * @return Cell 109 */ 110 public function update(Cell $cell) 111 { 112 return $this->add($cell->getCoordinate(), $cell); 113 } 114 115 /** 116 * Delete a cell in cache identified by coordinate. 117 * 118 * @param string $cellCoordinate Coordinate of the cell to delete 119 */ 120 public function delete($cellCoordinate): void 121 { 122 if ($cellCoordinate === $this->currentCoordinate && $this->currentCell !== null) { 123 $this->currentCell->detach(); 124 $this->currentCoordinate = null; 125 $this->currentCell = null; 126 $this->currentCellIsDirty = false; 127 } 128 129 unset($this->index[$cellCoordinate]); 130 131 // Delete the entry from cache 132 $this->cache->delete($this->cachePrefix . $cellCoordinate); 133 } 134 135 /** 136 * Get a list of all cell coordinates currently held in the collection. 137 * 138 * @return string[] 139 */ 140 public function getCoordinates() 141 { 142 return array_keys($this->index); 143 } 144 145 /** 146 * Get a sorted list of all cell coordinates currently held in the collection by row and column. 147 * 148 * @return string[] 149 */ 150 public function getSortedCoordinates() 151 { 152 $sortKeys = []; 153 foreach ($this->getCoordinates() as $coord) { 154 $column = ''; 155 $row = 0; 156 sscanf($coord, '%[A-Z]%d', $column, $row); 157 $sortKeys[sprintf('%09d%3s', $row, $column)] = $coord; 158 } 159 ksort($sortKeys); 160 161 return array_values($sortKeys); 162 } 163 164 /** 165 * Get highest worksheet column and highest row that have cell records. 166 * 167 * @return array Highest column name and highest row number 168 */ 169 public function getHighestRowAndColumn() 170 { 171 // Lookup highest column and highest row 172 $col = ['A' => '1A']; 173 $row = [1]; 174 foreach ($this->getCoordinates() as $coord) { 175 $c = ''; 176 $r = 0; 177 sscanf($coord, '%[A-Z]%d', $c, $r); 178 $row[$r] = $r; 179 $col[$c] = strlen($c) . $c; 180 } 181 182 // Determine highest column and row 183 $highestRow = max($row); 184 $highestColumn = substr((string) @max($col), 1); 185 186 return [ 187 'row' => $highestRow, 188 'column' => $highestColumn, 189 ]; 190 } 191 192 /** 193 * Return the cell coordinate of the currently active cell object. 194 * 195 * @return null|string 196 */ 197 public function getCurrentCoordinate() 198 { 199 return $this->currentCoordinate; 200 } 201 202 /** 203 * Return the column coordinate of the currently active cell object. 204 * 205 * @return string 206 */ 207 public function getCurrentColumn() 208 { 209 $column = ''; 210 $row = 0; 211 212 sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row); 213 214 return $column; 215 } 216 217 /** 218 * Return the row coordinate of the currently active cell object. 219 * 220 * @return int 221 */ 222 public function getCurrentRow() 223 { 224 $column = ''; 225 $row = 0; 226 227 sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row); 228 229 return (int) $row; 230 } 231 232 /** 233 * Get highest worksheet column. 234 * 235 * @param null|int|string $row Return the highest column for the specified row, 236 * or the highest column of any row if no row number is passed 237 * 238 * @return string Highest column name 239 */ 240 public function getHighestColumn($row = null) 241 { 242 if ($row === null) { 243 $colRow = $this->getHighestRowAndColumn(); 244 245 return $colRow['column']; 246 } 247 248 $columnList = [1]; 249 foreach ($this->getCoordinates() as $coord) { 250 $c = ''; 251 $r = 0; 252 253 sscanf($coord, '%[A-Z]%d', $c, $r); 254 if ($r != $row) { 255 continue; 256 } 257 $columnList[] = Coordinate::columnIndexFromString($c); 258 } 259 260 return Coordinate::stringFromColumnIndex((int) @max($columnList)); 261 } 262 263 /** 264 * Get highest worksheet row. 265 * 266 * @param null|string $column Return the highest row for the specified column, 267 * or the highest row of any column if no column letter is passed 268 * 269 * @return int Highest row number 270 */ 271 public function getHighestRow($column = null) 272 { 273 if ($column === null) { 274 $colRow = $this->getHighestRowAndColumn(); 275 276 return $colRow['row']; 277 } 278 279 $rowList = [0]; 280 foreach ($this->getCoordinates() as $coord) { 281 $c = ''; 282 $r = 0; 283 284 sscanf($coord, '%[A-Z]%d', $c, $r); 285 if ($c != $column) { 286 continue; 287 } 288 $rowList[] = $r; 289 } 290 291 return max($rowList); 292 } 293 294 /** 295 * Generate a unique ID for cache referencing. 296 * 297 * @return string Unique Reference 298 */ 299 private function getUniqueID() 300 { 301 return uniqid('phpspreadsheet.', true) . '.'; 302 } 303 304 /** 305 * Clone the cell collection. 306 * 307 * @return self 308 */ 309 public function cloneCellCollection(Worksheet $worksheet) 310 { 311 $this->storeCurrentCell(); 312 $newCollection = clone $this; 313 314 $newCollection->parent = $worksheet; 315 if (is_object($newCollection->currentCell)) { 316 $newCollection->currentCell->attach($this); 317 } 318 319 // Get old values 320 $oldKeys = $newCollection->getAllCacheKeys(); 321 $oldValues = $newCollection->cache->getMultiple($oldKeys); 322 $newValues = []; 323 $oldCachePrefix = $newCollection->cachePrefix; 324 325 // Change prefix 326 $newCollection->cachePrefix = $newCollection->getUniqueID(); 327 foreach ($oldValues as $oldKey => $value) { 328 /** @var string $newKey */ 329 $newKey = str_replace($oldCachePrefix, $newCollection->cachePrefix, $oldKey); 330 $newValues[$newKey] = clone $value; 331 } 332 333 // Store new values 334 $stored = $newCollection->cache->setMultiple($newValues); 335 $this->destructIfNeeded($stored, $newCollection, 'Failed to copy cells in cache'); 336 337 return $newCollection; 338 } 339 340 /** 341 * Remove a row, deleting all cells in that row. 342 * 343 * @param string $row Row number to remove 344 */ 345 public function removeRow($row): void 346 { 347 foreach ($this->getCoordinates() as $coord) { 348 $c = ''; 349 $r = 0; 350 351 sscanf($coord, '%[A-Z]%d', $c, $r); 352 if ($r == $row) { 353 $this->delete($coord); 354 } 355 } 356 } 357 358 /** 359 * Remove a column, deleting all cells in that column. 360 * 361 * @param string $column Column ID to remove 362 */ 363 public function removeColumn($column): void 364 { 365 foreach ($this->getCoordinates() as $coord) { 366 $c = ''; 367 $r = 0; 368 369 sscanf($coord, '%[A-Z]%d', $c, $r); 370 if ($c == $column) { 371 $this->delete($coord); 372 } 373 } 374 } 375 376 /** 377 * Store cell data in cache for the current cell object if it's "dirty", 378 * and the 'nullify' the current cell object. 379 */ 380 private function storeCurrentCell(): void 381 { 382 if ($this->currentCellIsDirty && isset($this->currentCoordinate, $this->currentCell)) { 383 $this->currentCell->detach(); 384 385 $stored = $this->cache->set($this->cachePrefix . $this->currentCoordinate, $this->currentCell); 386 $this->destructIfNeeded($stored, $this, "Failed to store cell {$this->currentCoordinate} in cache"); 387 $this->currentCellIsDirty = false; 388 } 389 390 $this->currentCoordinate = null; 391 $this->currentCell = null; 392 } 393 394 private function destructIfNeeded(bool $stored, self $cells, string $message): void 395 { 396 if (!$stored) { 397 $cells->__destruct(); 398 399 throw new PhpSpreadsheetException($message); 400 } 401 } 402 403 /** 404 * Add or update a cell identified by its coordinate into the collection. 405 * 406 * @param string $cellCoordinate Coordinate of the cell to update 407 * @param Cell $cell Cell to update 408 * 409 * @return Cell 410 */ 411 public function add($cellCoordinate, Cell $cell) 412 { 413 if ($cellCoordinate !== $this->currentCoordinate) { 414 $this->storeCurrentCell(); 415 } 416 $this->index[$cellCoordinate] = true; 417 418 $this->currentCoordinate = $cellCoordinate; 419 $this->currentCell = $cell; 420 $this->currentCellIsDirty = true; 421 422 return $cell; 423 } 424 425 /** 426 * Get cell at a specific coordinate. 427 * 428 * @param string $cellCoordinate Coordinate of the cell 429 * 430 * @return null|Cell Cell that was found, or null if not found 431 */ 432 public function get($cellCoordinate) 433 { 434 if ($cellCoordinate === $this->currentCoordinate) { 435 return $this->currentCell; 436 } 437 $this->storeCurrentCell(); 438 439 // Return null if requested entry doesn't exist in collection 440 if (!$this->has($cellCoordinate)) { 441 return null; 442 } 443 444 // Check if the entry that has been requested actually exists 445 $cell = $this->cache->get($this->cachePrefix . $cellCoordinate); 446 if ($cell === null) { 447 throw new PhpSpreadsheetException("Cell entry {$cellCoordinate} no longer exists in cache. This probably means that the cache was cleared by someone else."); 448 } 449 450 // Set current entry to the requested entry 451 $this->currentCoordinate = $cellCoordinate; 452 $this->currentCell = $cell; 453 // Re-attach this as the cell's parent 454 $this->currentCell->attach($this); 455 456 // Return requested entry 457 return $this->currentCell; 458 } 459 460 /** 461 * Clear the cell collection and disconnect from our parent. 462 */ 463 public function unsetWorksheetCells(): void 464 { 465 if ($this->currentCell !== null) { 466 $this->currentCell->detach(); 467 $this->currentCell = null; 468 $this->currentCoordinate = null; 469 } 470 471 // Flush the cache 472 $this->__destruct(); 473 474 $this->index = []; 475 476 // detach ourself from the worksheet, so that it can then delete this object successfully 477 $this->parent = null; 478 } 479 480 /** 481 * Destroy this cell collection. 482 */ 483 public function __destruct() 484 { 485 $this->cache->deleteMultiple($this->getAllCacheKeys()); 486 } 487 488 /** 489 * Returns all known cache keys. 490 * 491 * @return Generator|string[] 492 */ 493 private function getAllCacheKeys() 494 { 495 foreach ($this->getCoordinates() as $coordinate) { 496 yield $this->cachePrefix . $coordinate; 497 } 498 } 499 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body