Skip to content
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
11 changes: 8 additions & 3 deletions README_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,24 @@ An intelligent caching mechanism designed for [PHPSpreadsheet](https://github.co
composer require kriss/spreadsheet-smart-cache
```

If you have cloned rather than required the project,
unit tests can be run with `pest` rather than `phpunit`:

```
vendor\bin\pest
```

# Usage

For detailed instructions, please refer to the official documentation: [PhpSpreadsheet Memory Saving](https://phpspreadsheet.readthedocs.io/en/latest/topics/memory_saving/)

```php
$yourLocalPath = __DIR__ . '/' . uniqid('spreadsheet_smart_cache_');
$cache = new \Kriss\SpreadsheetSmartCache\SpreadsheetSmartCache($yourLocalPath);
$cache = new \Kriss\SpreadsheetSmartCache\SpreadsheetSmartCache();

\PhpOffice\PhpSpreadsheet\Settings::setCache($cache);
```

> **Note**: To avoid cache directory conflicts in multi-concurrent scenarios, `$yourLocalPath` should be different each time it's executed.

# Design Concepts and Advantages

### Why not use APCu, Redis, or MemCache?
Expand Down
39 changes: 31 additions & 8 deletions scripts/benchmark.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require __DIR__ . '/../vendor/autoload.php';

use Kriss\SpreadsheetSmartCache\SpreadsheetSmartCache;
use PhpOffice\PhpSpreadsheet\Helper\TextGrid;
use PhpOffice\PhpSpreadsheet\Settings;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Border;
Expand Down Expand Up @@ -44,13 +45,35 @@ function write_line(string $msg)
unset($item);

write_line('result');
$table[] = '| cells | type | ts | memory_peak |';
$fnGetTableData = fn(array $item, string $key) => sprintf('| %s | %s | %.6f | %.2f |', $item['cells'], $key, $item[$key]['ts'], $item[$key]['memory_peak']);

$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->getCell('A1')->setValue('cell');
$sheet->getCell('B1')->setValue('type');
$sheet->getCell('C1')->setValue('seconds');
$sheet->getCell('D1')->setValue('memory');
$row = 1;
foreach ($data as $item) {
$table[] = $fnGetTableData($item, 'result');
$table[] = $fnGetTableData($item, 'cache_result');
++$row;
$sheet->getCell("A$row")->setValue($item['cells']);
$rs = 'result';
$sheet->getCell("B$row")->setValue('nocache');
$sheet->getCell("C$row")->setValue($item[$rs]['ts']);
$sheet->getCell("D$row")->setValue($item[$rs]['memory_peak']);
++$row;
$rs = 'cache_result';
$sheet->getCell("B$row")->setValue('cache');
$sheet->getCell("C$row")->setValue($item[$rs]['ts']);
$sheet->getCell("D$row")->setValue($item[$rs]['memory_peak']);
}
echo implode(PHP_EOL, $table) . PHP_EOL;
$matrix = $sheet->toArray(null, false, false);
$textGrid = new TextGrid($matrix, rowHeaders: false, columnHeaders: false);
if (method_exists($textGrid, 'setNumbersRight')) {
$textGrid->setNumbersRight(true);
}
echo $textGrid->render();
$spreadsheet->disconnectWorksheets();

return;
}

Expand Down Expand Up @@ -82,7 +105,7 @@ function do_spreadsheet(string $cellCoordinate): void

function set_cache(): void
{
$cache = new SpreadsheetSmartCache(__DIR__ . '/cache');
$cache = new SpreadsheetSmartCache();
Settings::setCache($cache);
}

Expand All @@ -96,6 +119,6 @@ function set_cache(): void
$time = round(microtime(true) - $start, 6);

echo json_encode([
'ts' => $time,
'memory_peak' => memory_get_peak_usage(true) / 1024 / 1024,
'ts' => (int) $time,
'memory_peak' => (int) (memory_get_peak_usage(true) / 1024 / 1024),
]);
70 changes: 34 additions & 36 deletions src/SpreadsheetSmartCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,36 @@ final class SpreadsheetSmartCache implements CacheInterface
private array $chunkList;

public function __construct(
private readonly string $localDir,
?array $chunkList = null,
private readonly bool $autoClear = true,
string $localDir = 'no longer used',
?array $chunkList = null,
bool $autoClear = true, // no longer used
)
{
$this->ensureDirectoryExists($this->localDir);

$this->chunkList = $chunkList ?? [1024, 2048, 3072, 4096, 5120];

if ($this->autoClear) {
register_shutdown_function(fn() => $this->clear());
}
}

private function ensureDirectoryExists(string $dir): void
{
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
}

/**
* @var array<int, resource>
*/
private array $resources = [];

/**
* @return resource
*/
private static function tmpFileOrThrow()
{
$temp = tmpfile();
if ($temp === false) {
throw new \Exception('tmpfile() failed');
}

return $temp;
}

private function getResource(int $chunkSize)
{
if (!isset($this->resources[$chunkSize])) {
$this->resources[$chunkSize] = fopen($this->localDir . '/' . $chunkSize . '.bin', 'w+');
$this->resources[$chunkSize] = self::tmpfileOrThrow();
}
return $this->resources[$chunkSize];
}
Expand Down Expand Up @@ -96,23 +96,33 @@ private function isExistInChunkSize(string $key): bool
return !!$this->getChunkSizeByKey($key);
}

/**
* @var array<string, resource>
*/
private array $singleFiles = [];

private function writeToSingleFile(string $key, string $value): void
{
file_put_contents($this->localDir . '/' . $key, $value);
$this->singleFiles[$key] = 1;
if (isset($this->singleFiles[$key])) {
fclose($this->singleFiles[$key]);
}
$this->singleFiles[$key] = self::tmpfileOrThrow();
fwrite($this->singleFiles[$key], $value);
}

private function readFromSingleFile(string $key): ?string
{
return file_get_contents($this->localDir . '/' . $key);
rewind($this->singleFiles[$key]);

return stream_get_contents($this->singleFiles[$key]);
}

private function deleteFromSingleFile(string $key): void
{
unset($this->singleFiles[$key]);
// 不做实际操作
if (isset($this->singleFiles[$key])) {
fclose($this->singleFiles[$key]);
unset($this->singleFiles[$key]);
}
}

private function isExistInSingleFile(string $key): bool
Expand All @@ -125,6 +135,8 @@ private function normalizeKey(string $key): string
/**
* 因为 key 会被作为 keyPos 的键存在于内存中
* 为了进一步节省内存,因此进行一些处理
* Because the key will be stored in memory as the keyPos,
* to further save memory, some processing is performed.
* @see Cells::getUniqueID()
*/
return str_replace('phpspreadsheet.', '', $key);
Expand Down Expand Up @@ -209,17 +221,6 @@ public function delete($key): bool
*/
public function clear(): bool
{
if (is_dir($this->localDir)) {
$files = scandir($this->localDir);
foreach ($files as $file) {
if ($file === '.' || $file === '..') {
continue;
}
unlink($this->localDir . '/' . $file);
}
rmdir($this->localDir);
}

return true;
}

Expand Down Expand Up @@ -268,9 +269,6 @@ public function has($key): bool

public function __destruct()
{
if ($this->autoClear) {
$this->clear();
}
foreach ($this->resources as $resource) {
fclose($resource);
}
Expand Down
18 changes: 4 additions & 14 deletions tests/Unit/SpreadsheetSmartCacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@
use Kriss\SpreadsheetSmartCache\SpreadsheetSmartCache;

test('simple crud', function () {
$path = runtime_path('spreadsheet_smart_cache');
$cache = new SpreadsheetSmartCache($path);
$cache = new SpreadsheetSmartCache();

// 初始化后文件目录存在
expect(file_exists($path))->toBeTrue();

// 测试简单的读写删
// 测试简单的读写删 - Testing simple read, write, and delete operations
$key = str_random();
$value = str_random();
// create
Expand All @@ -24,15 +20,10 @@
$cache->delete($key);
expect($cache->get($key))->toBeNull()
->and($cache->has($key))->toBeFalse();

// 用完之后,没有文件残留
$cache->__destruct();
expect(file_exists($path))->toBeFalse();
});

test('memory control', function () {
$path = runtime_path('spreadsheet_smart_cache');
$cache = new SpreadsheetSmartCache($path);
$cache = new SpreadsheetSmartCache();

$memoryStart = memory_get_peak_usage(true) / 1024;

Expand Down Expand Up @@ -70,8 +61,7 @@
});

test('value size change', function () {
$path = runtime_path('spreadsheet_smart_cache');
$cache = new SpreadsheetSmartCache($path);
$cache = new SpreadsheetSmartCache();

$key = str_random();

Expand Down