Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Spreadsheet Serialization #4326

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions src/PhpSpreadsheet/Calculation/Calculation.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,6 @@ class Calculation
*/
public ?string $formulaError = null;

/**
* Reference Helper.
*/
private static ReferenceHelper $referenceHelper;

/**
* An array of the nested cell references accessed by the calculation engine, used for the debug log.
*/
Expand Down Expand Up @@ -2890,7 +2885,6 @@ public function __construct(?Spreadsheet $spreadsheet = null)
$this->cyclicReferenceStack = new CyclicReferenceStack();
$this->debugLog = new Logger($this->cyclicReferenceStack);
$this->branchPruner = new BranchPruner($this->branchPruningEnabled);
self::$referenceHelper = ReferenceHelper::getInstance();
}

private static function loadLocales(): void
Expand Down Expand Up @@ -5732,11 +5726,14 @@ private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksh
$recursiveCalculationCellAddress = $recursiveCalculationCell->getCoordinate();

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

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

Expand Down
2 changes: 1 addition & 1 deletion src/PhpSpreadsheet/Cell/Cell.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class Cell implements Stringable
*
* @var null|array<string, string>
*/
private mixed $formulaAttributes = null;
private ?array $formulaAttributes = null;

private IgnoredErrors $ignoredErrors;

Expand Down
23 changes: 1 addition & 22 deletions src/PhpSpreadsheet/Spreadsheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@
use PhpOffice\PhpSpreadsheet\Cell\IValueBinder;
use PhpOffice\PhpSpreadsheet\Document\Properties;
use PhpOffice\PhpSpreadsheet\Document\Security;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PhpOffice\PhpSpreadsheet\Worksheet\Iterator;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;

class Spreadsheet implements JsonSerializable
{
Expand Down Expand Up @@ -1042,17 +1039,7 @@ public function getWorksheetIterator(): Iterator
*/
public function copy(): self
{
$filename = File::temporaryFilename();
$writer = new XlsxWriter($this);
$writer->setIncludeCharts(true);
$writer->save($filename);

$reader = new XlsxReader();
$reader->setIncludeCharts(true);
$reloadedSpreadsheet = $reader->load($filename);
unlink($filename);

return $reloadedSpreadsheet;
return unserialize(serialize($this));
}

public function __clone()
Expand Down Expand Up @@ -1516,14 +1503,6 @@ public function reevaluateAutoFilters(bool $resetToMax): void
}
}

/**
* @throws Exception
*/
public function __serialize(): array
{
throw new Exception('Spreadsheet objects cannot be serialized');
}

/**
* @throws Exception
*/
Expand Down
1 change: 0 additions & 1 deletion src/PhpSpreadsheet/Worksheet/Worksheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,6 @@ public function __destruct()
public function __wakeup(): void
{
$this->hash = spl_object_id($this);
$this->parent = null;
}

/**
Expand Down
96 changes: 96 additions & 0 deletions tests/PhpSpreadsheetTests/SpreadsheetSerializeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpSpreadsheetTests;

use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
use PhpOffice\PhpSpreadsheet\Helper\Sample;
use PhpOffice\PhpSpreadsheet\NamedRange;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PHPUnit\Framework\Attributes;
use PHPUnit\Framework\TestCase;

class SpreadsheetSerializeTest extends TestCase
{
private ?Spreadsheet $spreadsheet = null;

protected function tearDown(): void
{
if ($this->spreadsheet !== null) {
$this->spreadsheet->disconnectWorksheets();
$this->spreadsheet = null;
}
}

public function testSerialize(): void
{
$this->spreadsheet = new Spreadsheet();
$sheet = $this->spreadsheet->getActiveSheet();
$sheet->getCell('A1')->setValue(10);

$serialized = serialize($this->spreadsheet);
$newSpreadsheet = unserialize($serialized);
self::assertInstanceOf(Spreadsheet::class, $newSpreadsheet);
self::assertNotSame($this->spreadsheet, $newSpreadsheet);
$newSheet = $newSpreadsheet->getActiveSheet();
self::assertSame(10, $newSheet->getCell('A1')->getValue());
$newSpreadsheet->disconnectWorksheets();
}

public function testNotJsonEncodable(): void
{
$this->spreadsheet = new Spreadsheet();

$this->expectException(SpreadsheetException::class);
$this->expectExceptionMessage('Spreadsheet objects cannot be json encoded');
json_encode($this->spreadsheet);
}

/**
* These tests are a bit weird.
* If prepareSerialize and readSerialize are run in the same
* process, the latter's assertions will always succeed.
* So to demonstrate that the
* problem is solved, they need to run in separate processes.
* But then they can't share the file name. So we need to send
* the file to a semi-hard-coded destination.
*/
private static function getTempFileName(): string
{
$helper = new Sample();

return $helper->getTemporaryFolder() . '/spreadsheet.serialize.test.txt';
}

