Skip to content

Commit 2661adf

Browse files
authored
Merge pull request #4370 from oleibman/clonespreadsheet
Allow Clone on Spreadsheet
2 parents d813d7e + 51de172 commit 2661adf

File tree

5 files changed

+196
-61
lines changed

5 files changed

+196
-61
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
99

1010
### Added
1111

12-
- Nothing yet.
12+
- Allow Spreadsheet clone. [PR #437-](https://github.com/PHPOffice/PhpSpreadsheet/pull/4370)
1313

1414
### Removed
1515

src/PhpSpreadsheet/Spreadsheet.php

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,11 +1063,78 @@ public function copy(): self
10631063
return unserialize(serialize($this));
10641064
}
10651065

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

10731140
/**

src/PhpSpreadsheet/Worksheet/Worksheet.php

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

79-
private bool $cellCollectionInitialized = true;
80-
8179
/**
8280
* Collection of row dimensions.
8381
*
@@ -355,10 +353,9 @@ public function __construct(?Spreadsheet $parent = null, string $title = 'Worksh
355353
*/
356354
public function disconnectCells(): void
357355
{
358-
if ($this->cellCollectionInitialized) {
356+
if (isset($this->cellCollection)) {
359357
$this->cellCollection->unsetWorksheetCells();
360358
unset($this->cellCollection);
361-
$this->cellCollectionInitialized = false;
362359
}
363360
// detach ourself from the workbook, so that it can then delete this worksheet successfully
364361
$this->parent = null;
@@ -457,7 +454,7 @@ private static function checkSheetTitle(string $sheetTitle): string
457454
*/
458455
public function getCoordinates(bool $sorted = true): array
459456
{
460-
if ($this->cellCollectionInitialized === false) {
457+
if (!isset($this->cellCollection)) {
461458
return [];
462459
}
463460

@@ -829,6 +826,13 @@ public function rebindParent(Spreadsheet $parent): static
829826
return $this;
830827
}
831828

829+
public function setParent(Spreadsheet $parent): self
830+
{
831+
$this->parent = $parent;
832+
833+
return $this;
834+
}
835+
832836
/**
833837
* Get title.
834838
*/
@@ -3515,8 +3519,7 @@ public function isEmptyColumn(string $columnId, int $definitionOfEmptyFlags = 0)
35153519
*/
35163520
public function __clone()
35173521
{
3518-
// @phpstan-ignore-next-line
3519-
foreach ($this as $key => $val) {
3522+
foreach (get_object_vars($this) as $key => $val) {
35203523
if ($key == 'parent') {
35213524
continue;
35223525
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests;
6+
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class SpreadsheetCopyCloneTest extends TestCase
12+
{
13+
private ?Spreadsheet $spreadsheet = null;
14+
15+
private ?Spreadsheet $spreadsheet2 = null;
16+
17+
protected function tearDown(): void
18+
{
19+
if ($this->spreadsheet !== null) {
20+
$this->spreadsheet->disconnectWorksheets();
21+
$this->spreadsheet = null;
22+
}
23+
if ($this->spreadsheet2 !== null) {
24+
$this->spreadsheet2->disconnectWorksheets();
25+
$this->spreadsheet2 = null;
26+
}
27+
}
28+
29+
#[DataProvider('providerCopyClone')]
30+
public function testCopyClone(string $type): void
31+
{
32+
$this->spreadsheet = new Spreadsheet();
33+
$sheet = $this->spreadsheet->getActiveSheet();
34+
$sheet->setTitle('original');
35+
$sheet->getStyle('A1')->getFont()->setName('font1');
36+
$sheet->getStyle('A2')->getFont()->setName('font2');
37+
$sheet->getStyle('A3')->getFont()->setName('font3');
38+
$sheet->getStyle('B1')->getFont()->setName('font1');
39+
$sheet->getStyle('B2')->getFont()->setName('font2');
40+
$sheet->getCell('A1')->setValue('this is a1');
41+
$sheet->getCell('A2')->setValue('this is a2');
42+
$sheet->getCell('A3')->setValue('this is a3');
43+
$sheet->getCell('B1')->setValue('this is b1');
44+
$sheet->getCell('B2')->setValue('this is b2');
45+
self::assertSame('font1', $sheet->getStyle('A1')->getFont()->getName());
46+
$sheet->setSelectedCells('A3');
47+
if ($type === 'copy') {
48+
$this->spreadsheet2 = $this->spreadsheet->copy();
49+
} else {
50+
$this->spreadsheet2 = clone $this->spreadsheet;
51+
}
52+
self::assertSame('A3', $sheet->getSelectedCells());
53+
$copysheet = $this->spreadsheet2->getActiveSheet();
54+
self::assertSame('A3', $copysheet->getSelectedCells());
55+
self::assertSame('original', $copysheet->getTitle());
56+
$copysheet->setTitle('unoriginal');
57+
self::assertSame('original', $sheet->getTitle());
58+
self::assertSame('unoriginal', $copysheet->getTitle());
59+
$copysheet->getStyle('A2')->getFont()->setName('font12');
60+
$copysheet->getCell('A2')->setValue('this was a2');
61+
62+
self::assertSame('font1', $sheet->getStyle('A1')->getFont()->getName());
63+
self::assertSame('font2', $sheet->getStyle('A2')->getFont()->getName());
64+
self::assertSame('font3', $sheet->getStyle('A3')->getFont()->getName());
65+
self::assertSame('font1', $sheet->getStyle('B1')->getFont()->getName());
66+
self::assertSame('font2', $sheet->getStyle('B2')->getFont()->getName());
67+
self::assertSame('this is a1', $sheet->getCell('A1')->getValue());
68+
self::assertSame('this is a2', $sheet->getCell('A2')->getValue());
69+
self::assertSame('this is a3', $sheet->getCell('A3')->getValue());
70+
self::assertSame('this is b1', $sheet->getCell('B1')->getValue());
71+
self::assertSame('this is b2', $sheet->getCell('B2')->getValue());
72+
73+
self::assertSame('font1', $copysheet->getStyle('A1')->getFont()->getName());
74+
self::assertSame('font12', $copysheet->getStyle('A2')->getFont()->getName());
75+
self::assertSame('font3', $copysheet->getStyle('A3')->getFont()->getName());
76+
self::assertSame('font1', $copysheet->getStyle('B1')->getFont()->getName());
77+
self::assertSame('font2', $copysheet->getStyle('B2')->getFont()->getName());
78+
self::assertSame('this is a1', $copysheet->getCell('A1')->getValue());
79+
self::assertSame('this was a2', $copysheet->getCell('A2')->getValue());
80+
self::assertSame('this is a3', $copysheet->getCell('A3')->getValue());
81+
self::assertSame('this is b1', $copysheet->getCell('B1')->getValue());
82+
self::assertSame('this is b2', $copysheet->getCell('B2')->getValue());
83+
}
84+
85+
#[DataProvider('providerCopyClone')]
86+
public function testCopyCloneActiveSheet(string $type): void
87+
{
88+
$this->spreadsheet = new Spreadsheet();
89+
$sheet0 = $this->spreadsheet->getActiveSheet();
90+
$sheet1 = $this->spreadsheet->createSheet();
91+
$sheet2 = $this->spreadsheet->createSheet();
92+
$sheet3 = $this->spreadsheet->createSheet();
93+
$sheet4 = $this->spreadsheet->createSheet();
94+
$sheet0->getStyle('B1')->getFont()->setName('whatever');
95+
$sheet1->getStyle('B1')->getFont()->setName('whatever');
96+
$sheet2->getStyle('B1')->getFont()->setName('whatever');
97+
$sheet3->getStyle('B1')->getFont()->setName('whatever');
98+
$sheet4->getStyle('B1')->getFont()->setName('whatever');
99+
$this->spreadsheet->setActiveSheetIndex(2);
100+
if ($type === 'copy') {
101+
$this->spreadsheet2 = $this->spreadsheet->copy();
102+
} else {
103+
$this->spreadsheet2 = clone $this->spreadsheet;
104+
}
105+
self::assertSame(2, $this->spreadsheet2->getActiveSheetIndex());
106+
}
107+
108+
public static function providerCopyClone(): array
109+
{
110+
return [
111+
['copy'],
112+
['clone'],
113+
];
114+
}
115+
}

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)