Skip to content

Commit 18cfe5b

Browse files
authored
Merge pull request #4326 from oleibman/serialize
Allow Spreadsheet Serialization
2 parents a432882 + e58dd89 commit 18cfe5b

File tree

8 files changed

+118
-58
lines changed

8 files changed

+118
-58
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
1919
### Added
2020

2121
- Pdf Charts and Drawings. [Discussion #4129](https://github.com/PHPOffice/PhpSpreadsheet/discussions/4129) [Discussion #4168](https://github.com/PHPOffice/PhpSpreadsheet/discussions/4168) [PR #4327](https://github.com/PHPOffice/PhpSpreadsheet/pull/4327)
22+
- Allow spreadsheet serialization. [Discussion #4324](https://github.com/PHPOffice/PhpSpreadsheet/discussions/4324) [Issue #1741](https://github.com/PHPOffice/PhpSpreadsheet/issues/1741) [Issue #1757](https://github.com/PHPOffice/PhpSpreadsheet/issues/1757) [PR #4326](https://github.com/PHPOffice/PhpSpreadsheet/pull/4326)
2223

2324
### Removed
2425

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 8 additions & 11 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
@@ -5745,11 +5739,14 @@ private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksh
57455739
$recursiveCalculationCellAddress = $recursiveCalculationCell->getCoordinate();
57465740

57475741
// Adjust relative references in ranges and formulae so that we execute the calculation for the correct rows and columns
5748-
$definedNameValue = self::$referenceHelper->updateFormulaReferencesAnyWorksheet(
5749-
$definedNameValue,
5750-
Coordinate::columnIndexFromString($cell->getColumn()) - 1,
5751-
$cell->getRow() - 1
5752-
);
5742+
$definedNameValue = ReferenceHelper::getInstance()
5743+
->updateFormulaReferencesAnyWorksheet(
5744+
$definedNameValue,
5745+
Coordinate::columnIndexFromString(
5746+
$cell->getColumn()
5747+
) - 1,
5748+
$cell->getRow() - 1
5749+
);
57535750

57545751
$this->debugLog->writeDebugLog('Value adjusted for relative references is %s', $definedNameValue);
57555752

src/PhpSpreadsheet/Cell/Cell.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class Cell implements Stringable
6363
*
6464
* @var null|array<string, string>
6565
*/
66-
private mixed $formulaAttributes = null;
66+
private ?array $formulaAttributes = null;
6767

6868
private IgnoredErrors $ignoredErrors;
6969

src/PhpSpreadsheet/Spreadsheet.php

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,12 @@
77
use PhpOffice\PhpSpreadsheet\Cell\IValueBinder;
88
use PhpOffice\PhpSpreadsheet\Document\Properties;
99
use PhpOffice\PhpSpreadsheet\Document\Security;
10-
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
1110
use PhpOffice\PhpSpreadsheet\Shared\Date;
12-
use PhpOffice\PhpSpreadsheet\Shared\File;
1311
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
1412
use PhpOffice\PhpSpreadsheet\Style\Style;
1513
use PhpOffice\PhpSpreadsheet\Worksheet\Iterator;
1614
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
1715
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
18-
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
1916

2017
class Spreadsheet implements JsonSerializable
2118
{
@@ -1063,17 +1060,7 @@ public function getWorksheetIterator(): Iterator
10631060
*/
10641061
public function copy(): self
10651062
{
1066-
$filename = File::temporaryFilename();
1067-
$writer = new XlsxWriter($this);
1068-
$writer->setIncludeCharts(true);
1069-
$writer->save($filename);
1070-
1071-
$reader = new XlsxReader();
1072-
$reader->setIncludeCharts(true);
1073-
$reloadedSpreadsheet = $reader->load($filename);
1074-
unlink($filename);
1075-
1076-
return $reloadedSpreadsheet;
1063+
return unserialize(serialize($this));
10771064
}
10781065

10791066
public function __clone()
@@ -1542,14 +1529,6 @@ public function reevaluateAutoFilters(bool $resetToMax): void
15421529
}
15431530
}
15441531

1545-
/**
1546-
* @throws Exception
1547-
*/
1548-
public function __serialize(): array
1549-
{
1550-
throw new Exception('Spreadsheet objects cannot be serialized');
1551-
}
1552-
15531532
/**
15541533
* @throws Exception
15551534
*/

src/PhpSpreadsheet/Worksheet/Worksheet.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,6 @@ public function __destruct()
377377
public function __wakeup(): void
378378
{
379379
$this->hash = spl_object_id($this);
380-
$this->parent = null;
381380
}
382381

383382
/**
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests;
6+
7+
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
8+
use PhpOffice\PhpSpreadsheet\Helper\Sample;
9+
use PhpOffice\PhpSpreadsheet\NamedRange;
10+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
11+
use PHPUnit\Framework\Attributes;
12+
use PHPUnit\Framework\TestCase;
13+
14+
class SpreadsheetSerializeTest extends TestCase
15+
{
16+
private ?Spreadsheet $spreadsheet = null;
17+
18+
protected function tearDown(): void
19+
{
20+
if ($this->spreadsheet !== null) {
21+
$this->spreadsheet->disconnectWorksheets();
22+
$this->spreadsheet = null;
23+
}
24+
}
25+
26+
public function testSerialize(): void
27+
{
28+
$this->spreadsheet = new Spreadsheet();
29+
$sheet = $this->spreadsheet->getActiveSheet();
30+
$sheet->getCell('A1')->setValue(10);
31+
32+
$serialized = serialize($this->spreadsheet);
33+
$newSpreadsheet = unserialize($serialized);
34+
self::assertInstanceOf(Spreadsheet::class, $newSpreadsheet);
35+
self::assertNotSame($this->spreadsheet, $newSpreadsheet);
36+
$newSheet = $newSpreadsheet->getActiveSheet();
37+
self::assertSame(10, $newSheet->getCell('A1')->getValue());
38+
$newSpreadsheet->disconnectWorksheets();
39+
}
40+
41+
public function testNotJsonEncodable(): void
42+
{
43+
$this->spreadsheet = new Spreadsheet();
44+
45+
$this->expectException(SpreadsheetException::class);
46+
$this->expectExceptionMessage('Spreadsheet objects cannot be json encoded');
47+
json_encode($this->spreadsheet);
48+
}
49+
50+
/**
51+
* These tests are a bit weird.
52+
* If prepareSerialize and readSerialize are run in the same
53+
* process, the latter's assertions will always succeed.
54+
* So to demonstrate that the
55+
* problem is solved, they need to run in separate processes.
56+
* But then they can't share the file name. So we need to send
57+
* the file to a semi-hard-coded destination.
58+
*/
59+
private static function getTempFileName(): string
60+
{
61+
$helper = new Sample();
62+
63+
return $helper->getTemporaryFolder() . '/spreadsheet.serialize.test.txt';
64+
}
65+
66+
public function testPrepareSerialize(): void
67+
{
68+
$this->spreadsheet = new Spreadsheet();
69+
$sheet = $this->spreadsheet->getActiveSheet();
70+
$this->spreadsheet->addNamedRange(new NamedRange('summedcells', $sheet, '$A$1:$A$5'));
71+
$sheet->setCellValue('A1', 1);
72+
$sheet->setCellValue('A2', 2);
73+
$sheet->setCellValue('A3', 3);
74+
$sheet->setCellValue('A4', 4);
75+
$sheet->setCellValue('A5', 5);
76+
$sheet->setCellValue('C1', '=SUM(summedcells)');
77+
$ser = serialize($this->spreadsheet);
78+
$this->spreadsheet->disconnectWorksheets();
79+
$outputFileName = self::getTempFileName();
80+
self::assertNotFalse(
81+
file_put_contents($outputFileName, $ser)
82+
);
83+
}
84+
85+
#[Attributes\RunInSeparateProcess]
86+
public function testReadSerialize(): void
87+
{
88+
$inputFileName = self::getTempFileName();
89+
$ser = (string) file_get_contents($inputFileName);
90+
unlink($inputFileName);
91+
$this->spreadsheet = unserialize($ser);
92+
$sheet = $this->spreadsheet->getActiveSheet();
93+
self::assertSame('=SUM(summedcells)', $sheet->getCell('C1')->getValue());
94+
self::assertSame(15, $sheet->getCell('C1')->getCalculatedValue());
95+
}
96+
}

