Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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 \Psr\SimpleCache\CacheInterface 16 */ 17 private $cache; 18 19 /** 20 * Parent worksheet. 21 * 22 * @var Worksheet 23 */ 24 private $parent; 25 26 /** 27 * The currently active Cell. 28 * 29 * @var Cell 30 */ 31 private $currentCell; 32 33 /** 34 * Coordinate of the currently active Cell. 35 * 36 * @var 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 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 $pCoord Coordinate of the cell to check 90 * 91 * @return bool 92 */ 93 public function has($pCoord) 94 { 95 if ($pCoord === $this->currentCoordinate) { 96 return true; 97 } 98 99 // Check if the requested entry exists in the index 100 return isset($this->index[$pCoord]); 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 $pCoord Coordinate of the cell to delete 119 */ 120 public function delete($pCoord): void 121 { 122 if ($pCoord === $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[$pCoord]); 130 131 // Delete the entry from cache 132 $this->cache->delete($this->cachePrefix . $pCoord); 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(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 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 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(max($columnList)); 261 } 262 263 /** 264 * Get highest worksheet row. 265 * 266 * @param 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 * @param Worksheet $parent The new worksheet that we're copying to 308 * 309 * @return self 310 */ 311 public function cloneCellCollection(Worksheet $parent) 312 { 313 $this->storeCurrentCell(); 314 $newCollection = clone $this; 315 316 $newCollection->parent = $parent; 317 if (($newCollection->currentCell !== null) && (is_object($newCollection->currentCell))) { 318 $newCollection->currentCell->attach($this); 319 } 320 321 // Get old values 322 $oldKeys = $newCollection->getAllCacheKeys(); 323 $oldValues = $newCollection->cache->getMultiple($oldKeys); 324 $newValues = []; 325 $oldCachePrefix = $newCollection->cachePrefix; 326 327 // Change prefix 328 $newCollection->cachePrefix = $newCollection->getUniqueID(); 329 foreach ($oldValues as $oldKey => $value) { 330 $newValues[str_replace($oldCachePrefix, $newCollection->cachePrefix, $oldKey)] = clone $value; 331 } 332 333 // Store new values 334 $stored = $newCollection->cache->setMultiple($newValues); 335 if (!$stored) { 336 $newCollection->__destruct(); 337 338 throw new PhpSpreadsheetException('Failed to copy cells in cache'); 339 } 340 341 return $newCollection; 342 } 343 344 /** 345 * Remove a row, deleting all cells in that row. 346 * 347 * @param string $row Row number to remove 348 */ 349 public function removeRow($row): void 350 { 351 foreach ($this->getCoordinates() as $coord) { 352 $c = ''; 353 $r = 0; 354 355 sscanf($coord, '%[A-Z]%d', $c, $r); 356 if ($r == $row) { 357 $this->delete($coord); 358 } 359 } 360 } 361 362 /** 363 * Remove a column, deleting all cells in that column. 364 * 365 * @param string $column Column ID to remove 366 */ 367 public function removeColumn($column): void 368 { 369 foreach ($this->getCoordinates() as $coord) { 370 $c = ''; 371 $r = 0; 372 373 sscanf($coord, '%[A-Z]%d', $c, $r); 374 if ($c == $column) { 375 $this->delete($coord); 376 } 377 } 378 } 379 380 /** 381 * Store cell data in cache for the current cell object if it's "dirty", 382 * and the 'nullify' the current cell object. 383 */ 384 private function storeCurrentCell(): void 385 { 386 if ($this->currentCellIsDirty && !empty($this->currentCoordinate)) { 387 $this->currentCell->detach(); 388 389 $stored = $this->cache->set($this->cachePrefix . $this->currentCoordinate, $this->currentCell); 390 if (!$stored) { 391 $this->__destruct(); 392 393 throw new PhpSpreadsheetException("Failed to store cell {$this->currentCoordinate} in cache"); 394 } 395 $this->currentCellIsDirty = false; 396 } 397 398 $this->currentCoordinate = null; 399 $this->currentCell = null; 400 } 401 402 /** 403 * Add or update a cell identified by its coordinate into the collection. 404 * 405 * @param string $pCoord Coordinate of the cell to update 406 * @param Cell $cell Cell to update 407 * 408 * @return \PhpOffice\PhpSpreadsheet\Cell\Cell 409 */ 410 public function add($pCoord, Cell $cell) 411 { 412 if ($pCoord !== $this->currentCoordinate) { 413 $this->storeCurrentCell(); 414 } 415 $this->index[$pCoord] = true; 416 417 $this->currentCoordinate = $pCoord; 418 $this->currentCell = $cell; 419 $this->currentCellIsDirty = true; 420 421 return $cell; 422 } 423 424 /** 425 * Get cell at a specific coordinate. 426 * 427 * @param string $pCoord Coordinate of the cell 428 * 429 * @return null|\PhpOffice\PhpSpreadsheet\Cell\Cell Cell that was found, or null if not found 430 */ 431 public function get($pCoord) 432 { 433 if ($pCoord === $this->currentCoordinate) { 434 return $this->currentCell; 435 } 436 $this->storeCurrentCell(); 437 438 // Return null if requested entry doesn't exist in collection 439 if (!$this->has($pCoord)) { 440 return null; 441 } 442 443 // Check if the entry that has been requested actually exists 444 $cell = $this->cache->get($this->cachePrefix . $pCoord); 445 if ($cell === null) { 446 throw new PhpSpreadsheetException("Cell entry {$pCoord} no longer exists in cache. This probably means that the cache was cleared by someone else."); 447 } 448 449 // Set current entry to the requested entry 450 $this->currentCoordinate = $pCoord; 451 $this->currentCell = $cell; 452 // Re-attach this as the cell's parent 453 $this->currentCell->attach($this); 454 455 // Return requested entry 456 return $this->currentCell; 457 } 458 459 /** 460 * Clear the cell collection and disconnect from our parent. 461 */ 462 public function unsetWorksheetCells(): void 463 { 464 if ($this->currentCell !== null) { 465 $this->currentCell->detach(); 466 $this->currentCell = null; 467 $this->currentCoordinate = null; 468 } 469 470 // Flush the cache 471 $this->__destruct(); 472 473 $this->index = []; 474 475 // detach ourself from the worksheet, so that it can then delete this object successfully 476 $this->parent = null; 477 } 478 479 /** 480 * Destroy this cell collection. 481 */ 482 public function __destruct() 483 { 484 $this->cache->deleteMultiple($this->getAllCacheKeys()); 485 } 486 487 /** 488 * Returns all known cache keys. 489 * 490 * @return Generator|string[] 491 */ 492 private function getAllCacheKeys() 493 { 494 foreach ($this->getCoordinates() as $coordinate) { 495 yield $this->cachePrefix . $coordinate; 496 } 497 } 498 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body