Skip to content

Commit 1a404e8

Browse files
authored
Merge pull request #2888 from PHPOffice/ReferenceHelper-Performance-Improvements
Reference helper performance improvements
2 parents 04f4667 + ec5e46a commit 1a404e8

File tree

3 files changed

+52
-26
lines changed

3 files changed

+52
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
3131
- Memory and speed improvements, particularly for the Cell Collection, and the Writers.
3232

3333
See [the Discussion section on github](https://github.com/PHPOffice/PhpSpreadsheet/discussions/2821) for details of performance across versions
34+
- Improved performance for removing rows/columns from a worksheet
3435

3536
### Deprecated
3637

docs/topics/recipes.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1348,14 +1348,31 @@ Removing a merge can be done using the unmergeCells method:
13481348
$spreadsheet->getActiveSheet()->unmergeCells('A18:E22');
13491349
```
13501350

1351-
## Inserting rows/columns
1351+
## Inserting or Removing rows/columns
13521352

13531353
You can insert/remove rows/columns at a specific position. The following
13541354
code inserts 2 new rows, right before row 7:
13551355

13561356
```php
13571357
$spreadsheet->getActiveSheet()->insertNewRowBefore(7, 2);
13581358
```
1359+
while
1360+
```php
1361+
$spreadsheet->getActiveSheet()->removeRow(7, 2);
1362+
```
1363+
will remove 2 rows starting at row number 7 (ie. rows 7 and 8).
1364+
1365+
Equivalent methods exist for inserting/removing columns:
1366+
1367+
```php
1368+
$spreadsheet->getActiveSheet()->removeColumn('C', 2);
1369+
```
1370+
1371+
All subsequent rows (or columns) will be moved to allow the insertion (or removal) with all formulae referencing thise cells adjusted accordingly.
1372+
1373+
Note that this is a fairly intensive process, particularly with large worksheets, and especially if you are inserting/removing rows/columns from near beginning of the worksheet.
1374+
1375+
If you need to insert/remove several consecutive rows/columns, always use the second argument rather than making multiple calls to insert/remove a single row/column if possible.
13591376

13601377
## Add a drawing to a worksheet
13611378

src/PhpSpreadsheet/ReferenceHelper.php

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,6 @@ public function insertNewBefore(
367367
Worksheet $worksheet
368368
): void {
369369
$remove = ($numberOfColumns < 0 || $numberOfRows < 0);
370-
$allCoordinates = $worksheet->getCoordinates();
371370

372371
if (
373372
$this->cellReferenceHelper === null ||
@@ -394,12 +393,13 @@ public function insertNewBefore(
394393
}
395394

396395
// Find missing coordinates. This is important when inserting column before the last column
396+
$cellCollection = $worksheet->getCellCollection();
397397
$missingCoordinates = array_filter(
398398
array_map(function ($row) use ($highestColumn) {
399399
return $highestColumn . $row;
400400
}, range(1, $highestRow)),
401-
function ($coordinate) use ($allCoordinates) {
402-
return in_array($coordinate, $allCoordinates, true) === false;
401+
function ($coordinate) use ($cellCollection) {
402+
return $cellCollection->has($coordinate) === false;
403403
}
404404
);
405405

@@ -408,16 +408,15 @@ function ($coordinate) use ($allCoordinates) {
408408
foreach ($missingCoordinates as $coordinate) {
409409
$worksheet->createNewCell($coordinate);
410410
}
411-
412-
// Refresh all coordinates
413-
$allCoordinates = $worksheet->getCoordinates();
414411
}
415412

416-
// Loop through cells, bottom-up, and change cell coordinate
413+
$allCoordinates = $worksheet->getCoordinates();
417414
if ($remove) {
418415
// It's faster to reverse and pop than to use unshift, especially with large cell collections
419416
$allCoordinates = array_reverse($allCoordinates);
420417
}
418+
419+
// Loop through cells, bottom-up, and change cell coordinate
421420
while ($coordinate = array_pop($allCoordinates)) {
422421
$cell = $worksheet->getCell($coordinate);
423422
$cellIndex = Coordinate::columnIndexFromString($cell->getColumn());
@@ -924,34 +923,43 @@ private function updateCellRange(string $cellRange = 'A1:A1', bool $includeAbsol
924923

925924
private function clearColumnStrips(int $highestRow, int $beforeColumn, int $numberOfColumns, Worksheet $worksheet): void
926925
{
927-
for ($i = 1; $i <= $highestRow - 1; ++$i) {
928-
for ($j = $beforeColumn - 1 + $numberOfColumns; $j <= $beforeColumn - 2; ++$j) {
929-
$coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i;
930-
$worksheet->removeConditionalStyles($coordinate);
931-
if ($worksheet->cellExists($coordinate)) {
932-
$worksheet->getCell($coordinate)->setValueExplicit(null, DataType::TYPE_NULL);
933-
$worksheet->getCell($coordinate)->setXfIndex(0);
934-
}
926+
$startColumnId = Coordinate::stringFromColumnIndex($beforeColumn + $numberOfColumns);
927+
$endColumnId = Coordinate::stringFromColumnIndex($beforeColumn);
928+
929+
for ($row = 1; $row <= $highestRow - 1; ++$row) {
930+
for ($column = $startColumnId; $column !== $endColumnId; ++$column) {
931+
$coordinate = $column . $row;
932+
$this->clearStripCell($worksheet, $coordinate);
935933
}
936934
}
937935
}
938936

939937
private function clearRowStrips(string $highestColumn, int $beforeColumn, int $beforeRow, int $numberOfRows, Worksheet $worksheet): void
940938
{
941-
$lastColumnIndex = Coordinate::columnIndexFromString($highestColumn) - 1;
942-
943-
for ($i = $beforeColumn - 1; $i <= $lastColumnIndex; ++$i) {
944-
for ($j = $beforeRow + $numberOfRows; $j <= $beforeRow - 1; ++$j) {
945-
$coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j;
946-
$worksheet->removeConditionalStyles($coordinate);
947-
if ($worksheet->cellExists($coordinate)) {
948-
$worksheet->getCell($coordinate)->setValueExplicit(null, DataType::TYPE_NULL);
949-
$worksheet->getCell($coordinate)->setXfIndex(0);
950-
}
939+
$startColumnId = Coordinate::stringFromColumnIndex($beforeColumn);
940+
++$highestColumn;
941+
942+
for ($column = $startColumnId; $column !== $highestColumn; ++$column) {
943+
for ($row = $beforeRow + $numberOfRows; $row <= $beforeRow - 1; ++$row) {
944+
$coordinate = $column . $row;
945+
$this->clearStripCell($worksheet, $coordinate);
951946
}
952947
}
953948
}
954949

950+
private function clearStripCell(Worksheet $worksheet, string $coordinate): void
951+
{
952+
$worksheet->removeConditionalStyles($coordinate);
953+
$worksheet->setHyperlink($coordinate);
954+
$worksheet->setDataValidation($coordinate);
955+
$worksheet->removeComment($coordinate);
956+
957+
if ($worksheet->cellExists($coordinate)) {
958+
$worksheet->getCell($coordinate)->setValueExplicit(null, DataType::TYPE_NULL);
959+
$worksheet->getCell($coordinate)->setXfIndex(0);
960+
}
961+
}
962+
955963
private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void
956964
{
957965
$autoFilter = $worksheet->getAutoFilter();

0 commit comments

Comments
 (0)