From c319f893d5170c0ee3b9a7c6cdcd86a6b0a43cdf Mon Sep 17 00:00:00 2001 From: kemo Date: Thu, 26 Mar 2026 09:23:26 +0100 Subject: [PATCH] Refactor formula token cache to instance properties, opt-in by default --- phpunit.xml.dist | 13 +- .../Calculation/Calculation.php | 59 +++++-- ...php => FormulaTokenCacheBenchmarkTest.php} | 111 +++++------- .../Calculation/FormulaTokenCacheTest.php | 159 +++++++++--------- 4 files changed, 180 insertions(+), 162 deletions(-) rename tests/Benchmark/{FormulaTokenCacheBenchmark.php => FormulaTokenCacheBenchmarkTest.php} (60%) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d3e845c1e..5c9ef847d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,18 @@ - + - - ./tests/PhpSpreadsheetTests - + + + ./tests/PhpSpreadsheetTests + + + ./tests/Benchmark + + ./src diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 30cc231b5..f767dc107 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -92,15 +92,16 @@ class Calculation extends CalculationLocale /** * Maximum number of entries in the formula token cache. + * Default 0 (disabled). Set via setFormulaTokenCacheMaxSize() to enable. */ - private static int $formulaTokenCacheMaxSize = 1000; + private int $formulaTokenCacheMaxSize = 0; /** * Cache of parsed formula tokens, keyed by the raw formula string. * * @var array|bool> */ - private static array $formulaTokenCache = []; + private array $formulaTokenCache = []; private BranchPruner $branchPruner; @@ -253,7 +254,7 @@ class Calculation extends CalculationLocale { $this->clearCalculationCache(); $this->branchPruner->clearBranchStore(); - self::$formulaTokenCache = []; + $this->formulaTokenCache = []; } /** @@ -380,19 +381,41 @@ class Calculation extends CalculationLocale } /** - * Clear the static formula token cache. + * Clear the formula token cache. */ - public static function clearFormulaTokenCache(): void + public function clearFormulaTokenCache(): void { - self::$formulaTokenCache = []; + $this->formulaTokenCache = []; } /** * Get the current number of entries in the formula token cache. */ - public static function getFormulaTokenCacheSize(): int + public function getFormulaTokenCacheSize(): int { - return count(self::$formulaTokenCache); + return count($this->formulaTokenCache); + } + + /** + * Set the maximum number of entries in the formula token cache. + * Set to 0 to disable caching (default), or a positive integer to enable. + */ + public function setFormulaTokenCacheMaxSize(int $size): self + { + $this->formulaTokenCacheMaxSize = max(0, $size); + if ($this->formulaTokenCacheMaxSize === 0) { + $this->formulaTokenCache = []; + } + + return $this; + } + + /** + * Get the maximum number of entries allowed in the formula token cache. + */ + public function getFormulaTokenCacheMaxSize(): int + { + return $this->formulaTokenCacheMaxSize; } /** @@ -588,9 +611,9 @@ class Calculation extends CalculationLocale */ public function parseFormula(string $formula): array|bool { - // Check the formula token cache first - if (isset(self::$formulaTokenCache[$formula])) { - return self::$formulaTokenCache[$formula]; + // Check the formula token cache first (only when caching is enabled) + if ($this->formulaTokenCacheMaxSize > 0 && isset($this->formulaTokenCache[$formula])) { + return $this->formulaTokenCache[$formula]; } $originalFormula = $formula; @@ -613,13 +636,15 @@ class Calculation extends CalculationLocale // Parse the formula and return the token stack $result = $this->internalParseFormula($formula); - // Cache the result (clear cache if it exceeds the maximum size) - if (count(self::$formulaTokenCache) >= self::$formulaTokenCacheMaxSize) { - self::$formulaTokenCache = []; + // Cache the result when caching is enabled (clear cache if it exceeds the maximum size) + if ($this->formulaTokenCacheMaxSize > 0) { + if (count($this->formulaTokenCache) >= $this->formulaTokenCacheMaxSize) { + $this->formulaTokenCache = []; + } + // Cache key is the original formula string (before ANCHORARRAY transformation) + // to ensure consistent lookup regardless of internal transformations. + $this->formulaTokenCache[$originalFormula] = $result; } - // Cache key is the original formula string (before ANCHORARRAY transformation) - // to ensure consistent lookup regardless of internal transformations. - self::$formulaTokenCache[$originalFormula] = $result; return $result; } diff --git a/tests/Benchmark/FormulaTokenCacheBenchmark.php b/tests/Benchmark/FormulaTokenCacheBenchmarkTest.php similarity index 60% rename from tests/Benchmark/FormulaTokenCacheBenchmark.php rename to tests/Benchmark/FormulaTokenCacheBenchmarkTest.php index f203d67e8..fca435345 100644 --- a/tests/Benchmark/FormulaTokenCacheBenchmark.php +++ b/tests/Benchmark/FormulaTokenCacheBenchmarkTest.php @@ -14,11 +14,13 @@ use PHPUnit\Framework\TestCase; * These tests demonstrate the performance benefit of caching parsed formula * tokens so that identical formula strings are not re-parsed on every evaluation. * - * Run with: vendor/bin/phpunit --group benchmark --stderr + * Run with: vendor/bin/phpunit --testsuite Benchmark --filter FormulaTokenCacheBenchmark --stderr */ #[\PHPUnit\Framework\Attributes\Group('benchmark')] -class FormulaTokenCacheBenchmark extends TestCase +class FormulaTokenCacheBenchmarkTest extends TestCase { + private const CACHE_SIZE = 1000; + /** Formula patterns used across benchmarks. */ private const FORMULA_PATTERNS = [ '=A%d+B%d', @@ -33,75 +35,65 @@ class FormulaTokenCacheBenchmark extends TestCase '=IFERROR(A%d/B%d,0)', ]; - protected function tearDown(): void - { - Calculation::clearFormulaTokenCache(); - } - /** - * Benchmark: calculate 1000 formula cells with cache enabled (warm) vs - * with cache cleared (cold, forces re-parsing). + * Benchmark: parse 1000 spreadsheet-like formulas with cache enabled vs disabled. */ - public function testSpreadsheetCalculationCacheVsNocache(): void + public function testParseFormulaCacheEnabledVsDisabled(): void { $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); + $calculation = Calculation::getInstance($spreadsheet); $cellCount = 1000; - // Populate source data - for ($row = 1; $row <= $cellCount; ++$row) { - $sheet->setCellValue("A{$row}", $row); - $sheet->setCellValue("B{$row}", $row * 2); - $sheet->setCellValue("C{$row}", $row * 3); - $sheet->setCellValue("D{$row}", max($row, 1)); - } - - // Assign formulas in column E using repeated patterns. - // We cycle through a small set of row references (1-100) so many cells - // share the same formula string, maximising token cache hits. + // Build a realistic set of formulas that a spreadsheet might contain $patternCount = count(self::FORMULA_PATTERNS); + $formulas = []; for ($row = 1; $row <= $cellCount; ++$row) { $pattern = self::FORMULA_PATTERNS[$row % $patternCount]; - $refRow = (($row - 1) % 100) + 1; // only 100 distinct row refs - $formula = $this->buildFormula($pattern, $refRow); - $sheet->setCellValue("E{$row}", $formula); + $refRow = (($row - 1) % 100) + 1; + $formulas[] = $this->buildFormula($pattern, $refRow); } - // --- Run 1: Cold cache (first-time parsing + calculation) --- - Calculation::clearFormulaTokenCache(); - $this->clearCalculationCache($spreadsheet); + // --- Run 1: Cache disabled (default) --- + $calculation->setFormulaTokenCacheMaxSize(0); + + $noCacheStart = hrtime(true); + foreach ($formulas as $formula) { + $calculation->parseFormula($formula); + } + $noCacheNs = hrtime(true) - $noCacheStart; + + // --- Run 2: Cache enabled, cold --- + $calculation->setFormulaTokenCacheMaxSize(self::CACHE_SIZE); $coldStart = hrtime(true); - for ($row = 1; $row <= $cellCount; ++$row) { - $sheet->getCell("E{$row}")->getCalculatedValue(); + foreach ($formulas as $formula) { + $calculation->parseFormula($formula); } $coldNs = hrtime(true) - $coldStart; + $cacheSize = $calculation->getFormulaTokenCacheSize(); - // --- Run 2: Warm token cache (tokens already cached, recalculate) --- - $this->clearCalculationCache($spreadsheet); - + // --- Run 3: Cache enabled, warm --- $warmStart = hrtime(true); - for ($row = 1; $row <= $cellCount; ++$row) { - $sheet->getCell("E{$row}")->getCalculatedValue(); + foreach ($formulas as $formula) { + $calculation->parseFormula($formula); } $warmNs = hrtime(true) - $warmStart; + $noCacheMs = $noCacheNs / 1_000_000; $coldMs = $coldNs / 1_000_000; $warmMs = $warmNs / 1_000_000; - $speedup = $coldMs > 0 ? (($coldMs - $warmMs) / $coldMs) * 100 : 0; fwrite(STDERR, "\n"); - fwrite(STDERR, "=== Spreadsheet Calculation Benchmark ({$cellCount} formula cells) ===\n"); - fwrite(STDERR, sprintf(" Cold cache (parse + calc): %8.2f ms\n", $coldMs)); - fwrite(STDERR, sprintf(" Warm cache (cached parse): %8.2f ms\n", $warmMs)); - fwrite(STDERR, sprintf(" Token cache speedup: %8.2f %%\n", $speedup)); - fwrite(STDERR, sprintf(" Token cache size: %d entries\n", Calculation::getFormulaTokenCacheSize())); + fwrite(STDERR, "=== parseFormula() Enabled vs Disabled ({$cellCount} formulas) ===\n"); + fwrite(STDERR, sprintf(" PHP version: %s (%s)\n", PHP_VERSION, PHP_OS)); + fwrite(STDERR, sprintf(" Cache disabled: %8.2f ms\n", $noCacheMs)); + fwrite(STDERR, sprintf(" Cache enabled (cold): %8.2f ms\n", $coldMs)); + fwrite(STDERR, sprintf(" Cache enabled (warm): %8.2f ms\n", $warmMs)); + fwrite(STDERR, sprintf(" Cache entries: %d\n", $cacheSize)); fwrite(STDERR, "\n"); - // The warm run should not be dramatically slower than the cold run. - // We assert it completes successfully; the timing output shows benefit. - self::assertGreaterThan(0, $warmMs); - self::assertGreaterThan(0, Calculation::getFormulaTokenCacheSize()); + self::assertGreaterThan(0, $cacheSize); + self::assertLessThan($noCacheMs, $warmMs, 'Warm cache should be faster than no cache'); $spreadsheet->disconnectWorksheets(); } @@ -111,15 +103,16 @@ class FormulaTokenCacheBenchmark extends TestCase * with cache enabled vs after clearing the cache. * * The formula set is designed so the total distinct formulas stay well - * under the 1,000-entry cache limit, ensuring cache hits are reliable. + * under the cache limit, ensuring cache hits are reliable. */ public function testParseFormulaCacheVsNocache(): void { $spreadsheet = new Spreadsheet(); $calculation = Calculation::getInstance($spreadsheet); + $calculation->setFormulaTokenCacheMaxSize(self::CACHE_SIZE); $totalFormulas = 10_000; - // Build a pool of 200 distinct formulas (well under the 1,000 cache limit). + // Build a pool of 200 distinct formulas (well under the cache limit). // Each formula will be parsed ~50 times on average across 10,000 calls. $distinctPool = []; $patternCount = count(self::FORMULA_PATTERNS); @@ -134,7 +127,7 @@ class FormulaTokenCacheBenchmark extends TestCase } // --- Run 1: Cold cache (every formula must be fully parsed) --- - Calculation::clearFormulaTokenCache(); + $calculation->clearFormulaTokenCache(); $coldStart = hrtime(true); foreach ($formulas as $formula) { @@ -143,19 +136,16 @@ class FormulaTokenCacheBenchmark extends TestCase $coldNs = hrtime(true) - $coldStart; // --- Run 2: Warm cache (repeated formulas served from cache) --- - // Do NOT clear cache; the repeated formulas are already cached. - // Clear and re-parse to measure warm-cache performance. - $cacheSize = Calculation::getFormulaTokenCacheSize(); + $cacheSize = $calculation->getFormulaTokenCacheSize(); - // Re-run with warm cache $warmStart = hrtime(true); foreach ($formulas as $formula) { $calculation->parseFormula($formula); } $warmNs = hrtime(true) - $warmStart; - // --- Run 3: Cleared cache (simulates no-cache scenario) --- - Calculation::clearFormulaTokenCache(); + // --- Run 3: Cleared cache (simulates re-parsing) --- + $calculation->clearFormulaTokenCache(); $clearedStart = hrtime(true); foreach ($formulas as $formula) { @@ -172,13 +162,14 @@ class FormulaTokenCacheBenchmark extends TestCase fwrite(STDERR, "\n"); $distinctCount = count($distinctPool); fwrite(STDERR, "=== parseFormula() Benchmark ({$totalFormulas} calls, {$distinctCount} distinct) ===\n"); + fwrite(STDERR, sprintf(" PHP version: %s (%s)\n", PHP_VERSION, PHP_OS)); fwrite(STDERR, sprintf(" Cold cache (first parse): %8.2f ms\n", $coldMs)); fwrite(STDERR, sprintf(" Warm cache (all cached): %8.2f ms\n", $warmMs)); fwrite(STDERR, sprintf(" Cleared cache (re-parse): %8.2f ms\n", $clearedMs)); fwrite(STDERR, sprintf(" Warm vs cold improvement: %8.2f %%\n", $warmVsColdPct)); fwrite(STDERR, sprintf(" Warm vs cleared improvement:%7.2f %%\n", $warmVsClearedPct)); fwrite(STDERR, sprintf(" Cache entries after cold: %d\n", $cacheSize)); - fwrite(STDERR, sprintf(" Cache entries after clear: %d\n", Calculation::getFormulaTokenCacheSize())); + fwrite(STDERR, sprintf(" Cache entries after clear: %d\n", $calculation->getFormulaTokenCacheSize())); fwrite(STDERR, "\n"); // Warm cache should be faster than cold cache for repeated formulas @@ -200,14 +191,4 @@ class FormulaTokenCacheBenchmark extends TestCase ...array_fill(0, substr_count($pattern, '%d'), $row) ); } - - /** - * Clear the calculation value cache (not the token cache) so formulas - * are re-evaluated but token parsing cache state is preserved. - */ - private function clearCalculationCache(Spreadsheet $spreadsheet): void - { - $calculation = Calculation::getInstance($spreadsheet); - $calculation->clearCalculationCache(); - } } diff --git a/tests/PhpSpreadsheetTests/Calculation/FormulaTokenCacheTest.php b/tests/PhpSpreadsheetTests/Calculation/FormulaTokenCacheTest.php index 0948afedf..98a39bebb 100644 --- a/tests/PhpSpreadsheetTests/Calculation/FormulaTokenCacheTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/FormulaTokenCacheTest.php @@ -10,81 +10,71 @@ use PHPUnit\Framework\TestCase; class FormulaTokenCacheTest extends TestCase { + private Spreadsheet $spreadsheet; + + private Calculation $calculation; + protected function setUp(): void { - Calculation::clearFormulaTokenCache(); + $this->spreadsheet = new Spreadsheet(); + $this->calculation = Calculation::getInstance($this->spreadsheet); + $this->calculation->setFormulaTokenCacheMaxSize(1000); } protected function tearDown(): void { - Calculation::clearFormulaTokenCache(); + $this->calculation->clearFormulaTokenCache(); + $this->spreadsheet->disconnectWorksheets(); } public function testCachedResultMatchesUncachedResult(): void { - $spreadsheet = new Spreadsheet(); - $calculation = Calculation::getInstance($spreadsheet); - $formula = '=1+2'; // First call: uncached (cold cache) - $firstResult = $calculation->parseFormula($formula); + $firstResult = $this->calculation->parseFormula($formula); // Second call: should come from cache - $secondResult = $calculation->parseFormula($formula); + $secondResult = $this->calculation->parseFormula($formula); self::assertSame($firstResult, $secondResult); - $spreadsheet->disconnectWorksheets(); } public function testIdenticalFormulasReuseCache(): void { - $spreadsheet = new Spreadsheet(); - $calculation = Calculation::getInstance($spreadsheet); - $formula = '=SUM(A1:B2)'; - self::assertSame(0, Calculation::getFormulaTokenCacheSize()); + self::assertSame(0, $this->calculation->getFormulaTokenCacheSize()); - $calculation->parseFormula($formula); - self::assertSame(1, Calculation::getFormulaTokenCacheSize()); + $this->calculation->parseFormula($formula); + self::assertSame(1, $this->calculation->getFormulaTokenCacheSize()); // Parsing the same formula again should not increase cache size - $calculation->parseFormula($formula); - self::assertSame(1, Calculation::getFormulaTokenCacheSize()); - - $spreadsheet->disconnectWorksheets(); + $this->calculation->parseFormula($formula); + self::assertSame(1, $this->calculation->getFormulaTokenCacheSize()); } public function testCacheCanBeCleared(): void { - $spreadsheet = new Spreadsheet(); - $calculation = Calculation::getInstance($spreadsheet); + $this->calculation->parseFormula('=1+2'); + $this->calculation->parseFormula('=3*4'); + self::assertSame(2, $this->calculation->getFormulaTokenCacheSize()); - $calculation->parseFormula('=1+2'); - $calculation->parseFormula('=3*4'); - self::assertSame(2, Calculation::getFormulaTokenCacheSize()); - - Calculation::clearFormulaTokenCache(); - self::assertSame(0, Calculation::getFormulaTokenCacheSize()); - - $spreadsheet->disconnectWorksheets(); + $this->calculation->clearFormulaTokenCache(); + self::assertSame(0, $this->calculation->getFormulaTokenCacheSize()); } public function testDifferentFormulasGetSeparateCacheEntries(): void { - $spreadsheet = new Spreadsheet(); - $calculation = Calculation::getInstance($spreadsheet); - $formula1 = '=1+2'; $formula2 = '=3*4'; $formula3 = '=SUM(A1:A10)'; - $result1 = $calculation->parseFormula($formula1); - $result2 = $calculation->parseFormula($formula2); - $result3 = $calculation->parseFormula($formula3); + $result1 = $this->calculation->parseFormula($formula1); + $result2 = $this->calculation->parseFormula($formula2); + $result3 = $this->calculation->parseFormula($formula3); - self::assertSame(3, Calculation::getFormulaTokenCacheSize()); + self::assertSame(3, $this->calculation->getFormulaTokenCacheSize()); // Results should be different for different formulas self::assertNotEquals($result1, $result2); @@ -92,33 +82,28 @@ class FormulaTokenCacheTest extends TestCase self::assertNotEquals($result2, $result3); // Each formula still returns correct cached result - self::assertSame($result1, $calculation->parseFormula($formula1)); - self::assertSame($result2, $calculation->parseFormula($formula2)); - self::assertSame($result3, $calculation->parseFormula($formula3)); + 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, Calculation::getFormulaTokenCacheSize()); - - $spreadsheet->disconnectWorksheets(); + self::assertSame(3, $this->calculation->getFormulaTokenCacheSize()); } public function testCacheWorksAcrossMultipleCalculationCalls(): void { - $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); + $sheet = $this->spreadsheet->getActiveSheet(); $sheet->setCellValue('A1', 10); $sheet->setCellValue('A2', 20); $sheet->setCellValue('B1', '=A1+A2'); $sheet->setCellValue('B2', '=A1+A2'); - $calculation = Calculation::getInstance($spreadsheet); - - Calculation::clearFormulaTokenCache(); + $this->calculation->clearFormulaTokenCache(); // Parse the formula used in both cells $formula = '=A1+A2'; - $firstResult = $calculation->parseFormula($formula); - self::assertSame(1, Calculation::getFormulaTokenCacheSize()); + $firstResult = $this->calculation->parseFormula($formula); + self::assertSame(1, $this->calculation->getFormulaTokenCacheSize()); // Calculating different cells with the same formula structure $valueB1 = $sheet->getCell('B1')->getCalculatedValue(); @@ -128,17 +113,12 @@ class FormulaTokenCacheTest extends TestCase self::assertSame(30, $valueB2); // Cache should still return the same parsed result for the formula - $cachedResult = $calculation->parseFormula($formula); + $cachedResult = $this->calculation->parseFormula($formula); self::assertSame($firstResult, $cachedResult); - - $spreadsheet->disconnectWorksheets(); } public function testCacheHandlesComplexFormulas(): void { - $spreadsheet = new Spreadsheet(); - $calculation = Calculation::getInstance($spreadsheet); - $formulas = [ '=IF(A1>0,A1*2,0)', '=VLOOKUP(A1,B1:C10,2,FALSE)', @@ -148,55 +128,82 @@ class FormulaTokenCacheTest extends TestCase ]; foreach ($formulas as $formula) { - $result = $calculation->parseFormula($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), Calculation::getFormulaTokenCacheSize()); + self::assertSame(count($formulas), $this->calculation->getFormulaTokenCacheSize()); // Verify each formula returns the same result on subsequent calls foreach ($formulas as $formula) { - $first = $calculation->parseFormula($formula); - $second = $calculation->parseFormula($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), Calculation::getFormulaTokenCacheSize()); - - $spreadsheet->disconnectWorksheets(); + self::assertSame(count($formulas), $this->calculation->getFormulaTokenCacheSize()); } public function testCacheEvictsWhenFull(): void { - $spreadsheet = new Spreadsheet(); - $calculation = Calculation::getInstance($spreadsheet); + $this->calculation->setFormulaTokenCacheMaxSize(100); - // Fill the cache beyond its max size by generating unique formulas - // The cache max is 1000, so generate 1001 unique formulas - for ($i = 0; $i < 1001; ++$i) { - $calculation->parseFormula("={$i}+1"); + // 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 1000, the 1001st entry was added, so size should be 1 - self::assertSame(1, Calculation::getFormulaTokenCacheSize()); - - $spreadsheet->disconnectWorksheets(); + // After clearing at 100, the 101st entry was added, so size should be 1 + self::assertSame(1, $this->calculation->getFormulaTokenCacheSize()); } public function testNonFormulaStringsReturnEmptyArrayAndAreNotCached(): void { - $spreadsheet = new Spreadsheet(); - $calculation = Calculation::getInstance($spreadsheet); - - $result = $calculation->parseFormula('not a formula'); + $result = $this->calculation->parseFormula('not a formula'); self::assertSame([], $result); // Non-formulas (no = prefix) return early before caching - self::assertSame(0, Calculation::getFormulaTokenCacheSize()); + 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()); + } }