mirror of
https://github.com/PHPOffice/PhpSpreadsheet.git
synced 2026-09-05 23:18:49 +00:00
210 lines
7.4 KiB
PHP
210 lines
7.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace PhpOffice\PhpSpreadsheetTests\Calculation;
|
|
|
|
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class FormulaTokenCacheTest extends TestCase
|
|
{
|
|
private Spreadsheet $spreadsheet;
|
|
|
|
private Calculation $calculation;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->spreadsheet = new Spreadsheet();
|
|
$this->calculation = Calculation::getInstance($this->spreadsheet);
|
|
$this->calculation->setFormulaTokenCacheMaxSize(1000);
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
$this->calculation->clearFormulaTokenCache();
|
|
$this->spreadsheet->disconnectWorksheets();
|
|
}
|
|
|
|
public function testCachedResultMatchesUncachedResult(): void
|
|
{
|
|
$formula = '=1+2';
|
|
|
|
// First call: uncached (cold cache)
|
|
$firstResult = $this->calculation->parseFormula($formula);
|
|
|
|
// Second call: should come from cache
|
|
$secondResult = $this->calculation->parseFormula($formula);
|
|
|
|
self::assertSame($firstResult, $secondResult);
|
|
}
|
|
|
|
public function testIdenticalFormulasReuseCache(): void
|
|
{
|
|
$formula = '=SUM(A1:B2)';
|
|
|
|
self::assertSame(0, $this->calculation->getFormulaTokenCacheSize());
|
|
|
|
$this->calculation->parseFormula($formula);
|
|
self::assertSame(1, $this->calculation->getFormulaTokenCacheSize());
|
|
|
|
// Parsing the same formula again should not increase cache size
|
|
$this->calculation->parseFormula($formula);
|
|
self::assertSame(1, $this->calculation->getFormulaTokenCacheSize());
|
|
}
|
|
|
|
public function testCacheCanBeCleared(): void
|
|
{
|
|
$this->calculation->parseFormula('=1+2');
|
|
$this->calculation->parseFormula('=3*4');
|
|
self::assertSame(2, $this->calculation->getFormulaTokenCacheSize());
|
|
|
|
$this->calculation->clearFormulaTokenCache();
|
|
self::assertSame(0, $this->calculation->getFormulaTokenCacheSize());
|
|
}
|
|
|
|
public function testDifferentFormulasGetSeparateCacheEntries(): void
|
|
{
|
|
$formula1 = '=1+2';
|
|
$formula2 = '=3*4';
|
|
$formula3 = '=SUM(A1:A10)';
|
|
|
|
$result1 = $this->calculation->parseFormula($formula1);
|
|
$result2 = $this->calculation->parseFormula($formula2);
|
|
$result3 = $this->calculation->parseFormula($formula3);
|
|
|
|
self::assertSame(3, $this->calculation->getFormulaTokenCacheSize());
|
|
|
|
// Results should be different for different formulas
|
|
self::assertNotEquals($result1, $result2);
|
|
self::assertNotEquals($result1, $result3);
|
|
self::assertNotEquals($result2, $result3);
|
|
|
|
// Each formula still returns correct cached result
|
|
self::assertSame($result1, $this->calculation->parseFormula($formula1));
|
|
self::assertSame($result2, $this->calculation->parseFormula($formula2));
|
|
self::assertSame($result3, $this->calculation->parseFormula($formula3));
|
|
|
|
// Cache size should not have increased
|
|
self::assertSame(3, $this->calculation->getFormulaTokenCacheSize());
|
|
}
|
|
|
|
public function testCacheWorksAcrossMultipleCalculationCalls(): void
|
|
{
|
|
$sheet = $this->spreadsheet->getActiveSheet();
|
|
$sheet->setCellValue('A1', 10);
|
|
$sheet->setCellValue('A2', 20);
|
|
$sheet->setCellValue('B1', '=A1+A2');
|
|
$sheet->setCellValue('B2', '=A1+A2');
|
|
|
|
$this->calculation->clearFormulaTokenCache();
|
|
|
|
// Parse the formula used in both cells
|
|
$formula = '=A1+A2';
|
|
$firstResult = $this->calculation->parseFormula($formula);
|
|
self::assertSame(1, $this->calculation->getFormulaTokenCacheSize());
|
|
|
|
// Calculating different cells with the same formula structure
|
|
$valueB1 = $sheet->getCell('B1')->getCalculatedValue();
|
|
$valueB2 = $sheet->getCell('B2')->getCalculatedValue();
|
|
|
|
self::assertSame(30, $valueB1);
|
|
self::assertSame(30, $valueB2);
|
|
|
|
// Cache should still return the same parsed result for the formula
|
|
$cachedResult = $this->calculation->parseFormula($formula);
|
|
self::assertSame($firstResult, $cachedResult);
|
|
}
|
|
|
|
public function testCacheHandlesComplexFormulas(): void
|
|
{
|
|
$formulas = [
|
|
'=IF(A1>0,A1*2,0)',
|
|
'=VLOOKUP(A1,B1:C10,2,FALSE)',
|
|
'="Hello"&" "&"World"',
|
|
'=SUM(A1:A10)/COUNT(A1:A10)',
|
|
'=-A1%',
|
|
];
|
|
|
|
foreach ($formulas as $formula) {
|
|
$result = $this->calculation->parseFormula($formula);
|
|
self::assertIsArray($result, "Formula {$formula} should parse to an array");
|
|
self::assertNotEmpty($result, "Formula {$formula} should produce non-empty tokens");
|
|
}
|
|
|
|
self::assertSame(count($formulas), $this->calculation->getFormulaTokenCacheSize());
|
|
|
|
// Verify each formula returns the same result on subsequent calls
|
|
foreach ($formulas as $formula) {
|
|
$first = $this->calculation->parseFormula($formula);
|
|
$second = $this->calculation->parseFormula($formula);
|
|
self::assertSame($first, $second, "Cached result should match for {$formula}");
|
|
}
|
|
|
|
// Cache size should remain unchanged
|
|
self::assertSame(count($formulas), $this->calculation->getFormulaTokenCacheSize());
|
|
}
|
|
|
|
public function testCacheEvictsWhenFull(): void
|
|
{
|
|
$this->calculation->setFormulaTokenCacheMaxSize(100);
|
|
|
|
// Fill the cache beyond its max size
|
|
for ($i = 0; $i < 101; ++$i) {
|
|
$this->calculation->parseFormula("={$i}+1");
|
|
}
|
|
|
|
// The cache should have been cleared and then started re-filling
|
|
// After clearing at 100, the 101st entry was added, so size should be 1
|
|
self::assertSame(1, $this->calculation->getFormulaTokenCacheSize());
|
|
}
|
|
|
|
public function testNonFormulaStringsReturnEmptyArrayAndAreNotCached(): void
|
|
{
|
|
$result = $this->calculation->parseFormula('not a formula');
|
|
self::assertSame([], $result);
|
|
|
|
// Non-formulas (no = prefix) return early before caching
|
|
self::assertSame(0, $this->calculation->getFormulaTokenCacheSize());
|
|
}
|
|
|
|
public function testCacheDisabledByDefault(): void
|
|
{
|
|
$spreadsheet = new Spreadsheet();
|
|
$calculation = Calculation::getInstance($spreadsheet);
|
|
|
|
// Default max size is 0 (disabled)
|
|
self::assertSame(0, $calculation->getFormulaTokenCacheMaxSize());
|
|
|
|
$calculation->parseFormula('=1+2');
|
|
self::assertSame(0, $calculation->getFormulaTokenCacheSize());
|
|
|
|
$spreadsheet->disconnectWorksheets();
|
|
}
|
|
|
|
public function testSetMaxSizeEnablesCache(): void
|
|
{
|
|
$spreadsheet = new Spreadsheet();
|
|
$calculation = Calculation::getInstance($spreadsheet);
|
|
|
|
$calculation->setFormulaTokenCacheMaxSize(500);
|
|
self::assertSame(500, $calculation->getFormulaTokenCacheMaxSize());
|
|
|
|
$calculation->parseFormula('=1+2');
|
|
self::assertSame(1, $calculation->getFormulaTokenCacheSize());
|
|
|
|
$spreadsheet->disconnectWorksheets();
|
|
}
|
|
|
|
public function testSetMaxSizeToZeroClearsCache(): void
|
|
{
|
|
$this->calculation->parseFormula('=1+2');
|
|
self::assertSame(1, $this->calculation->getFormulaTokenCacheSize());
|
|
|
|
$this->calculation->setFormulaTokenCacheMaxSize(0);
|
|
self::assertSame(0, $this->calculation->getFormulaTokenCacheSize());
|
|
self::assertSame(0, $this->calculation->getFormulaTokenCacheMaxSize());
|
|
}
|
|
}
|