Skip to content

Commit 49bab44

Browse files
committed
Allow Clone on Spreadsheet
Since we have a usable copy method in Spreadsheet, this is probably overkill, especially with serialization now allowed. Nevertheless, I would prefer to not artificially render a standard Php operation (clone) unusable.
1 parent 414f8a2 commit 49bab44

File tree

5 files changed

+170
-67
lines changed

5 files changed

+170
-67
lines changed

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,6 @@ class Calculation
133133
*/
134134
public ?string $formulaError = null;
135135

136-
/**
137-
* Reference Helper.
138-
*/
139-
private static ReferenceHelper $referenceHelper;
140-
141136
/**
142137
* An array of the nested cell references accessed by the calculation engine, used for the debug log.
143138
*/
@@ -2895,7 +2890,6 @@ public function __construct(?Spreadsheet $spreadsheet = null)
28952890
$this->cyclicReferenceStack = new CyclicReferenceStack();
28962891
$this->debugLog = new Logger($this->cyclicReferenceStack);
28972892
$this->branchPruner = new BranchPruner($this->branchPruningEnabled);
2898-
self::$referenceHelper = ReferenceHelper::getInstance();
28992893
}
29002894

29012895
private static function loadLocales(): void
@@ -5741,7 +5735,7 @@ private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksh
57415735
$recursiveCalculationCellAddress = $recursiveCalculationCell->getCoordinate();
57425736