tests/PhpSpreadsheetTests/SpreadsheetTest.php

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -291,22 +291,4 @@ public function testAddExternalRowDimensionStyles(): void
291291
self::assertEquals($countXfs + $index, $sheet3->getCell('A2')->getXfIndex());
292292
self::assertEquals($countXfs + $index, $sheet3->getRowDimension(2)->getXfIndex());
293293
}
294-
295-
public function testNotSerializable(): void
296-
{
297-
$this->spreadsheet = new Spreadsheet();
298-
299-
$this->expectException(Exception::class);
300-
$this->expectExceptionMessage('Spreadsheet objects cannot be serialized');
301-
serialize($this->spreadsheet);
302-
}
303-
304-
public function testNotJsonEncodable(): void
305-
{
306-
$this->spreadsheet = new Spreadsheet();
307-
308-
$this->expectException(Exception::class);
309-
$this->expectExceptionMessage('Spreadsheet objects cannot be json encoded');
310-
json_encode($this->spreadsheet);
311-
}
312294
}

tests/PhpSpreadsheetTests/Worksheet/CloneTest.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,27 @@ public function testGetCloneIndex(): void
4444

4545
public function testSerialize1(): void
4646
{
47-
// If worksheet attached to spreadsheet, can't serialize it.
48-
$this->expectException(SpreadsheetException::class);
49-
$this->expectExceptionMessage('cannot be serialized');
5047
$spreadsheet = new Spreadsheet();
5148
$sheet1 = $spreadsheet->getActiveSheet();
52-
serialize($sheet1);
49+
$sheet1->getCell('A1')->setValue(10);
50+
$serialized = serialize($sheet1);
51+
$newSheet = unserialize($serialized);
52+
self::assertInstanceOf(Worksheet::class, $newSheet);
53+
self::assertSame(10, $newSheet->getCell('A1')->getValue());
54+
self::assertNotEquals($newSheet->getHashInt(), $sheet1->getHashInt());
55+
self::assertNotNull($newSheet->getParent());
56+
self::assertNotSame($newSheet->getParent(), $sheet1->getParent());
57+
$newSheet->getParent()->disconnectWorksheets();
58+
$spreadsheet->disconnectWorksheets();
5359
}
5460

5561
public function testSerialize2(): void
5662
{
5763
$sheet1 = new Worksheet();
5864
$sheet1->getCell('A1')->setValue(10);
5965
$serialized = serialize($sheet1);
60-
/** @var Worksheet */
6166
$newSheet = unserialize($serialized);
67+
self::assertInstanceOf(Worksheet::class, $newSheet);
6268
self::assertSame(10, $newSheet->getCell('A1')->getValue());
6369
self::assertNotEquals($newSheet->getHashInt(), $sheet1->getHashInt());
6470
}

0 commit comments

Comments
 (0)