public function testPrepareSerialize(): void
{
$this->spreadsheet = new Spreadsheet();
$sheet = $this->spreadsheet->getActiveSheet();
$this->spreadsheet->addNamedRange(new NamedRange('summedcells', $sheet, '$A$1:$A$5'));
$sheet->setCellValue('A1', 1);
$sheet->setCellValue('A2', 2);
$sheet->setCellValue('A3', 3);
$sheet->setCellValue('A4', 4);
$sheet->setCellValue('A5', 5);
$sheet->setCellValue('C1', '=SUM(summedcells)');
$ser = serialize($this->spreadsheet);
$this->spreadsheet->disconnectWorksheets();
$outputFileName = self::getTempFileName();
self::assertNotFalse(
file_put_contents($outputFileName, $ser)
);
}

#[Attributes\RunInSeparateProcess]
public function testReadSerialize(): void
{
$inputFileName = self::getTempFileName();
$ser = (string) file_get_contents($inputFileName);
unlink($inputFileName);
$this->spreadsheet = unserialize($ser);
$sheet = $this->spreadsheet->getActiveSheet();
self::assertSame('=SUM(summedcells)', $sheet->getCell('C1')->getValue());
self::assertSame(15, $sheet->getCell('C1')->getCalculatedValue());
}
}
18 changes: 0 additions & 18 deletions tests/PhpSpreadsheetTests/SpreadsheetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,22 +291,4 @@ public function testAddExternalRowDimensionStyles(): void
self::assertEquals($countXfs + $index, $sheet3->getCell('A2')->getXfIndex());
self::assertEquals($countXfs + $index, $sheet3->getRowDimension(2)->getXfIndex());
}

public function testNotSerializable(): void
{
$this->spreadsheet = new Spreadsheet();

$this->expectException(Exception::class);
$this->expectExceptionMessage('Spreadsheet objects cannot be serialized');
serialize($this->spreadsheet);
}

public function testNotJsonEncodable(): void
{
$this->spreadsheet = new Spreadsheet();

$this->expectException(Exception::class);
$this->expectExceptionMessage('Spreadsheet objects cannot be json encoded');
json_encode($this->spreadsheet);
}
}
16 changes: 11 additions & 5 deletions tests/PhpSpreadsheetTests/Worksheet/CloneTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,27 @@ public function testGetCloneIndex(): void

public function testSerialize1(): void
{
// If worksheet attached to spreadsheet, can't serialize it.
$this->expectException(SpreadsheetException::class);
$this->expectExceptionMessage('cannot be serialized');
$spreadsheet = new Spreadsheet();
$sheet1 = $spreadsheet->getActiveSheet();
serialize($sheet1);
$sheet1->getCell('A1')->setValue(10);
$serialized = serialize($sheet1);
$newSheet = unserialize($serialized);
self::assertInstanceOf(Worksheet::class, $newSheet);
self::assertSame(10, $newSheet->getCell('A1')->getValue());
self::assertNotEquals($newSheet->getHashInt(), $sheet1->getHashInt());
self::assertNotNull($newSheet->getParent());
self::assertNotSame($newSheet->getParent(), $sheet1->getParent());
$newSheet->getParent()->disconnectWorksheets();
$spreadsheet->disconnectWorksheets();
}

public function testSerialize2(): void
{
$sheet1 = new Worksheet();
$sheet1->getCell('A1')->setValue(10);
$serialized = serialize($sheet1);
/** @var Worksheet */
$newSheet = unserialize($serialized);
self::assertInstanceOf(Worksheet::class, $newSheet);
self::assertSame(10, $newSheet->getCell('A1')->getValue());
self::assertNotEquals($newSheet->getHashInt(), $sheet1->getHashInt());
}
Expand Down
Loading