57435737
// Adjust relative references in ranges and formulae so that we execute the calculation for the correct rows and columns
5744-
$definedNameValue = self::$referenceHelper->updateFormulaReferencesAnyWorksheet(
5738+
$definedNameValue = ReferenceHelper::getInstance()->updateFormulaReferencesAnyWorksheet(
57455739
$definedNameValue,
57465740
Coordinate::columnIndexFromString($cell->getColumn()) - 1,
57475741
$cell->getRow() - 1

src/PhpSpreadsheet/Spreadsheet.php

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,11 +1076,78 @@ public function copy(): self
10761076
return $reloadedSpreadsheet;
10771077
}
10781078

1079+
/**
1080+
* Implement PHP __clone to create a deep clone, not just a shallow copy.
1081+
*/
10791082
public function __clone()
10801083
{
1081-
throw new Exception(
1082-
'Do not use clone on spreadsheet. Use spreadsheet->copy() instead.'
1083-
);
1084+
$this->uniqueID = uniqid('', true);
1085+
1086+
$usedKeys = [];
1087+
// I don't now why new Style rather than clone.
1088+
$this->cellXfSupervisor = new Style(true);
1089+
//$this->cellXfSupervisor = clone $this->cellXfSupervisor;
1090+
$this->cellXfSupervisor->bindParent($this);
1091+
$usedKeys['cellXfSupervisor'] = true;
1092+
1093+
$oldCalc = $this->calculationEngine;
1094+
$this->calculationEngine = new Calculation($this);
1095+
if ($oldCalc !== null) {
1096+
$this->calculationEngine
1097+
->setInstanceArrayReturnType(
1098+
$oldCalc->getInstanceArrayReturnType()
1099+
);
1100+
}
1101+
$usedKeys['calculationEngine'] = true;
1102+
1103+
$currentCollection = $this->cellStyleXfCollection;
1104+
$this->cellStyleXfCollection = [];
1105+
foreach ($currentCollection as $item) {
1106+
$clone = $item->exportArray();
1107+
$style = (new Style())->applyFromArray($clone);
1108+
$this->addCellStyleXf($style);
1109+
}
1110+
$usedKeys['cellStyleXfCollection'] = true;
1111+
1112+
$currentCollection = $this->cellXfCollection;
1113+
$this->cellXfCollection = [];
1114+
foreach ($currentCollection as $item) {
1115+
$clone = $item->exportArray();
1116+
$style = (new Style())->applyFromArray($clone);
1117+
$this->addCellXf($style);
1118+
}
1119+
$usedKeys['cellXfCollection'] = true;
1120+
1121+
$currentCollection = $this->workSheetCollection;
1122+
$this->workSheetCollection = [];
1123+
foreach ($currentCollection as $item) {
1124+
$clone = clone $item;
1125+
$clone->setParent($this);
1126+
$this->workSheetCollection[] = $clone;
1127+
}
1128+
$usedKeys['workSheetCollection'] = true;
1129+
1130+
foreach (get_object_vars($this) as $key => $val) {
1131+
if (isset($usedKeys[$key])) {
1132+
continue;
1133+
}
1134+
switch ($key) {
1135+
// arrays of objects not covered above
1136+
case 'definedNames':
1137+
$currentCollection = $val;
1138+
$this->$key = [];
1139+
foreach ($currentCollection as $item) {
1140+
$clone = clone $item;
1141+
$this->$key[] = $clone;
1142+
}
1143+
1144+
break;
1145+
default:
1146+
if (is_object($val)) {
1147+
$this->$key = clone $val;
1148+
}
1149+
}
1150+
}
10841151
}
10851152

10861153
/**

src/PhpSpreadsheet/Worksheet/Worksheet.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,6 @@ class Worksheet
7575
*/
7676
private Cells $cellCollection;
7777

78-
private bool $cellCollectionInitialized = true;
79-
8078
/**
8179
* Collection of row dimensions.
8280
*
@@ -360,10 +358,9 @@ public function __construct(?Spreadsheet $parent = null, string $title = 'Worksh
360358
*/
361359
public function disconnectCells(): void
362360
{
363-
if ($this->cellCollectionInitialized) {
361+
if (isset($this->cellCollection)) {
364362
$this->cellCollection->unsetWorksheetCells();
365363
unset($this->cellCollection);
366-
$this->cellCollectionInitialized = false;
367364
}
368365
// detach ourself from the workbook, so that it can then delete this worksheet successfully
369366
$this->parent = null;
@@ -463,7 +460,7 @@ private static function checkSheetTitle(string $sheetTitle): string
463460
*/
464461
public function getCoordinates(bool $sorted = true): array
465462
{
466-
if ($this->cellCollectionInitialized === false) {
463+
if (!isset($this->cellCollection)) {
467464
return [];
468465
}
469466

@@ -835,6 +832,13 @@ public function rebindParent(Spreadsheet $parent): static
835832
return $this;
836833
}
837834

835+
public function setParent(Spreadsheet $parent): self
836+
{
837+
$this->parent = $parent;
838+
839+
return $this;
840+
}
841+
838842
/**
839843
* Get title.
840844
*/
@@ -3465,8 +3469,7 @@ public function isEmptyColumn(string $columnId, int $definitionOfEmptyFlags = 0)
34653469
*/
34663470
public function __clone()
34673471
{
3468-
// @phpstan-ignore-next-line
3469-
foreach ($this as $key => $val) {
3472+
foreach (get_object_vars($this) as $key => $val) {
34703473
if ($key == 'parent') {
34713474
continue;
34723475
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests;
6+
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class SpreadsheetCopyCloneTest extends TestCase
11+
{
12+
private ?Spreadsheet $spreadsheet = null;
13+
14+
private ?Spreadsheet $spreadsheet2 = null;
15+
16+
protected function tearDown(): void
17+
{
18+
if ($this->spreadsheet !== null) {
19+
$this->spreadsheet->disconnectWorksheets();
20+
$this->spreadsheet = null;
21+
}
22+
if ($this->spreadsheet2 !== null) {
23+
$this->spreadsheet2->disconnectWorksheets();
24+
$this->spreadsheet2 = null;
25+
}
26+
}
27+
28+
public function runTests(string $type): void
29+
{
30+
$this->spreadsheet = new Spreadsheet();
31+
$sheet = $this->spreadsheet->getActiveSheet();
32+
$sheet->setTitle('original');
33+
$sheet->getStyle('A1')->getFont()->setName('font1');
34+
$sheet->getStyle('A2')->getFont()->setName('font2');
35+
$sheet->getStyle('A3')->getFont()->setName('font3');
36+
$sheet->getStyle('B1')->getFont()->setName('font1');
37+
$sheet->getStyle('B2')->getFont()->setName('font2');
38+
$sheet->getCell('A1')->setValue('this is a1');
39+
$sheet->getCell('A2')->setValue('this is a2');
40+
$sheet->getCell('A3')->setValue('this is a3');
41+
$sheet->getCell('B1')->setValue('this is b1');
42+
$sheet->getCell('B2')->setValue('this is b2');
43+
self::assertSame('font1', $sheet->getStyle('A1')->getFont()->getName());
44+
if ($type === 'copy') {
45+
$this->spreadsheet2 = $this->spreadsheet->copy();
46+
} else {
47+
$this->spreadsheet2 = clone $this->spreadsheet;
48+
}
49+
$copysheet = $this->spreadsheet2->getActiveSheet();
50+
self::assertSame('original', $copysheet->getTitle());
51+
$copysheet->setTitle('unoriginal');
52+
self::assertSame('original', $sheet->getTitle());
53+
self::assertSame('unoriginal', $copysheet->getTitle());
54+
$copysheet->getStyle('A2')->getFont()->setName('font12');
55+
$copysheet->getCell('A2')->setValue('this was a2');
56+
57+
self::assertSame('font1', $sheet->getStyle('A1')->getFont()->getName());
58+
self::assertSame('font2', $sheet->getStyle('A2')->getFont()->getName());
59+
self::assertSame('font3', $sheet->getStyle('A3')->getFont()->getName());
60+
self::assertSame('font1', $sheet->getStyle('B1')->getFont()->getName());
61+
self::assertSame('font2', $sheet->getStyle('B2')->getFont()->getName());
62+
self::assertSame('this is a1', $sheet->getCell('A1')->getValue());
63+
self::assertSame('this is a2', $sheet->getCell('A2')->getValue());
64+
self::assertSame('this is a3', $sheet->getCell('A3')->getValue());
65+
self::assertSame('this is b1', $sheet->getCell('B1')->getValue());
66+
self::assertSame('this is b2', $sheet->getCell('B2')->getValue());
67+
68+
self::assertSame('font1', $copysheet->getStyle('A1')->getFont()->getName());
69+
self::assertSame('font12', $copysheet->getStyle('A2')->getFont()->getName());
70+
self::assertSame('font3', $copysheet->getStyle('A3')->getFont()->getName());
71+
self::assertSame('font1', $copysheet->getStyle('B1')->getFont()->getName());
72+
self::assertSame('font2', $copysheet->getStyle('B2')->getFont()->getName());
73+
self::assertSame('this is a1', $copysheet->getCell('A1')->getValue());
74+
self::assertSame('this was a2', $copysheet->getCell('A2')->getValue());
75+
self::assertSame('this is a3', $copysheet->getCell('A3')->getValue());
76+
self::assertSame('this is b1', $copysheet->getCell('B1')->getValue());
77+
self::assertSame('this is b2', $copysheet->getCell('B2')->getValue());
78+
}
79+
80+
public function testClone(): void
81+
{
82+
$this->runTests('clone');
83+
}
84+
85+
public function testCopy(): void
86+
{
87+
$this->runTests('copy');
88+
}
89+
}

tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -154,54 +154,4 @@ public function testInvalidTabRatio(): void
154154
$this->spreadsheet = new Spreadsheet();
155155
$this->spreadsheet->setTabRatio(2000);
156156
}
157-
158-
public function testCopy(): void
159-
{
160-
$this->spreadsheet = new Spreadsheet();
161-
$sheet = $this->spreadsheet->getActiveSheet();
162-
$sheet->getStyle('A1')->getFont()->setName('font1');
163-
$sheet->getStyle('A2')->getFont()->setName('font2');
164-
$sheet->getStyle('A3')->getFont()->setName('font3');
165-
$sheet->getStyle('B1')->getFont()->setName('font1');
166-
$sheet->getStyle('B2')->getFont()->setName('font2');
167-
$sheet->getCell('A1')->setValue('this is a1');
168-
$sheet->getCell('A2')->setValue('this is a2');
169-
$sheet->getCell('A3')->setValue('this is a3');
170-
$sheet->getCell('B1')->setValue('this is b1');
171-
$sheet->getCell('B2')->setValue('this is b2');
172-
$this->spreadsheet2 = $this->spreadsheet->copy();
173-
$copysheet = $this->spreadsheet2->getActiveSheet();
174-
$copysheet->getStyle('A2')->getFont()->setName('font12');
175-
$copysheet->getCell('A2')->setValue('this was a2');
176-
177-
self::assertSame('font1', $sheet->getStyle('A1')->getFont()->getName());
178-
self::assertSame('font2', $sheet->getStyle('A2')->getFont()->getName());
179-
self::assertSame('font3', $sheet->getStyle('A3')->getFont()->getName());
180-
self::assertSame('font1', $sheet->getStyle('B1')->getFont()->getName());
181-
self::assertSame('font2', $sheet->getStyle('B2')->getFont()->getName());
182-
self::assertSame('this is a1', $sheet->getCell('A1')->getValue());
183-
self::assertSame('this is a2', $sheet->getCell('A2')->getValue());
184-
self::assertSame('this is a3', $sheet->getCell('A3')->getValue());
185-
self::assertSame('this is b1', $sheet->getCell('B1')->getValue());
186-
self::assertSame('this is b2', $sheet->getCell('B2')->getValue());
187-
188-
self::assertSame('font1', $copysheet->getStyle('A1')->getFont()->getName());
189-
self::assertSame('font12', $copysheet->getStyle('A2')->getFont()->getName());
190-
self::assertSame('font3', $copysheet->getStyle('A3')->getFont()->getName());
191-
self::assertSame('font1', $copysheet->getStyle('B1')->getFont()->getName());
192-
self::assertSame('font2', $copysheet->getStyle('B2')->getFont()->getName());
193-
self::assertSame('this is a1', $copysheet->getCell('A1')->getValue());
194-
self::assertSame('this was a2', $copysheet->getCell('A2')->getValue());
195-
self::assertSame('this is a3', $copysheet->getCell('A3')->getValue());
196-
self::assertSame('this is b1', $copysheet->getCell('B1')->getValue());
197-
self::assertSame('this is b2', $copysheet->getCell('B2')->getValue());
198-
}
199-
200-
public function testClone(): void
201-
{
202-
$this->expectException(SSException::class);
203-
$this->expectExceptionMessage('Do not use clone on spreadsheet. Use spreadsheet->copy() instead.');
204-
$this->spreadsheet = new Spreadsheet();
205-
$this->spreadsheet2 = clone $this->spreadsheet;
206-
}
207157
}

0 commit comments

Comments
 (0)