From fd9c925a7b2c7d31126e7b0501aa671c17d662e8 Mon Sep 17 00:00:00 2001 From: Adrien Crivelli Date: Fri, 14 Apr 2017 14:22:33 +0900 Subject: [PATCH] Refactor CachedObjectStorage to PSR-16 This drop a lot of non-core features code and delegate their maintainance to third parties. Also it open the door to any missing implementation out of the box, such as Redis for the moment. Finally this consistently enforce a constraint where there can be one and only one active cell at any point in time in code. This used to be true for non-default implementation of cache, but it was not true for default implementation where all cells were kept in-memory and thus were never detached from their worksheet and thus were all kept functionnal at any point in time. This inconsistency of behavior between in-memory and off-memory could lead to bugs when changing cache system if the end-user code was badly written. Now end-user will never be able to write buggy code in the first place, avoiding future headache when introducing caching. Closes #3 --- composer.json | 3 +- composer.lock | 53 +- docs/topics/memory_saving.md | 108 ++++ docs/topics/migration-from-PHPExcel.md | 29 +- docs/topics/settings.md | 171 +----- samples/06_Largescale_with_cellcaching.php | 15 - ...06_Largescale_with_cellcaching_sqlite3.php | 15 - .../CachedObjectStorage/APC.php | 287 ---------- .../CachedObjectStorage/CacheBase.php | 377 ------------- .../CachedObjectStorage/DiscISAM.php | 209 -------- .../CachedObjectStorage/ICache.php | 109 ---- .../CachedObjectStorage/Igbinary.php | 150 ------ .../CachedObjectStorage/Memcache.php | 313 ----------- .../CachedObjectStorage/Memory.php | 116 ---- .../CachedObjectStorage/MemoryGZip.php | 129 ----- .../CachedObjectStorage/MemorySerialized.php | 129 ----- .../CachedObjectStorage/PHPTemp.php | 197 ------- .../CachedObjectStorage/SQLite3.php | 359 ------------- .../CachedObjectStorage/Wincache.php | 292 ---------- .../CachedObjectStorageFactory.php | 228 -------- src/PhpSpreadsheet/Calculation.php | 2 +- src/PhpSpreadsheet/Cell.php | 42 +- src/PhpSpreadsheet/Collection/Cells.php | 507 ++++++++++++++++++ .../Collection/CellsFactory.php | 45 ++ src/PhpSpreadsheet/Collection/Memory.php | 77 +++ src/PhpSpreadsheet/Helper/Migrator.php | 16 +- src/PhpSpreadsheet/ReferenceHelper.php | 32 +- src/PhpSpreadsheet/Settings.php | 60 +-- src/PhpSpreadsheet/Spreadsheet.php | 16 +- src/PhpSpreadsheet/Worksheet.php | 81 ++- src/PhpSpreadsheet/Writer/Xls.php | 4 +- src/PhpSpreadsheet/Writer/Xls/Worksheet.php | 5 +- .../Writer/Xlsx/StringTable.php | 4 +- src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php | 6 +- .../Cell/AdvancedValueBinderTest.php | 12 +- .../Collection/CellsTest.php | 118 ++++ tests/PhpSpreadsheetTests/Reader/OdsTest.php | 2 +- .../Worksheet/AutoFilterTest.php | 10 +- .../Worksheet/CellCollectionTest.php | 24 - 39 files changed, 1077 insertions(+), 3275 deletions(-) create mode 100644 docs/topics/memory_saving.md delete mode 100644 samples/06_Largescale_with_cellcaching.php delete mode 100644 samples/06_Largescale_with_cellcaching_sqlite3.php delete mode 100644 src/PhpSpreadsheet/CachedObjectStorage/APC.php delete mode 100644 src/PhpSpreadsheet/CachedObjectStorage/CacheBase.php delete mode 100644 src/PhpSpreadsheet/CachedObjectStorage/DiscISAM.php delete mode 100644 src/PhpSpreadsheet/CachedObjectStorage/ICache.php delete mode 100644 src/PhpSpreadsheet/CachedObjectStorage/Igbinary.php delete mode 100644 src/PhpSpreadsheet/CachedObjectStorage/Memcache.php delete mode 100644 src/PhpSpreadsheet/CachedObjectStorage/Memory.php delete mode 100644 src/PhpSpreadsheet/CachedObjectStorage/MemoryGZip.php delete mode 100644 src/PhpSpreadsheet/CachedObjectStorage/MemorySerialized.php delete mode 100644 src/PhpSpreadsheet/CachedObjectStorage/PHPTemp.php delete mode 100644 src/PhpSpreadsheet/CachedObjectStorage/SQLite3.php delete mode 100644 src/PhpSpreadsheet/CachedObjectStorage/Wincache.php delete mode 100644 src/PhpSpreadsheet/CachedObjectStorageFactory.php create mode 100644 src/PhpSpreadsheet/Collection/Cells.php create mode 100644 src/PhpSpreadsheet/Collection/CellsFactory.php create mode 100644 src/PhpSpreadsheet/Collection/Memory.php create mode 100644 tests/PhpSpreadsheetTests/Collection/CellsTest.php delete mode 100644 tests/PhpSpreadsheetTests/Worksheet/CellCollectionTest.php diff --git a/composer.json b/composer.json index d38d94ff..05bb1806 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,8 @@ "ext-iconv": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "ext-zip": "*" + "ext-zip": "*", + "psr/simple-cache": "^1.0" }, "require-dev": { "mpdf/mpdf": "^6.1", diff --git a/composer.lock b/composer.lock index f64b9b5a..b2bf65ba 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,57 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "f2f5913aedbb6dbe0a6e6a97fde25039", - "packages": [], + "content-hash": "81b6f3fcd32849e29ea13dd37c9af17f", + "packages": [ + { + "name": "psr/simple-cache", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/753fa598e8f3b9966c886fe13f370baa45ef0e24", + "reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "time": "2017-01-02T13:31:39+00:00" + } + ], "packages-dev": [ { "name": "doctrine/instantiator", diff --git a/docs/topics/memory_saving.md b/docs/topics/memory_saving.md new file mode 100644 index 00000000..f2ff2070 --- /dev/null +++ b/docs/topics/memory_saving.md @@ -0,0 +1,108 @@ +# Memory saving + +PhpSpreadsheet uses an average of about 1k per cell in your worksheets, so +large workbooks can quickly use up available memory. Cell caching +provides a mechanism that allows PhpSpreadsheet to maintain the cell +objects in a smaller size of memory, or off-memory (eg: on disk, in APCu, +memcache or redis). This allows you to reduce the memory usage for large +workbooks, although at a cost of speed to access cell data. + +By default, PhpSpreadsheet holds all cell objects in memory, but +you can specify alternatives by providing your own +[PSR-16](http://www.php-fig.org/psr/psr-16/) implementation. PhpSpreadsheet keys +are automatically namespaced, and cleaned up after use, so a single cache +instance may be shared across several usage of PhpSpreadsheet or even with other +cache usages. + +To enable cell caching, you must provide your own implementation of cache like so: + +``` php +$cache = new MyCustomPsr16Implementation(); + +\PhpOffice\PhpSpreadsheet\Settings::setCache($cache); +``` + +A separate cache is maintained for each individual worksheet, and is +automatically created when the worksheet is instantiated based on the +settings that you have configured. You cannot change +the configuration settings once you have started to read a workbook, or +have created your first worksheet. + +## Beware of TTL + +As opposed to common cache concept, PhpSpreadsheet data cannot be re-generated +from scratch. If some data is stored and later is not retrievable, +PhpSpreadsheet will throw an exception. + +That means that the data stored in cache **must not be deleted** by a +third-party or via TTL mechanism. + +So be sure that TTL is either de-activated or long enough to cover the entire +usage of PhpSpreadsheet. + +## Common use cases + +PhpSpreadsheet does not ship with alternative cache implementation. It is up to +you to select the most appropriate implementation for your environnement. You +can either implement [PSR-16](http://www.php-fig.org/psr/psr-16/) from scratch, +or use [pre-existing libraries](https://packagist.org/search/?q=psr-16). + +One such library is [PHP Cache](http://www.php-cache.com/) which +provides a wide range of alternatives. Refers to their documentation for +details, but here are a few suggestions that should get you started. + + +### APCu + +Require the packages into your project: + +```sh +composer require cache/simple-cache-bridge cache/apcu-adapter +``` + +Configure PhpSpreadsheet with something like: + +```php +$pool = new \Cache\Adapter\Apcu\ApcuCachePool(); +$simpleCache = new \Cache\Bridge\SimpleCache\SimpleCacheBridge($pool); + +\PhpOffice\PhpSpreadsheet\Settings::setCache($simpleCache); +``` + +### Redis + +Require the packages into your project: + +```sh +composer require cache/simple-cache-bridge cache/redis-adapter +``` + +Configure PhpSpreadsheet with something like: + +```php +$client = new \Redis(); +$client->connect('127.0.0.1', 6379); +$pool = new \Cache\Adapter\Redis\RedisCachePool($client); +$simpleCache = new \Cache\Bridge\SimpleCache\SimpleCacheBridge($pool); + +\PhpOffice\PhpSpreadsheet\Settings::setCache($simpleCache); +``` + +### Memcache + +Require the packages into your project: + +```sh +composer require cache/simple-cache-bridge cache/memcache-adapter +``` + +Configure PhpSpreadsheet with something like: + +```php +$client = new \Memcache(); +$client->connect('localhost', 11211); +$pool = new \Cache\Adapter\Memcache\MemcacheCachePool($client); +$simpleCache = new \Cache\Bridge\SimpleCache\SimpleCacheBridge($pool); + +\PhpOffice\PhpSpreadsheet\Settings::setCache($simpleCache); +``` \ No newline at end of file diff --git a/docs/topics/migration-from-PHPExcel.md b/docs/topics/migration-from-PHPExcel.md index 912f08b8..b948eac2 100644 --- a/docs/topics/migration-from-PHPExcel.md +++ b/docs/topics/migration-from-PHPExcel.md @@ -136,7 +136,8 @@ $rendererName = \PhpOffice\PhpSpreadsheet\Settings::PDF_RENDERER_MPDF; ## PclZip and ZipArchive Support for PclZip were dropped in favor of the more complete and modern -PHP extension ZipArchive. So the following were removed: +[PHP extension ZipArchive](http://php.net/manual/en/book.zip.php). +So the following were removed: - `PclZip` - `PHPExcel_Settings::setZipClass()` @@ -144,3 +145,29 @@ PHP extension ZipArchive. So the following were removed: - `PHPExcel_Shared_ZipArchive` - `PHPExcel_Shared_ZipStreamWrapper` + +## Cell caching + +Cell caching was heavily refactored to leverage +[PSR-16](http://www.php-fig.org/psr/psr-16/). That means most classes +related to that feature were removed: + +- `PHPExcel_CachedObjectStorage_APC` +- `PHPExcel_CachedObjectStorage_DiscISAM` +- `PHPExcel_CachedObjectStorage_ICache` +- `PHPExcel_CachedObjectStorage_Igbinary` +- `PHPExcel_CachedObjectStorage_Memcache` +- `PHPExcel_CachedObjectStorage_Memory` +- `PHPExcel_CachedObjectStorage_MemoryGZip` +- `PHPExcel_CachedObjectStorage_MemorySerialized` +- `PHPExcel_CachedObjectStorage_PHPTemp` +- `PHPExcel_CachedObjectStorage_SQLite` +- `PHPExcel_CachedObjectStorage_SQLite3` +- `PHPExcel_CachedObjectStorage_Wincache` + +In addition to that, `\PhpOffice\PhpSpreadsheet::getCellCollection()` was renamed +to `\PhpOffice\PhpSpreadsheet::getCoordinates()` and +`\PhpOffice\PhpSpreadsheet::getCellCacheController()` to +`\PhpOffice\PhpSpreadsheet::getCellCollection()` for clarity. + +Refer to [the new documentation](./memory_saving.md) to see how to migrate. diff --git a/docs/topics/settings.md b/docs/topics/settings.md index b0ab796a..a9aae9f9 100644 --- a/docs/topics/settings.md +++ b/docs/topics/settings.md @@ -5,177 +5,20 @@ before instantiating a `Spreadsheet` object or loading a workbook file, there are a number of configuration options that can be set which will affect the subsequent behaviour of the script. -## Cell Caching +## Cell collection caching -PhpSpreadsheet uses an average of about 1k/cell in your worksheets, so -large workbooks can quickly use up available memory. Cell caching -provides a mechanism that allows PhpSpreadsheet to maintain the cell -objects in a smaller size of memory, on disk, or in APC, memcache or -Wincache, rather than in PHP memory. This allows you to reduce the -memory usage for large workbooks, although at a cost of speed to access -cell data. +By default, PhpSpreadsheet holds all cell objects in memory, but +you can specify alternatives to reduce memory consumption at the cost of speed. +Read more about [memory saving](./memory_saving.md). -By default, PhpSpreadsheet still holds all cell objects in memory, but -you can specify alternatives. To enable cell caching, you must call the -\PhpOffice\PhpSpreadsheet\Settings::setCacheStorageMethod() method, -passing in the caching method that you wish to use. +To enable cell caching, you must provide your own implementation of cache like so: ``` php -$cacheMethod = \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE_IN_MEMORY; +$cache = new MyCustomPsr16Implementation(); -\PhpOffice\PhpSpreadsheet\Settings::setCacheStorageMethod($cacheMethod); +\PhpOffice\PhpSpreadsheet\Settings::setCache($cache); ``` -setCacheStorageMethod() will return a boolean true on success, false on -failure (for example if trying to cache to APC when APC is not enabled). - -A separate cache is maintained for each individual worksheet, and is -automatically created when the worksheet is instantiated based on the -caching method and settings that you have configured. You cannot change -the configuration settings once you have started to read a workbook, or -have created your first worksheet. - -Currently, the following caching methods are available. - -### \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE\_IN\_MEMORY - -The default. If you don't initialise any caching method, then this is -the method that PhpSpreadsheet will use. Cell objects are maintained in -PHP memory as at present. - -### \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE\_IN\_MEMORY\_SERIALIZED - -Using this caching method, cells are held in PHP memory as an array of -serialized objects, which reduces the memory footprint with minimal -performance overhead. - -### \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE\_IN\_MEMORY\_GZIP - -Like cache\_in\_memory\_serialized, this method holds cells in PHP -memory as an array of serialized objects, but gzipped to reduce the -memory usage still further, although access to read or write a cell is -slightly slower. - -### \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE\_IGBINARY - -Uses PHPs igbinary extension (if its available) to serialize cell -objects in memory. This is normally faster and uses less memory than -standard PHP serialization, but isnt available in most hosting -environments. - -### \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE\_TO\_DISCISAM - -When using CACHE\_TO\_DISCISAM all cells are held in a temporary disk -file, with only an index to their location in that file maintained in -PHP memory. This is slower than any of the CACHE\_IN\_MEMORY methods, -but significantly reduces the memory footprint. By default, -PhpSpreadsheet will use PHP's temp directory for the cache file, but you -can specify a different directory when initialising CACHE\_TO\_DISCISAM. - -``` php -$cacheMethod = \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE_TO_DISCISAM; -$cacheSettings = array( - 'dir' => '/usr/local/tmp' -); -\PhpOffice\PhpSpreadsheet\Settings::setCacheStorageMethod($cacheMethod, $cacheSettings); -``` - -The temporary disk file is automatically deleted when your script -terminates. - -### \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE\_TO\_PHPTEMP - -Like CACHE\_TO\_DISCISAM, when using CACHE\_TO\_PHPTEMP all cells are -held in the php://temp I/O stream, with only an index to their location -maintained in PHP memory. In PHP, the php://memory wrapper stores data -in the memory: php://temp behaves similarly, but uses a temporary file -for storing the data when a certain memory limit is reached. The default -is 1 MB, but you can change this when initialising CACHE\_TO\_PHPTEMP. - -``` php -$cacheMethod = \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE_TO_PHPTEMP; -$cacheSettings = array( - 'memoryCacheSize' => '8MB' -); -\PhpOffice\PhpSpreadsheet\Settings::setCacheStorageMethod($cacheMethod, $cacheSettings); -``` - -The php://temp file is automatically deleted when your script -terminates. - -### \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE\_TO\_APC - -When using CACHE\_TO\_APC, cell objects are maintained in APC with only -an index maintained in PHP memory to identify that the cell exists. By -default, an APC cache timeout of 600 seconds is used, which should be -enough for most applications: although it is possible to change this -when initialising CACHE\_TO\_APC. - -``` php -$cacheMethod = \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE_TO_APC; -$cacheSettings = array( - 'cacheTime' => 600 -); -\PhpOffice\PhpSpreadsheet\Settings::setCacheStorageMethod($cacheMethod, $cacheSettings); -``` - -When your script terminates all entries will be cleared from APC, -regardless of the cacheTime value, so it cannot be used for persistent -storage using this mechanism. - -### \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE\_TO\_MEMCACHE - -When using CACHE\_TO\_MEMCACHE, cell objects are maintained in memcache -with only an index maintained in PHP memory to identify that the cell -exists. - -By default, PhpSpreadsheet looks for a memcache server on localhost at -port 11211. It also sets a memcache timeout limit of 600 seconds. If you -are running memcache on a different server or port, then you can change -these defaults when you initialise CACHE\_TO\_MEMCACHE: - -``` php -$cacheMethod = \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE_TO_MEMCACHE; -$cacheSettings = array( - 'memcacheServer' => 'localhost', - 'memcachePort' => 11211, - 'cacheTime' => 600 -); -\PhpOffice\PhpSpreadsheet\Settings::setCacheStorageMethod($cacheMethod, $cacheSettings); -``` - -When your script terminates all entries will be cleared from memcache, -regardless of the cacheTime value, so it cannot be used for persistent -storage using this mechanism. - -### \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE\_TO\_WINCACHE - -When using CACHE\_TO\_WINCACHE, cell objects are maintained in Wincache -with only an index maintained in PHP memory to identify that the cell -exists. By default, a Wincache cache timeout of 600 seconds is used, -which should be enough for most applications: although it is possible to -change this when initialising CACHE\_TO\_WINCACHE. - -``` php -$cacheMethod = \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE_TO_WINCACHE; -$cacheSettings = array( - 'cacheTime' => 600 -); -\PhpOffice\PhpSpreadsheet\Settings::setCacheStorageMethod($cacheMethod, $cacheSettings); -``` - -When your script terminates all entries will be cleared from Wincache, -regardless of the cacheTime value, so it cannot be used for persistent -storage using this mechanism. - -### \PhpOffice\PhpSpreadsheet\CachedObjectStorageFactory::CACHE\_TO\_SQLITE3; - -Uses an SQLite 3 "in-memory" database for caching cell data. Unlike -other caching methods, neither cells nor an index are held in PHP memory -- an indexed database table makes it unnecessary to hold any index in -PHP memory, which makes this the most memory-efficient of the cell -caching methods. - ## Language/Locale Some localisation elements have been included in PhpSpreadsheet. You can diff --git a/samples/06_Largescale_with_cellcaching.php b/samples/06_Largescale_with_cellcaching.php deleted file mode 100644 index 30e8b9bd..00000000 --- a/samples/06_Largescale_with_cellcaching.php +++ /dev/null @@ -1,15 +0,0 @@ -log('Enable Cell Caching using ' . $cacheMethod . ' method'); -} else { - $helper->log('ERROR: Unable to set Cell Caching using ' . $cacheMethod . ' method, reverting to memory'); -} - -$spreadsheet = require __DIR__ . '/templates/largeSpreadsheet.php'; - -// Save -$helper->write($spreadsheet, __FILE__); diff --git a/samples/06_Largescale_with_cellcaching_sqlite3.php b/samples/06_Largescale_with_cellcaching_sqlite3.php deleted file mode 100644 index 3d781004..00000000 --- a/samples/06_Largescale_with_cellcaching_sqlite3.php +++ /dev/null @@ -1,15 +0,0 @@ -log('Enable Cell Caching using ' . $cacheMethod . ' method'); -} else { - $helper->log('ERROR: Unable to set Cell Caching using ' . $cacheMethod . ' method, reverting to memory'); -} - -$spreadsheet = require __DIR__ . '/templates/largeSpreadsheet.php'; - -// Save -$helper->write($spreadsheet, __FILE__); diff --git a/src/PhpSpreadsheet/CachedObjectStorage/APC.php b/src/PhpSpreadsheet/CachedObjectStorage/APC.php deleted file mode 100644 index 12d6f345..00000000 --- a/src/PhpSpreadsheet/CachedObjectStorage/APC.php +++ /dev/null @@ -1,287 +0,0 @@ -currentCellIsDirty && !empty($this->currentObjectID)) { - $this->currentObject->detach(); - - if (!apc_store( - $this->cachePrefix . $this->currentObjectID . '.cache', - serialize($this->currentObject), - $this->cacheTime - )) { - $this->__destruct(); - throw new \PhpOffice\PhpSpreadsheet\Exception('Failed to store cell ' . $this->currentObjectID . ' in APC'); - } - $this->currentCellIsDirty = false; - } - $this->currentObjectID = $this->currentObject = null; - } - - /** - * Add or Update a cell in cache identified by coordinate address. - * - * @param string $pCoord Coordinate address of the cell to update - * @param \PhpOffice\PhpSpreadsheet\Cell $cell Cell to update - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell - */ - public function addCacheData($pCoord, \PhpOffice\PhpSpreadsheet\Cell $cell) - { - if (($pCoord !== $this->currentObjectID) && ($this->currentObjectID !== null)) { - $this->storeData(); - } - $this->cellCache[$pCoord] = true; - - $this->currentObjectID = $pCoord; - $this->currentObject = $cell; - $this->currentCellIsDirty = true; - - return $cell; - } - - /** - * Is a value set in the current \PhpOffice\PhpSpreadsheet\CachedObjectStorage\ICache for an indexed cell? - * - * @param string $pCoord Coordinate address of the cell to check - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return bool - */ - public function isDataSet($pCoord) - { - // Check if the requested entry is the current object, or exists in the cache - if (parent::isDataSet($pCoord)) { - if ($this->currentObjectID == $pCoord) { - return true; - } - // Check if the requested entry still exists in apc - $success = apc_fetch($this->cachePrefix . $pCoord . '.cache'); - if ($success === false) { - // Entry no longer exists in APC, so clear it from the cache array - parent::deleteCacheData($pCoord); - throw new \PhpOffice\PhpSpreadsheet\Exception('Cell entry ' . $pCoord . ' no longer exists in APC cache'); - } - - return true; - } - - return false; - } - - /** - * Get cell at a specific coordinate. - * - * @param string $pCoord Coordinate of the cell - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell Cell that was found, or null if not found - */ - public function getCacheData($pCoord) - { - if ($pCoord === $this->currentObjectID) { - return $this->currentObject; - } - $this->storeData(); - - // Check if the entry that has been requested actually exists - if (parent::isDataSet($pCoord)) { - $obj = apc_fetch($this->cachePrefix . $pCoord . '.cache'); - if ($obj === false) { - // Entry no longer exists in APC, so clear it from the cache array - parent::deleteCacheData($pCoord); - throw new \PhpOffice\PhpSpreadsheet\Exception('Cell entry ' . $pCoord . ' no longer exists in APC cache'); - } - } else { - // Return null if requested entry doesn't exist in cache - return null; - } - - // Set current entry to the requested entry - $this->currentObjectID = $pCoord; - $this->currentObject = unserialize($obj); - // Re-attach this as the cell's parent - $this->currentObject->attach($this); - - // Return requested entry - return $this->currentObject; - } - - /** - * Get a list of all cell addresses currently held in cache. - * - * @return string[] - */ - public function getCellList() - { - if ($this->currentObjectID !== null) { - $this->storeData(); - } - - return parent::getCellList(); - } - - /** - * Delete a cell in cache identified by coordinate address. - * - * @param string $pCoord Coordinate address of the cell to delete - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - */ - public function deleteCacheData($pCoord) - { - // Delete the entry from APC - apc_delete($this->cachePrefix . $pCoord . '.cache'); - - // Delete the entry from our cell address array - parent::deleteCacheData($pCoord); - } - - /** - * Clone the cell collection. - * - * @param \PhpOffice\PhpSpreadsheet\Worksheet $parent The new worksheet that we're copying to - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - */ - public function copyCellCollection(\PhpOffice\PhpSpreadsheet\Worksheet $parent) - { - parent::copyCellCollection($parent); - // Get a new id for the new file name - $baseUnique = $this->getUniqueID(); - $newCachePrefix = substr(md5($baseUnique), 0, 8) . '.'; - $cacheList = $this->getCellList(); - foreach ($cacheList as $cellID) { - if ($cellID != $this->currentObjectID) { - $obj = apc_fetch($this->cachePrefix . $cellID . '.cache'); - if ($obj === false) { - // Entry no longer exists in APC, so clear it from the cache array - parent::deleteCacheData($cellID); - throw new \PhpOffice\PhpSpreadsheet\Exception('Cell entry ' . $cellID . ' no longer exists in APC'); - } - if (!apc_store($newCachePrefix . $cellID . '.cache', $obj, $this->cacheTime)) { - $this->__destruct(); - throw new \PhpOffice\PhpSpreadsheet\Exception('Failed to store cell ' . $cellID . ' in APC'); - } - } - } - $this->cachePrefix = $newCachePrefix; - } - - /** - * Clear the cell collection and disconnect from our parent. - */ - public function unsetWorksheetCells() - { - if ($this->currentObject !== null) { - $this->currentObject->detach(); - $this->currentObject = $this->currentObjectID = null; - } - - // Flush the APC cache - $this->__destruct(); - - $this->cellCache = []; - - // detach ourself from the worksheet, so that it can then delete this object successfully - $this->parent = null; - } - - /** - * Initialise this new cell collection. - * - * @param \PhpOffice\PhpSpreadsheet\Worksheet $parent The worksheet for this cell collection - * @param array $arguments Additional initialisation arguments - */ - public function __construct(\PhpOffice\PhpSpreadsheet\Worksheet $parent, $arguments) - { - $cacheTime = (isset($arguments['cacheTime'])) ? $arguments['cacheTime'] : 600; - - if ($this->cachePrefix === null) { - $baseUnique = $this->getUniqueID(); - $this->cachePrefix = substr(md5($baseUnique), 0, 8) . '.'; - $this->cacheTime = $cacheTime; - - parent::__construct($parent); - } - } - - /** - * Destroy this cell collection. - */ - public function __destruct() - { - $cacheList = $this->getCellList(); - foreach ($cacheList as $cellID) { - apc_delete($this->cachePrefix . $cellID . '.cache'); - } - } - - /** - * Identify whether the caching method is currently available - * Some methods are dependent on the availability of certain extensions being enabled in the PHP build. - * - * @return bool - */ - public static function cacheMethodIsAvailable() - { - if (!function_exists('apc_store')) { - return false; - } - if (apc_sma_info() === false) { - return false; - } - - return true; - } -} diff --git a/src/PhpSpreadsheet/CachedObjectStorage/CacheBase.php b/src/PhpSpreadsheet/CachedObjectStorage/CacheBase.php deleted file mode 100644 index db224696..00000000 --- a/src/PhpSpreadsheet/CachedObjectStorage/CacheBase.php +++ /dev/null @@ -1,377 +0,0 @@ -parent = $parent; - } - - /** - * Return the parent worksheet for this cell collection. - * - * @return \PhpOffice\PhpSpreadsheet\Worksheet - */ - public function getParent() - { - return $this->parent; - } - - /** - * Is a value set in the current \PhpOffice\PhpSpreadsheet\CachedObjectStorage\ICache for an indexed cell? - * - * @param string $pCoord Coordinate address of the cell to check - * - * @return bool - */ - public function isDataSet($pCoord) - { - if ($pCoord === $this->currentObjectID) { - return true; - } - // Check if the requested entry exists in the cache - return isset($this->cellCache[$pCoord]); - } - - /** - * Move a cell object from one address to another. - * - * @param string $fromAddress Current address of the cell to move - * @param string $toAddress Destination address of the cell to move - * - * @return bool - */ - public function moveCell($fromAddress, $toAddress) - { - if ($fromAddress === $this->currentObjectID) { - $this->currentObjectID = $toAddress; - } - $this->currentCellIsDirty = true; - if (isset($this->cellCache[$fromAddress])) { - $this->cellCache[$toAddress] = &$this->cellCache[$fromAddress]; - unset($this->cellCache[$fromAddress]); - } - - return true; - } - - /** - * Add or Update a cell in cache. - * - * @param \PhpOffice\PhpSpreadsheet\Cell $cell Cell to update - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell - */ - public function updateCacheData(\PhpOffice\PhpSpreadsheet\Cell $cell) - { - return $this->addCacheData($cell->getCoordinate(), $cell); - } - - /** - * Delete a cell in cache identified by coordinate address. - * - * @param string $pCoord Coordinate address of the cell to delete - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - */ - public function deleteCacheData($pCoord) - { - if ($pCoord === $this->currentObjectID && !is_null($this->currentObject)) { - $this->currentObject->detach(); - $this->currentObjectID = $this->currentObject = null; - } - - if (is_object($this->cellCache[$pCoord])) { - $this->cellCache[$pCoord]->detach(); - } - unset($this->cellCache[$pCoord]); - $this->currentCellIsDirty = false; - } - - /** - * Get a list of all cell addresses currently held in cache. - * - * @return string[] - */ - public function getCellList() - { - return array_keys($this->cellCache); - } - - /** - * Sort the list of all cell addresses currently held in cache by row and column. - * - * @return string[] - */ - public function getSortedCellList() - { - $sortKeys = []; - foreach ($this->getCellList() as $coord) { - sscanf($coord, '%[A-Z]%d', $column, $row); - $sortKeys[sprintf('%09d%3s', $row, $column)] = $coord; - } - ksort($sortKeys); - - return array_values($sortKeys); - } - - /** - * Get highest worksheet column and highest row that have cell records. - * - * @return array Highest column name and highest row number - */ - public function getHighestRowAndColumn() - { - // Lookup highest column and highest row - $col = ['A' => '1A']; - $row = [1]; - foreach ($this->getCellList() as $coord) { - sscanf($coord, '%[A-Z]%d', $c, $r); - $row[$r] = $r; - $col[$c] = strlen($c) . $c; - } - if (!empty($row)) { - // Determine highest column and row - $highestRow = max($row); - $highestColumn = substr(max($col), 1); - } - - return [ - 'row' => $highestRow, - 'column' => $highestColumn, - ]; - } - - /** - * Return the cell address of the currently active cell object. - * - * @return string - */ - public function getCurrentAddress() - { - return $this->currentObjectID; - } - - /** - * Return the column address of the currently active cell object. - * - * @return string - */ - public function getCurrentColumn() - { - sscanf($this->currentObjectID, '%[A-Z]%d', $column, $row); - - return $column; - } - - /** - * Return the row address of the currently active cell object. - * - * @return int - */ - public function getCurrentRow() - { - sscanf($this->currentObjectID, '%[A-Z]%d', $column, $row); - - return (int) $row; - } - - /** - * Get highest worksheet column. - * - * @param string $row Return the highest column for the specified row, - * or the highest column of any row if no row number is passed - * - * @return string Highest column name - */ - public function getHighestColumn($row = null) - { - if ($row == null) { - $colRow = $this->getHighestRowAndColumn(); - - return $colRow['column']; - } - - $columnList = [1]; - foreach ($this->getCellList() as $coord) { - sscanf($coord, '%[A-Z]%d', $c, $r); - if ($r != $row) { - continue; - } - $columnList[] = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($c); - } - - return \PhpOffice\PhpSpreadsheet\Cell::stringFromColumnIndex(max($columnList) - 1); - } - - /** - * Get highest worksheet row. - * - * @param string $column Return the highest row for the specified column, - * or the highest row of any column if no column letter is passed - * - * @return int Highest row number - */ - public function getHighestRow($column = null) - { - if ($column == null) { - $colRow = $this->getHighestRowAndColumn(); - - return $colRow['row']; - } - - $rowList = [0]; - foreach ($this->getCellList() as $coord) { - sscanf($coord, '%[A-Z]%d', $c, $r); - if ($c != $column) { - continue; - } - $rowList[] = $r; - } - - return max($rowList); - } - - /** - * Generate a unique ID for cache referencing. - * - * @return string Unique Reference - */ - protected function getUniqueID() - { - if (function_exists('posix_getpid')) { - $baseUnique = posix_getpid(); - } else { - $baseUnique = mt_rand(); - } - - return uniqid($baseUnique, true); - } - - /** - * Clone the cell collection. - * - * @param \PhpOffice\PhpSpreadsheet\Worksheet $parent The new worksheet that we're copying to - */ - public function copyCellCollection(\PhpOffice\PhpSpreadsheet\Worksheet $parent) - { - $this->currentCellIsDirty; - $this->storeData(); - - $this->parent = $parent; - if (($this->currentObject !== null) && (is_object($this->currentObject))) { - $this->currentObject->attach($this); - } - } - - /** - * Remove a row, deleting all cells in that row. - * - * @param string $row Row number to remove - */ - public function removeRow($row) - { - foreach ($this->getCellList() as $coord) { - sscanf($coord, '%[A-Z]%d', $c, $r); - if ($r == $row) { - $this->deleteCacheData($coord); - } - } - } - - /** - * Remove a column, deleting all cells in that column. - * - * @param string $column Column ID to remove - */ - public function removeColumn($column) - { - foreach ($this->getCellList() as $coord) { - sscanf($coord, '%[A-Z]%d', $c, $r); - if ($c == $column) { - $this->deleteCacheData($coord); - } - } - } - - /** - * Identify whether the caching method is currently available - * Some methods are dependent on the availability of certain extensions being enabled in the PHP build. - * - * @return bool - */ - public static function cacheMethodIsAvailable() - { - return true; - } -} diff --git a/src/PhpSpreadsheet/CachedObjectStorage/DiscISAM.php b/src/PhpSpreadsheet/CachedObjectStorage/DiscISAM.php deleted file mode 100644 index d630742b..00000000 --- a/src/PhpSpreadsheet/CachedObjectStorage/DiscISAM.php +++ /dev/null @@ -1,209 +0,0 @@ -currentCellIsDirty && !empty($this->currentObjectID)) { - $this->currentObject->detach(); - - fseek($this->fileHandle, 0, SEEK_END); - - $this->cellCache[$this->currentObjectID] = [ - 'ptr' => ftell($this->fileHandle), - 'sz' => fwrite($this->fileHandle, serialize($this->currentObject)), - ]; - $this->currentCellIsDirty = false; - } - $this->currentObjectID = $this->currentObject = null; - } - - /** - * Add or Update a cell in cache identified by coordinate address. - * - * @param string $pCoord Coordinate address of the cell to update - * @param \PhpOffice\PhpSpreadsheet\Cell $cell Cell to update - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell - */ - public function addCacheData($pCoord, \PhpOffice\PhpSpreadsheet\Cell $cell) - { - if (($pCoord !== $this->currentObjectID) && ($this->currentObjectID !== null)) { - $this->storeData(); - } - - $this->currentObjectID = $pCoord; - $this->currentObject = $cell; - $this->currentCellIsDirty = true; - - return $cell; - } - - /** - * Get cell at a specific coordinate. - * - * @param string $pCoord Coordinate of the cell - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell Cell that was found, or null if not found - */ - public function getCacheData($pCoord) - { - if ($pCoord === $this->currentObjectID) { - return $this->currentObject; - } - $this->storeData(); - - // Check if the entry that has been requested actually exists - if (!isset($this->cellCache[$pCoord])) { - // Return null if requested entry doesn't exist in cache - return null; - } - - // Set current entry to the requested entry - $this->currentObjectID = $pCoord; - fseek($this->fileHandle, $this->cellCache[$pCoord]['ptr']); - $this->currentObject = unserialize(fread($this->fileHandle, $this->cellCache[$pCoord]['sz'])); - // Re-attach this as the cell's parent - $this->currentObject->attach($this); - - // Return requested entry - return $this->currentObject; - } - - /** - * Get a list of all cell addresses currently held in cache. - * - * @return string[] - */ - public function getCellList() - { - if ($this->currentObjectID !== null) { - $this->storeData(); - } - - return parent::getCellList(); - } - - /** - * Clone the cell collection. - * - * @param \PhpOffice\PhpSpreadsheet\Worksheet $parent The new worksheet that we're copying to - */ - public function copyCellCollection(\PhpOffice\PhpSpreadsheet\Worksheet $parent) - { - parent::copyCellCollection($parent); - // Get a new id for the new file name - $baseUnique = $this->getUniqueID(); - $newFileName = $this->cacheDirectory . '/PhpSpreadsheet.' . $baseUnique . '.cache'; - // Copy the existing cell cache file - copy($this->fileName, $newFileName); - $this->fileName = $newFileName; - // Open the copied cell cache file - $this->fileHandle = fopen($this->fileName, 'a+'); - } - - /** - * Clear the cell collection and disconnect from our parent. - */ - public function unsetWorksheetCells() - { - if (!is_null($this->currentObject)) { - $this->currentObject->detach(); - $this->currentObject = $this->currentObjectID = null; - } - $this->cellCache = []; - - // detach ourself from the worksheet, so that it can then delete this object successfully - $this->parent = null; - - // Close down the temporary cache file - $this->__destruct(); - } - - /** - * Initialise this new cell collection. - * - * @param \PhpOffice\PhpSpreadsheet\Worksheet $parent The worksheet for this cell collection - * @param array of mixed $arguments Additional initialisation arguments - */ - public function __construct(\PhpOffice\PhpSpreadsheet\Worksheet $parent, $arguments) - { - $this->cacheDirectory = ((isset($arguments['dir'])) && ($arguments['dir'] !== null)) - ? $arguments['dir'] - : \PhpOffice\PhpSpreadsheet\Shared\File::sysGetTempDir(); - - parent::__construct($parent); - if (is_null($this->fileHandle)) { - $baseUnique = $this->getUniqueID(); - $this->fileName = $this->cacheDirectory . '/PhpSpreadsheet.' . $baseUnique . '.cache'; - $this->fileHandle = fopen($this->fileName, 'a+'); - } - } - - /** - * Destroy this cell collection. - */ - public function __destruct() - { - if (!is_null($this->fileHandle)) { - fclose($this->fileHandle); - unlink($this->fileName); - } - $this->fileHandle = null; - } -} diff --git a/src/PhpSpreadsheet/CachedObjectStorage/ICache.php b/src/PhpSpreadsheet/CachedObjectStorage/ICache.php deleted file mode 100644 index 9c2edefa..00000000 --- a/src/PhpSpreadsheet/CachedObjectStorage/ICache.php +++ /dev/null @@ -1,109 +0,0 @@ -currentCellIsDirty && !empty($this->currentObjectID)) { - $this->currentObject->detach(); - - $this->cellCache[$this->currentObjectID] = igbinary_serialize($this->currentObject); - $this->currentCellIsDirty = false; - } - $this->currentObjectID = $this->currentObject = null; - } - - // function _storeData() - - /** - * Add or Update a cell in cache identified by coordinate address. - * - * @param string $pCoord Coordinate address of the cell to update - * @param \PhpOffice\PhpSpreadsheet\Cell $cell Cell to update - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell - */ - public function addCacheData($pCoord, \PhpOffice\PhpSpreadsheet\Cell $cell) - { - if (($pCoord !== $this->currentObjectID) && ($this->currentObjectID !== null)) { - $this->storeData(); - } - - $this->currentObjectID = $pCoord; - $this->currentObject = $cell; - $this->currentCellIsDirty = true; - - return $cell; - } - - /** - * Get cell at a specific coordinate. - * - * @param string $pCoord Coordinate of the cell - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell Cell that was found, or null if not found - */ - public function getCacheData($pCoord) - { - if ($pCoord === $this->currentObjectID) { - return $this->currentObject; - } - $this->storeData(); - - // Check if the entry that has been requested actually exists - if (!isset($this->cellCache[$pCoord])) { - // Return null if requested entry doesn't exist in cache - return null; - } - - // Set current entry to the requested entry - $this->currentObjectID = $pCoord; - $this->currentObject = igbinary_unserialize($this->cellCache[$pCoord]); - // Re-attach this as the cell's parent - $this->currentObject->attach($this); - - // Return requested entry - return $this->currentObject; - } - - // function getCacheData() - - /** - * Get a list of all cell addresses currently held in cache. - * - * @return string[] - */ - public function getCellList() - { - if ($this->currentObjectID !== null) { - $this->storeData(); - } - - return parent::getCellList(); - } - - /** - * Clear the cell collection and disconnect from our parent. - */ - public function unsetWorksheetCells() - { - if (!is_null($this->currentObject)) { - $this->currentObject->detach(); - $this->currentObject = $this->currentObjectID = null; - } - $this->cellCache = []; - - // detach ourself from the worksheet, so that it can then delete this object successfully - $this->parent = null; - } - - // function unsetWorksheetCells() - - /** - * Identify whether the caching method is currently available - * Some methods are dependent on the availability of certain extensions being enabled in the PHP build. - * - * @return bool - */ - public static function cacheMethodIsAvailable() - { - if (!function_exists('igbinary_serialize')) { - return false; - } - - return true; - } -} diff --git a/src/PhpSpreadsheet/CachedObjectStorage/Memcache.php b/src/PhpSpreadsheet/CachedObjectStorage/Memcache.php deleted file mode 100644 index a760b38a..00000000 --- a/src/PhpSpreadsheet/CachedObjectStorage/Memcache.php +++ /dev/null @@ -1,313 +0,0 @@ -currentCellIsDirty && !empty($this->currentObjectID)) { - $this->currentObject->detach(); - - $obj = serialize($this->currentObject); - if (!$this->memcache->replace($this->cachePrefix . $this->currentObjectID . '.cache', $obj, null, $this->cacheTime)) { - if (!$this->memcache->add($this->cachePrefix . $this->currentObjectID . '.cache', $obj, null, $this->cacheTime)) { - $this->__destruct(); - throw new \PhpOffice\PhpSpreadsheet\Exception("Failed to store cell {$this->currentObjectID} in MemCache"); - } - } - $this->currentCellIsDirty = false; - } - $this->currentObjectID = $this->currentObject = null; - } - - /** - * Add or Update a cell in cache identified by coordinate address. - * - * @param string $pCoord Coordinate address of the cell to update - * @param \PhpOffice\PhpSpreadsheet\Cell $cell Cell to update - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell - */ - public function addCacheData($pCoord, \PhpOffice\PhpSpreadsheet\Cell $cell) - { - if (($pCoord !== $this->currentObjectID) && ($this->currentObjectID !== null)) { - $this->storeData(); - } - $this->cellCache[$pCoord] = true; - - $this->currentObjectID = $pCoord; - $this->currentObject = $cell; - $this->currentCellIsDirty = true; - - return $cell; - } - - /** - * Is a value set in the current \PhpOffice\PhpSpreadsheet\CachedObjectStorage\ICache for an indexed cell? - * - * @param string $pCoord Coordinate address of the cell to check - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return bool - */ - public function isDataSet($pCoord) - { - // Check if the requested entry is the current object, or exists in the cache - if (parent::isDataSet($pCoord)) { - if ($this->currentObjectID == $pCoord) { - return true; - } - // Check if the requested entry still exists in Memcache - $success = $this->memcache->get($this->cachePrefix . $pCoord . '.cache'); - if ($success === false) { - // Entry no longer exists in Memcache, so clear it from the cache array - parent::deleteCacheData($pCoord); - throw new \PhpOffice\PhpSpreadsheet\Exception('Cell entry ' . $pCoord . ' no longer exists in MemCache'); - } - - return true; - } - - return false; - } - - /** - * Get cell at a specific coordinate. - * - * @param string $pCoord Coordinate of the cell - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell Cell that was found, or null if not found - */ - public function getCacheData($pCoord) - { - if ($pCoord === $this->currentObjectID) { - return $this->currentObject; - } - $this->storeData(); - - // Check if the entry that has been requested actually exists - if (parent::isDataSet($pCoord)) { - $obj = $this->memcache->get($this->cachePrefix . $pCoord . '.cache'); - if ($obj === false) { - // Entry no longer exists in Memcache, so clear it from the cache array - parent::deleteCacheData($pCoord); - throw new \PhpOffice\PhpSpreadsheet\Exception("Cell entry {$pCoord} no longer exists in MemCache"); - } - } else { - // Return null if requested entry doesn't exist in cache - return null; - } - - // Set current entry to the requested entry - $this->currentObjectID = $pCoord; - $this->currentObject = unserialize($obj); - // Re-attach this as the cell's parent - $this->currentObject->attach($this); - - // Return requested entry - return $this->currentObject; - } - - /** - * Get a list of all cell addresses currently held in cache. - * - * @return string[] - */ - public function getCellList() - { - if ($this->currentObjectID !== null) { - $this->storeData(); - } - - return parent::getCellList(); - } - - /** - * Delete a cell in cache identified by coordinate address. - * - * @param string $pCoord Coordinate address of the cell to delete - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - */ - public function deleteCacheData($pCoord) - { - // Delete the entry from Memcache - $this->memcache->delete($this->cachePrefix . $pCoord . '.cache'); - - // Delete the entry from our cell address array - parent::deleteCacheData($pCoord); - } - - /** - * Clone the cell collection. - * - * @param \PhpOffice\PhpSpreadsheet\Worksheet $parent The new worksheet that we're copying to - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - */ - public function copyCellCollection(\PhpOffice\PhpSpreadsheet\Worksheet $parent) - { - parent::copyCellCollection($parent); - // Get a new id for the new file name - $baseUnique = $this->getUniqueID(); - $newCachePrefix = substr(md5($baseUnique), 0, 8) . '.'; - $cacheList = $this->getCellList(); - foreach ($cacheList as $cellID) { - if ($cellID != $this->currentObjectID) { - $obj = $this->memcache->get($this->cachePrefix . $cellID . '.cache'); - if ($obj === false) { - // Entry no longer exists in Memcache, so clear it from the cache array - parent::deleteCacheData($cellID); - throw new \PhpOffice\PhpSpreadsheet\Exception("Cell entry {$cellID} no longer exists in MemCache"); - } - if (!$this->memcache->add($newCachePrefix . $cellID . '.cache', $obj, null, $this->cacheTime)) { - $this->__destruct(); - throw new \PhpOffice\PhpSpreadsheet\Exception("Failed to store cell {$cellID} in MemCache"); - } - } - } - $this->cachePrefix = $newCachePrefix; - } - - /** - * Clear the cell collection and disconnect from our parent. - */ - public function unsetWorksheetCells() - { - if (!is_null($this->currentObject)) { - $this->currentObject->detach(); - $this->currentObject = $this->currentObjectID = null; - } - - // Flush the Memcache cache - $this->__destruct(); - - $this->cellCache = []; - - // detach ourself from the worksheet, so that it can then delete this object successfully - $this->parent = null; - } - - /** - * Initialise this new cell collection. - * - * @param \PhpOffice\PhpSpreadsheet\Worksheet $parent The worksheet for this cell collection - * @param mixed[] $arguments Additional initialisation arguments - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - */ - public function __construct(\PhpOffice\PhpSpreadsheet\Worksheet $parent, $arguments) - { - $memcacheServer = (isset($arguments['memcacheServer'])) ? $arguments['memcacheServer'] : 'localhost'; - $memcachePort = (isset($arguments['memcachePort'])) ? $arguments['memcachePort'] : 11211; - $cacheTime = (isset($arguments['cacheTime'])) ? $arguments['cacheTime'] : 600; - - if (is_null($this->cachePrefix)) { - $baseUnique = $this->getUniqueID(); - $this->cachePrefix = substr(md5($baseUnique), 0, 8) . '.'; - - // Set a new Memcache object and connect to the Memcache server - $this->memcache = new \Memcache(); - if (!$this->memcache->addServer($memcacheServer, $memcachePort, false, 50, 5, 5, true, [$this, 'failureCallback'])) { - throw new \PhpOffice\PhpSpreadsheet\Exception("Could not connect to MemCache server at {$memcacheServer}:{$memcachePort}"); - } - $this->cacheTime = $cacheTime; - - parent::__construct($parent); - } - } - - /** - * Memcache error handler. - * - * @param string $host Memcache server - * @param int $port Memcache port - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - */ - public function failureCallback($host, $port) - { - throw new \PhpOffice\PhpSpreadsheet\Exception("memcache {$host}:{$port} failed"); - } - - /** - * Destroy this cell collection. - */ - public function __destruct() - { - $cacheList = $this->getCellList(); - foreach ($cacheList as $cellID) { - $this->memcache->delete($this->cachePrefix . $cellID . '.cache'); - } - } - - /** - * Identify whether the caching method is currently available - * Some methods are dependent on the availability of certain extensions being enabled in the PHP build. - * - * @return bool - */ - public static function cacheMethodIsAvailable() - { - if (!function_exists('memcache_add')) { - return false; - } - - return true; - } -} diff --git a/src/PhpSpreadsheet/CachedObjectStorage/Memory.php b/src/PhpSpreadsheet/CachedObjectStorage/Memory.php deleted file mode 100644 index 57d0c496..00000000 --- a/src/PhpSpreadsheet/CachedObjectStorage/Memory.php +++ /dev/null @@ -1,116 +0,0 @@ -cellCache[$pCoord] = $cell; - - // Set current entry to the new/updated entry - $this->currentObjectID = $pCoord; - - return $cell; - } - - /** - * Get cell at a specific coordinate. - * - * @param string $pCoord Coordinate of the cell - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell Cell that was found, or null if not found - */ - public function getCacheData($pCoord) - { - // Check if the entry that has been requested actually exists - if (!isset($this->cellCache[$pCoord])) { - $this->currentObjectID = null; - // Return null if requested entry doesn't exist in cache - return null; - } - - // Set current entry to the requested entry - $this->currentObjectID = $pCoord; - - // Return requested entry - return $this->cellCache[$pCoord]; - } - - /** - * Clone the cell collection. - * - * @param \PhpOffice\PhpSpreadsheet\Worksheet $parent The new worksheet that we're copying to - */ - public function copyCellCollection(\PhpOffice\PhpSpreadsheet\Worksheet $parent) - { - parent::copyCellCollection($parent); - - $newCollection = []; - foreach ($this->cellCache as $k => &$cell) { - $newCollection[$k] = clone $cell; - $newCollection[$k]->attach($this); - } - - $this->cellCache = $newCollection; - } - - /** - * Clear the cell collection and disconnect from our parent. - */ - public function unsetWorksheetCells() - { - // Because cells are all stored as intact objects in memory, we need to detach each one from the parent - foreach ($this->cellCache as $k => &$cell) { - $cell->detach(); - $this->cellCache[$k] = null; - } - unset($cell); - - $this->cellCache = []; - - // detach ourself from the worksheet, so that it can then delete this object successfully - $this->parent = null; - } -} diff --git a/src/PhpSpreadsheet/CachedObjectStorage/MemoryGZip.php b/src/PhpSpreadsheet/CachedObjectStorage/MemoryGZip.php deleted file mode 100644 index 8483fbb6..00000000 --- a/src/PhpSpreadsheet/CachedObjectStorage/MemoryGZip.php +++ /dev/null @@ -1,129 +0,0 @@ -currentCellIsDirty && !empty($this->currentObjectID)) { - $this->currentObject->detach(); - - $this->cellCache[$this->currentObjectID] = gzdeflate(serialize($this->currentObject), 9); - $this->currentCellIsDirty = false; - } - $this->currentObjectID = $this->currentObject = null; - } - - /** - * Add or Update a cell in cache identified by coordinate address. - * - * @param string $pCoord Coordinate address of the cell to update - * @param \PhpOffice\PhpSpreadsheet\Cell $cell Cell to update - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell - */ - public function addCacheData($pCoord, \PhpOffice\PhpSpreadsheet\Cell $cell) - { - if (($pCoord !== $this->currentObjectID) && ($this->currentObjectID !== null)) { - $this->storeData(); - } - - $this->currentObjectID = $pCoord; - $this->currentObject = $cell; - $this->currentCellIsDirty = true; - - return $cell; - } - - /** - * Get cell at a specific coordinate. - * - * @param string $pCoord Coordinate of the cell - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell Cell that was found, or null if not found - */ - public function getCacheData($pCoord) - { - if ($pCoord === $this->currentObjectID) { - return $this->currentObject; - } - $this->storeData(); - - // Check if the entry that has been requested actually exists - if (!isset($this->cellCache[$pCoord])) { - // Return null if requested entry doesn't exist in cache - return null; - } - - // Set current entry to the requested entry - $this->currentObjectID = $pCoord; - $this->currentObject = unserialize(gzinflate($this->cellCache[$pCoord])); - // Re-attach this as the cell's parent - $this->currentObject->attach($this); - - // Return requested entry - return $this->currentObject; - } - - /** - * Get a list of all cell addresses currently held in cache. - * - * @return string[] - */ - public function getCellList() - { - if ($this->currentObjectID !== null) { - $this->storeData(); - } - - return parent::getCellList(); - } - - /** - * Clear the cell collection and disconnect from our parent. - */ - public function unsetWorksheetCells() - { - if (!is_null($this->currentObject)) { - $this->currentObject->detach(); - $this->currentObject = $this->currentObjectID = null; - } - $this->cellCache = []; - - // detach ourself from the worksheet, so that it can then delete this object successfully - $this->parent = null; - } -} diff --git a/src/PhpSpreadsheet/CachedObjectStorage/MemorySerialized.php b/src/PhpSpreadsheet/CachedObjectStorage/MemorySerialized.php deleted file mode 100644 index 2c3069c9..00000000 --- a/src/PhpSpreadsheet/CachedObjectStorage/MemorySerialized.php +++ /dev/null @@ -1,129 +0,0 @@ -currentCellIsDirty && !empty($this->currentObjectID)) { - $this->currentObject->detach(); - - $this->cellCache[$this->currentObjectID] = serialize($this->currentObject); - $this->currentCellIsDirty = false; - } - $this->currentObjectID = $this->currentObject = null; - } - - /** - * Add or Update a cell in cache identified by coordinate address. - * - * @param string $pCoord Coordinate address of the cell to update - * @param \PhpOffice\PhpSpreadsheet\Cell $cell Cell to update - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell - */ - public function addCacheData($pCoord, \PhpOffice\PhpSpreadsheet\Cell $cell) - { - if (($pCoord !== $this->currentObjectID) && ($this->currentObjectID !== null)) { - $this->storeData(); - } - - $this->currentObjectID = $pCoord; - $this->currentObject = $cell; - $this->currentCellIsDirty = true; - - return $cell; - } - - /** - * Get cell at a specific coordinate. - * - * @param string $pCoord Coordinate of the cell - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell Cell that was found, or null if not found - */ - public function getCacheData($pCoord) - { - if ($pCoord === $this->currentObjectID) { - return $this->currentObject; - } - $this->storeData(); - - // Check if the entry that has been requested actually exists - if (!isset($this->cellCache[$pCoord])) { - // Return null if requested entry doesn't exist in cache - return null; - } - - // Set current entry to the requested entry - $this->currentObjectID = $pCoord; - $this->currentObject = unserialize($this->cellCache[$pCoord]); - // Re-attach this as the cell's parent - $this->currentObject->attach($this); - - // Return requested entry - return $this->currentObject; - } - - /** - * Get a list of all cell addresses currently held in cache. - * - * @return string[] - */ - public function getCellList() - { - if ($this->currentObjectID !== null) { - $this->storeData(); - } - - return parent::getCellList(); - } - - /** - * Clear the cell collection and disconnect from our parent. - */ - public function unsetWorksheetCells() - { - if (!is_null($this->currentObject)) { - $this->currentObject->detach(); - $this->currentObject = $this->currentObjectID = null; - } - $this->cellCache = []; - - // detach ourself from the worksheet, so that it can then delete this object successfully - $this->parent = null; - } -} diff --git a/src/PhpSpreadsheet/CachedObjectStorage/PHPTemp.php b/src/PhpSpreadsheet/CachedObjectStorage/PHPTemp.php deleted file mode 100644 index e2497436..00000000 --- a/src/PhpSpreadsheet/CachedObjectStorage/PHPTemp.php +++ /dev/null @@ -1,197 +0,0 @@ -currentCellIsDirty && !empty($this->currentObjectID)) { - $this->currentObject->detach(); - - fseek($this->fileHandle, 0, SEEK_END); - - $this->cellCache[$this->currentObjectID] = [ - 'ptr' => ftell($this->fileHandle), - 'sz' => fwrite($this->fileHandle, serialize($this->currentObject)), - ]; - $this->currentCellIsDirty = false; - } - $this->currentObjectID = $this->currentObject = null; - } - - /** - * Add or Update a cell in cache identified by coordinate address. - * - * @param string $pCoord Coordinate address of the cell to update - * @param \PhpOffice\PhpSpreadsheet\Cell $cell Cell to update - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell - */ - public function addCacheData($pCoord, \PhpOffice\PhpSpreadsheet\Cell $cell) - { - if (($pCoord !== $this->currentObjectID) && ($this->currentObjectID !== null)) { - $this->storeData(); - } - - $this->currentObjectID = $pCoord; - $this->currentObject = $cell; - $this->currentCellIsDirty = true; - - return $cell; - } - - /** - * Get cell at a specific coordinate. - * - * @param string $pCoord Coordinate of the cell - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell Cell that was found, or null if not found - */ - public function getCacheData($pCoord) - { - if ($pCoord === $this->currentObjectID) { - return $this->currentObject; - } - $this->storeData(); - - // Check if the entry that has been requested actually exists - if (!isset($this->cellCache[$pCoord])) { - // Return null if requested entry doesn't exist in cache - return null; - } - - // Set current entry to the requested entry - $this->currentObjectID = $pCoord; - fseek($this->fileHandle, $this->cellCache[$pCoord]['ptr']); - $this->currentObject = unserialize(fread($this->fileHandle, $this->cellCache[$pCoord]['sz'])); - // Re-attach this as the cell's parent - $this->currentObject->attach($this); - - // Return requested entry - return $this->currentObject; - } - - /** - * Get a list of all cell addresses currently held in cache. - * - * @return string[] - */ - public function getCellList() - { - if ($this->currentObjectID !== null) { - $this->storeData(); - } - - return parent::getCellList(); - } - - /** - * Clone the cell collection. - * - * @param \PhpOffice\PhpSpreadsheet\Worksheet $parent The new worksheet that we're copying to - */ - public function copyCellCollection(\PhpOffice\PhpSpreadsheet\Worksheet $parent) - { - parent::copyCellCollection($parent); - // Open a new stream for the cell cache data - $newFileHandle = fopen('php://temp/maxmemory:' . $this->memoryCacheSize, 'a+'); - // Copy the existing cell cache data to the new stream - fseek($this->fileHandle, 0); - while (!feof($this->fileHandle)) { - fwrite($newFileHandle, fread($this->fileHandle, 1024)); - } - $this->fileHandle = $newFileHandle; - } - - /** - * Clear the cell collection and disconnect from our parent. - */ - public function unsetWorksheetCells() - { - if (!is_null($this->currentObject)) { - $this->currentObject->detach(); - $this->currentObject = $this->currentObjectID = null; - } - $this->cellCache = []; - - // detach ourself from the worksheet, so that it can then delete this object successfully - $this->parent = null; - - // Close down the php://temp file - $this->__destruct(); - } - - /** - * Initialise this new cell collection. - * - * @param \PhpOffice\PhpSpreadsheet\Worksheet $parent The worksheet for this cell collection - * @param mixed[] $arguments Additional initialisation arguments - */ - public function __construct(\PhpOffice\PhpSpreadsheet\Worksheet $parent, $arguments) - { - $this->memoryCacheSize = (isset($arguments['memoryCacheSize'])) ? $arguments['memoryCacheSize'] : 1 * 1024 * 1024; - - parent::__construct($parent); - if (is_null($this->fileHandle)) { - $this->fileHandle = fopen('php://temp/maxmemory:' . $this->memoryCacheSize, 'a+'); - } - } - - /** - * Destroy this cell collection. - */ - public function __destruct() - { - if (!is_null($this->fileHandle)) { - fclose($this->fileHandle); - } - $this->fileHandle = null; - } -} diff --git a/src/PhpSpreadsheet/CachedObjectStorage/SQLite3.php b/src/PhpSpreadsheet/CachedObjectStorage/SQLite3.php deleted file mode 100644 index 3f78e911..00000000 --- a/src/PhpSpreadsheet/CachedObjectStorage/SQLite3.php +++ /dev/null @@ -1,359 +0,0 @@ -currentCellIsDirty && !empty($this->currentObjectID)) { - $this->currentObject->detach(); - - $this->insertQuery->bindValue('id', $this->currentObjectID, SQLITE3_TEXT); - $this->insertQuery->bindValue('data', serialize($this->currentObject), SQLITE3_BLOB); - $result = $this->insertQuery->execute(); - if ($result === false) { - throw new \PhpOffice\PhpSpreadsheet\Exception($this->DBHandle->lastErrorMsg()); - } - $this->currentCellIsDirty = false; - } - $this->currentObjectID = $this->currentObject = null; - } - - /** - * Add or Update a cell in cache identified by coordinate address. - * - * @param string $pCoord Coordinate address of the cell to update - * @param \PhpOffice\PhpSpreadsheet\Cell $cell Cell to update - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell - */ - public function addCacheData($pCoord, \PhpOffice\PhpSpreadsheet\Cell $cell) - { - if (($pCoord !== $this->currentObjectID) && ($this->currentObjectID !== null)) { - $this->storeData(); - } - - $this->currentObjectID = $pCoord; - $this->currentObject = $cell; - $this->currentCellIsDirty = true; - - return $cell; - } - - /** - * Get cell at a specific coordinate. - * - * @param string $pCoord Coordinate of the cell - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell Cell that was found, or null if not found - */ - public function getCacheData($pCoord) - { - if ($pCoord === $this->currentObjectID) { - return $this->currentObject; - } - $this->storeData(); - - $this->selectQuery->bindValue('id', $pCoord, SQLITE3_TEXT); - $cellResult = $this->selectQuery->execute(); - if ($cellResult === false) { - throw new \PhpOffice\PhpSpreadsheet\Exception($this->DBHandle->lastErrorMsg()); - } - $cellData = $cellResult->fetchArray(SQLITE3_ASSOC); - if ($cellData === false) { - // Return null if requested entry doesn't exist in cache - return null; - } - - // Set current entry to the requested entry - $this->currentObjectID = $pCoord; - - $this->currentObject = unserialize($cellData['value']); - // Re-attach this as the cell's parent - $this->currentObject->attach($this); - - // Return requested entry - return $this->currentObject; - } - - /** - * Is a value set for an indexed cell? - * - * @param string $pCoord Coordinate address of the cell to check - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return bool - */ - public function isDataSet($pCoord) - { - if ($pCoord === $this->currentObjectID) { - return true; - } - - // Check if the requested entry exists in the cache - $this->selectQuery->bindValue('id', $pCoord, SQLITE3_TEXT); - $cellResult = $this->selectQuery->execute(); - if ($cellResult === false) { - throw new \PhpOffice\PhpSpreadsheet\Exception($this->DBHandle->lastErrorMsg()); - } - $cellData = $cellResult->fetchArray(SQLITE3_ASSOC); - - return ($cellData === false) ? false : true; - } - - /** - * Delete a cell in cache identified by coordinate address. - * - * @param string $pCoord Coordinate address of the cell to delete - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - */ - public function deleteCacheData($pCoord) - { - if ($pCoord === $this->currentObjectID) { - $this->currentObject->detach(); - $this->currentObjectID = $this->currentObject = null; - } - - // Check if the requested entry exists in the cache - $this->deleteQuery->bindValue('id', $pCoord, SQLITE3_TEXT); - $result = $this->deleteQuery->execute(); - if ($result === false) { - throw new \PhpOffice\PhpSpreadsheet\Exception($this->DBHandle->lastErrorMsg()); - } - - $this->currentCellIsDirty = false; - } - - /** - * Move a cell object from one address to another. - * - * @param string $fromAddress Current address of the cell to move - * @param string $toAddress Destination address of the cell to move - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return bool - */ - public function moveCell($fromAddress, $toAddress) - { - if ($fromAddress === $this->currentObjectID) { - $this->currentObjectID = $toAddress; - } - - $this->deleteQuery->bindValue('id', $toAddress, SQLITE3_TEXT); - $result = $this->deleteQuery->execute(); - if ($result === false) { - throw new \PhpOffice\PhpSpreadsheet\Exception($this->DBHandle->lastErrorMsg()); - } - - $this->updateQuery->bindValue('toid', $toAddress, SQLITE3_TEXT); - $this->updateQuery->bindValue('fromid', $fromAddress, SQLITE3_TEXT); - $result = $this->updateQuery->execute(); - if ($result === false) { - throw new \PhpOffice\PhpSpreadsheet\Exception($this->DBHandle->lastErrorMsg()); - } - - return true; - } - - /** - * Get a list of all cell addresses currently held in cache. - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return string[] - */ - public function getCellList() - { - if ($this->currentObjectID !== null) { - $this->storeData(); - } - - $query = 'SELECT id FROM kvp_' . $this->TableName; - $cellIdsResult = $this->DBHandle->query($query); - if ($cellIdsResult === false) { - throw new \PhpOffice\PhpSpreadsheet\Exception($this->DBHandle->lastErrorMsg()); - } - - $cellKeys = []; - while ($row = $cellIdsResult->fetchArray(SQLITE3_ASSOC)) { - $cellKeys[] = $row['id']; - } - - return $cellKeys; - } - - /** - * Clone the cell collection. - * - * @param \PhpOffice\PhpSpreadsheet\Worksheet $parent The new worksheet that we're copying to - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - */ - public function copyCellCollection(\PhpOffice\PhpSpreadsheet\Worksheet $parent) - { - $this->currentCellIsDirty; - $this->storeData(); - - // Get a new id for the new table name - $tableName = str_replace('.', '_', $this->getUniqueID()); - if (!$this->DBHandle->exec('CREATE TABLE kvp_' . $tableName . ' (id VARCHAR(12) PRIMARY KEY, value BLOB) - AS SELECT * FROM kvp_' . $this->TableName) - ) { - throw new \PhpOffice\PhpSpreadsheet\Exception($this->DBHandle->lastErrorMsg()); - } - - // Copy the existing cell cache file - $this->TableName = $tableName; - } - - /** - * Clear the cell collection and disconnect from our parent. - */ - public function unsetWorksheetCells() - { - if (!is_null($this->currentObject)) { - $this->currentObject->detach(); - $this->currentObject = $this->currentObjectID = null; - } - // detach ourself from the worksheet, so that it can then delete this object successfully - $this->parent = null; - - // Close down the temporary cache file - $this->__destruct(); - } - - /** - * Initialise this new cell collection. - * - * @param \PhpOffice\PhpSpreadsheet\Worksheet $parent The worksheet for this cell collection - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - */ - public function __construct(\PhpOffice\PhpSpreadsheet\Worksheet $parent) - { - parent::__construct($parent); - if (is_null($this->DBHandle)) { - $this->TableName = str_replace('.', '_', $this->getUniqueID()); - $_DBName = ':memory:'; - - $this->DBHandle = new \SQLite3($_DBName); - if ($this->DBHandle === false) { - throw new \PhpOffice\PhpSpreadsheet\Exception($this->DBHandle->lastErrorMsg()); - } - if (!$this->DBHandle->exec('CREATE TABLE kvp_' . $this->TableName . ' (id VARCHAR(12) PRIMARY KEY, value BLOB)')) { - throw new \PhpOffice\PhpSpreadsheet\Exception($this->DBHandle->lastErrorMsg()); - } - } - - $this->selectQuery = $this->DBHandle->prepare('SELECT value FROM kvp_' . $this->TableName . ' WHERE id = :id'); - $this->insertQuery = $this->DBHandle->prepare('INSERT OR REPLACE INTO kvp_' . $this->TableName . ' VALUES(:id,:data)'); - $this->updateQuery = $this->DBHandle->prepare('UPDATE kvp_' . $this->TableName . ' SET id=:toId WHERE id=:fromId'); - $this->deleteQuery = $this->DBHandle->prepare('DELETE FROM kvp_' . $this->TableName . ' WHERE id = :id'); - } - - /** - * Destroy this cell collection. - */ - public function __destruct() - { - if (!is_null($this->DBHandle)) { - $this->DBHandle->exec('DROP TABLE kvp_' . $this->TableName); - $this->DBHandle->close(); - } - $this->DBHandle = null; - } - - /** - * Identify whether the caching method is currently available - * Some methods are dependent on the availability of certain extensions being enabled in the PHP build. - * - * @return bool - */ - public static function cacheMethodIsAvailable() - { - if (!class_exists('SQLite3', false)) { - return false; - } - - return true; - } -} diff --git a/src/PhpSpreadsheet/CachedObjectStorage/Wincache.php b/src/PhpSpreadsheet/CachedObjectStorage/Wincache.php deleted file mode 100644 index 8dc656f7..00000000 --- a/src/PhpSpreadsheet/CachedObjectStorage/Wincache.php +++ /dev/null @@ -1,292 +0,0 @@ -currentCellIsDirty && !empty($this->currentObjectID)) { - $this->currentObject->detach(); - - $obj = serialize($this->currentObject); - if (wincache_ucache_exists($this->cachePrefix . $this->currentObjectID . '.cache')) { - if (!wincache_ucache_set($this->cachePrefix . $this->currentObjectID . '.cache', $obj, $this->cacheTime)) { - $this->__destruct(); - throw new \PhpOffice\PhpSpreadsheet\Exception('Failed to store cell ' . $this->currentObjectID . ' in WinCache'); - } - } else { - if (!wincache_ucache_add($this->cachePrefix . $this->currentObjectID . '.cache', $obj, $this->cacheTime)) { - $this->__destruct(); - throw new \PhpOffice\PhpSpreadsheet\Exception('Failed to store cell ' . $this->currentObjectID . ' in WinCache'); - } - } - $this->currentCellIsDirty = false; - } - - $this->currentObjectID = $this->currentObject = null; - } - - /** - * Add or Update a cell in cache identified by coordinate address. - * - * @param string $pCoord Coordinate address of the cell to update - * @param \PhpOffice\PhpSpreadsheet\Cell $cell Cell to update - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell - */ - public function addCacheData($pCoord, \PhpOffice\PhpSpreadsheet\Cell $cell) - { - if (($pCoord !== $this->currentObjectID) && ($this->currentObjectID !== null)) { - $this->storeData(); - } - $this->cellCache[$pCoord] = true; - - $this->currentObjectID = $pCoord; - $this->currentObject = $cell; - $this->currentCellIsDirty = true; - - return $cell; - } - - /** - * Is a value set in the current \PhpOffice\PhpSpreadsheet\CachedObjectStorage\ICache for an indexed cell? - * - * @param string $pCoord Coordinate address of the cell to check - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return bool - */ - public function isDataSet($pCoord) - { - // Check if the requested entry is the current object, or exists in the cache - if (parent::isDataSet($pCoord)) { - if ($this->currentObjectID == $pCoord) { - return true; - } - // Check if the requested entry still exists in cache - $success = wincache_ucache_exists($this->cachePrefix . $pCoord . '.cache'); - if ($success === false) { - // Entry no longer exists in Wincache, so clear it from the cache array - parent::deleteCacheData($pCoord); - throw new \PhpOffice\PhpSpreadsheet\Exception('Cell entry ' . $pCoord . ' no longer exists in WinCache'); - } - - return true; - } - - return false; - } - - /** - * Get cell at a specific coordinate. - * - * @param string $pCoord Coordinate of the cell - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return \PhpOffice\PhpSpreadsheet\Cell Cell that was found, or null if not found - */ - public function getCacheData($pCoord) - { - if ($pCoord === $this->currentObjectID) { - return $this->currentObject; - } - $this->storeData(); - - // Check if the entry that has been requested actually exists - $obj = null; - if (parent::isDataSet($pCoord)) { - $success = false; - $obj = wincache_ucache_get($this->cachePrefix . $pCoord . '.cache', $success); - if ($success === false) { - // Entry no longer exists in WinCache, so clear it from the cache array - parent::deleteCacheData($pCoord); - throw new \PhpOffice\PhpSpreadsheet\Exception('Cell entry ' . $pCoord . ' no longer exists in WinCache'); - } - } else { - // Return null if requested entry doesn't exist in cache - return null; - } - - // Set current entry to the requested entry - $this->currentObjectID = $pCoord; - $this->currentObject = unserialize($obj); - // Re-attach this as the cell's parent - $this->currentObject->attach($this); - - // Return requested entry - return $this->currentObject; - } - - /** - * Get a list of all cell addresses currently held in cache. - * - * @return string[] - */ - public function getCellList() - { - if ($this->currentObjectID !== null) { - $this->storeData(); - } - - return parent::getCellList(); - } - - /** - * Delete a cell in cache identified by coordinate address. - * - * @param string $pCoord Coordinate address of the cell to delete - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - */ - public function deleteCacheData($pCoord) - { - // Delete the entry from Wincache - wincache_ucache_delete($this->cachePrefix . $pCoord . '.cache'); - - // Delete the entry from our cell address array - parent::deleteCacheData($pCoord); - } - - /** - * Clone the cell collection. - * - * @param \PhpOffice\PhpSpreadsheet\Worksheet $parent The new worksheet that we're copying to - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - */ - public function copyCellCollection(\PhpOffice\PhpSpreadsheet\Worksheet $parent) - { - parent::copyCellCollection($parent); - // Get a new id for the new file name - $baseUnique = $this->getUniqueID(); - $newCachePrefix = substr(md5($baseUnique), 0, 8) . '.'; - $cacheList = $this->getCellList(); - foreach ($cacheList as $cellID) { - if ($cellID != $this->currentObjectID) { - $success = false; - $obj = wincache_ucache_get($this->cachePrefix . $cellID . '.cache', $success); - if ($success === false) { - // Entry no longer exists in WinCache, so clear it from the cache array - parent::deleteCacheData($cellID); - throw new \PhpOffice\PhpSpreadsheet\Exception('Cell entry ' . $cellID . ' no longer exists in Wincache'); - } - if (!wincache_ucache_add($newCachePrefix . $cellID . '.cache', $obj, $this->cacheTime)) { - $this->__destruct(); - throw new \PhpOffice\PhpSpreadsheet\Exception('Failed to store cell ' . $cellID . ' in Wincache'); - } - } - } - $this->cachePrefix = $newCachePrefix; - } - - /** - * Clear the cell collection and disconnect from our parent. - */ - public function unsetWorksheetCells() - { - if (!is_null($this->currentObject)) { - $this->currentObject->detach(); - $this->currentObject = $this->currentObjectID = null; - } - - // Flush the WinCache cache - $this->__destruct(); - - $this->cellCache = []; - - // detach ourself from the worksheet, so that it can then delete this object successfully - $this->parent = null; - } - - /** - * Initialise this new cell collection. - * - * @param \PhpOffice\PhpSpreadsheet\Worksheet $parent The worksheet for this cell collection - * @param mixed[] $arguments Additional initialisation arguments - */ - public function __construct(\PhpOffice\PhpSpreadsheet\Worksheet $parent, $arguments) - { - $cacheTime = (isset($arguments['cacheTime'])) ? $arguments['cacheTime'] : 600; - - if (is_null($this->cachePrefix)) { - $baseUnique = $this->getUniqueID(); - $this->cachePrefix = substr(md5($baseUnique), 0, 8) . '.'; - $this->cacheTime = $cacheTime; - - parent::__construct($parent); - } - } - - /** - * Destroy this cell collection. - */ - public function __destruct() - { - $cacheList = $this->getCellList(); - foreach ($cacheList as $cellID) { - wincache_ucache_delete($this->cachePrefix . $cellID . '.cache'); - } - } - - /** - * Identify whether the caching method is currently available - * Some methods are dependent on the availability of certain extensions being enabled in the PHP build. - * - * @return bool - */ - public static function cacheMethodIsAvailable() - { - if (!function_exists('wincache_ucache_add')) { - return false; - } - - return true; - } -} diff --git a/src/PhpSpreadsheet/CachedObjectStorageFactory.php b/src/PhpSpreadsheet/CachedObjectStorageFactory.php deleted file mode 100644 index 6b81eaf5..00000000 --- a/src/PhpSpreadsheet/CachedObjectStorageFactory.php +++ /dev/null @@ -1,228 +0,0 @@ - [], - self::CACHE_IN_MEMORY_GZIP => [], - self::CACHE_IN_MEMORY_SERIALIZED => [], - self::CACHE_IGBINARY => [], - self::CACHE_TO_PHPTEMP => [ - 'memoryCacheSize' => '1MB', - ], - self::CACHE_TO_DISCISAM => [ - 'dir' => null, - ], - self::CACHE_TO_APC => [ - 'cacheTime' => 600, - ], - self::CACHE_TO_MEMCACHE => [ - 'memcacheServer' => 'localhost', - 'memcachePort' => 11211, - 'cacheTime' => 600, - ], - self::CACHE_TO_WINCACHE => [ - 'cacheTime' => 600, - ], - self::CACHE_TO_SQLITE3 => [], - ]; - - /** - * Arguments for the active cache storage method. - * - * @var mixed[] - */ - private static $storageMethodParameters = []; - - /** - * Return the current cache storage method. - * - * @return string|null - **/ - public static function getCacheStorageMethod() - { - return self::$cacheStorageMethod; - } - - /** - * Return the current cache storage class. - * - * @return string - **/ - public static function getCacheStorageClass() - { - return self::$cacheStorageClass; - } - - /** - * Return the list of all possible cache storage methods. - * - * @return string[] - **/ - public static function getAllCacheStorageMethods() - { - return self::$storageMethods; - } - - /** - * Return the list of all available cache storage methods. - * - * @return string[] - **/ - public static function getCacheStorageMethods() - { - $activeMethods = []; - foreach (self::$storageMethods as $storageMethod) { - $cacheStorageClass = '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\' . $storageMethod; - if (call_user_func([$cacheStorageClass, 'cacheMethodIsAvailable'])) { - $activeMethods[] = $storageMethod; - } - } - - return $activeMethods; - } - - /** - * Identify the cache storage method to use. - * - * @param string $method Name of the method to use for cell cacheing - * @param mixed[] $arguments Additional arguments to pass to the cell caching class - * when instantiating - * - * @return bool - **/ - public static function initialize($method = self::CACHE_IN_MEMORY, $arguments = []) - { - if (!in_array($method, self::$storageMethods)) { - return false; - } - - $cacheStorageClass = '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\' . $method; - if (!call_user_func([$cacheStorageClass, 'cacheMethodIsAvailable'])) { - return false; - } - - self::$storageMethodParameters[$method] = self::$storageMethodDefaultParameters[$method]; - foreach ($arguments as $argument => $value) { - if (isset(self::$storageMethodParameters[$method][$argument])) { - self::$storageMethodParameters[$method][$argument] = $value; - } - } - - if (self::$cacheStorageMethod === null) { - self::$cacheStorageClass = '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\' . $method; - self::$cacheStorageMethod = $method; - } - - return true; - } - - /** - * Initialise the cache storage. - * - * @param Worksheet $parent Enable cell caching for this worksheet - * - * @return CachedObjectStorage\ICache - **/ - public static function getInstance(Worksheet $parent) - { - $cacheMethodIsAvailable = true; - if (self::$cacheStorageMethod === null) { - $cacheMethodIsAvailable = self::initialize(); - } - - if ($cacheMethodIsAvailable) { - $instance = new self::$cacheStorageClass( - $parent, - self::$storageMethodParameters[self::$cacheStorageMethod] - ); - if ($instance !== null) { - return $instance; - } - } - - return false; - } - - /** - * Clear the cache storage. - **/ - public static function finalize() - { - self::$cacheStorageMethod = null; - self::$cacheStorageClass = null; - self::$storageMethodParameters = []; - } -} diff --git a/src/PhpSpreadsheet/Calculation.php b/src/PhpSpreadsheet/Calculation.php index 8dc957d3..2d75f29a 100644 --- a/src/PhpSpreadsheet/Calculation.php +++ b/src/PhpSpreadsheet/Calculation.php @@ -3663,7 +3663,7 @@ class Calculation $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue)); } else { $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in current worksheet'); - if ($pCellParent->isDataSet($cellRef)) { + if ($pCellParent->has($cellRef)) { $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false); $pCell->attach($pCellParent); } else { diff --git a/src/PhpSpreadsheet/Cell.php b/src/PhpSpreadsheet/Cell.php index c035bcef..e01a473c 100644 --- a/src/PhpSpreadsheet/Cell.php +++ b/src/PhpSpreadsheet/Cell.php @@ -2,6 +2,8 @@ namespace PhpOffice\PhpSpreadsheet; +use PhpOffice\PhpSpreadsheet\Collection\Cells; + /** * Copyright (c) 2006 - 2016 PhpSpreadsheet. * @@ -67,9 +69,9 @@ class Cell private $dataType; /** - * Parent worksheet. + * Collection of cells. * - * @var CachedObjectStorage\CacheBase + * @var Cells */ private $parent; @@ -86,11 +88,13 @@ class Cell private $formulaAttributes; /** - * Send notification to the cache controller. - **/ - public function notifyCacheController() + * Update the cell into the cell collection. + * + * @return self + */ + public function updateInCollection() { - $this->parent->updateCacheData($this); + $this->parent->update($this); return $this; } @@ -100,7 +104,7 @@ class Cell $this->parent = null; } - public function attach(CachedObjectStorage\CacheBase $parent) + public function attach(Cells $parent) { $this->parent = $parent; } @@ -120,7 +124,7 @@ class Cell $this->value = $pValue; // Set worksheet cache - $this->parent = $pSheet->getCellCacheController(); + $this->parent = $pSheet->getCellCollection(); // Set datatype? if ($pDataType !== null) { @@ -160,7 +164,7 @@ class Cell */ public function getCoordinate() { - return $this->parent->getCurrentAddress(); + return $this->parent->getCurrentCoordinate(); } /** @@ -253,7 +257,7 @@ class Cell // set the datatype $this->dataType = $pDataType; - return $this->notifyCacheController(); + return $this->updateInCollection(); } /** @@ -313,7 +317,7 @@ class Cell $this->calculatedValue = (is_numeric($pValue)) ? (float) $pValue : $pValue; } - return $this->notifyCacheController(); + return $this->updateInCollection(); } /** @@ -355,7 +359,7 @@ class Cell } $this->dataType = $pDataType; - return $this->notifyCacheController(); + return $this->updateInCollection(); } /** @@ -417,7 +421,7 @@ class Cell $this->getWorksheet()->setDataValidation($this->getCoordinate(), $pDataValidation); - return $this->notifyCacheController(); + return $this->updateInCollection(); } /** @@ -469,13 +473,13 @@ class Cell $this->getWorksheet()->setHyperlink($this->getCoordinate(), $pHyperlink); - return $this->notifyCacheController(); + return $this->updateInCollection(); } /** - * Get parent worksheet. + * Get cell collection. * - * @return CachedObjectStorage\CacheBase + * @return Cells */ public function getParent() { @@ -555,9 +559,9 @@ class Cell */ public function rebindParent(Worksheet $parent) { - $this->parent = $parent->getCellCacheController(); + $this->parent = $parent->getCellCollection(); - return $this->notifyCacheController(); + return $this->updateInCollection(); } /** @@ -1031,7 +1035,7 @@ class Cell { $this->xfIndex = $pValue; - return $this->notifyCacheController(); + return $this->updateInCollection(); } /** diff --git a/src/PhpSpreadsheet/Collection/Cells.php b/src/PhpSpreadsheet/Collection/Cells.php new file mode 100644 index 00000000..137b6f90 --- /dev/null +++ b/src/PhpSpreadsheet/Collection/Cells.php @@ -0,0 +1,507 @@ +parent = $parent; + $this->cache = $cache; + $this->cachePrefix = $this->getUniqueID(); + } + + /** + * Return the parent worksheet for this cell collection. + * + * @return Worksheet + */ + public function getParent() + { + return $this->parent; + } + + /** + * Whether the collection holds a cell for the given coordinate. + * + * @param string $pCoord Coordinate of the cell to check + * + * @return bool + */ + public function has($pCoord) + { + if ($pCoord === $this->currentCoordinate) { + return true; + } + + // Check if the requested entry exists in the index + return isset($this->index[$pCoord]); + } + + /** + * Add or update a cell in the collection. + * + * @param Cell $cell Cell to update + * + * @throws \PhpOffice\PhpSpreadsheet\Exception + * + * @return Cell + */ + public function update(Cell $cell) + { + return $this->add($cell->getCoordinate(), $cell); + } + + /** + * Delete a cell in cache identified by coordinate. + * + * @param string $pCoord Coordinate of the cell to delete + * + * @throws \PhpOffice\PhpSpreadsheet\Exception + */ + public function delete($pCoord) + { + if ($pCoord === $this->currentCoordinate && !is_null($this->currentCell)) { + $this->currentCell->detach(); + $this->currentCoordinate = null; + $this->currentCell = null; + $this->currentCellIsDirty = false; + } + + unset($this->index[$pCoord]); + + // Delete the entry from cache + $this->cache->delete($this->cachePrefix . $pCoord); + } + + /** + * Get a list of all cell coordinates currently held in the collection. + * + * @return string[] + */ + public function getCoordinates() + { + return array_keys($this->index); + } + + /** + * Get a sorted list of all cell coordinates currently held in the collection by row and column. + * + * @return string[] + */ + public function getSortedCoordinates() + { + $sortKeys = []; + foreach ($this->getCoordinates() as $coord) { + sscanf($coord, '%[A-Z]%d', $column, $row); + $sortKeys[sprintf('%09d%3s', $row, $column)] = $coord; + } + ksort($sortKeys); + + return array_values($sortKeys); + } + + /** + * Get highest worksheet column and highest row that have cell records. + * + * @return array Highest column name and highest row number + */ + public function getHighestRowAndColumn() + { + // Lookup highest column and highest row + $col = ['A' => '1A']; + $row = [1]; + foreach ($this->getCoordinates() as $coord) { + sscanf($coord, '%[A-Z]%d', $c, $r); + $row[$r] = $r; + $col[$c] = strlen($c) . $c; + } + if (!empty($row)) { + // Determine highest column and row + $highestRow = max($row); + $highestColumn = substr(max($col), 1); + } + + return [ + 'row' => $highestRow, + 'column' => $highestColumn, + ]; + } + + /** + * Return the cell coordinate of the currently active cell object. + * + * @return string + */ + public function getCurrentCoordinate() + { + return $this->currentCoordinate; + } + + /** + * Return the column coordinate of the currently active cell object. + * + * @return string + */ + public function getCurrentColumn() + { + sscanf($this->currentCoordinate, '%[A-Z]%d', $column, $row); + + return $column; + } + + /** + * Return the row coordinate of the currently active cell object. + * + * @return int + */ + public function getCurrentRow() + { + sscanf($this->currentCoordinate, '%[A-Z]%d', $column, $row); + + return (int) $row; + } + + /** + * Get highest worksheet column. + * + * @param string $row Return the highest column for the specified row, + * or the highest column of any row if no row number is passed + * + * @return string Highest column name + */ + public function getHighestColumn($row = null) + { + if ($row == null) { + $colRow = $this->getHighestRowAndColumn(); + + return $colRow['column']; + } + + $columnList = [1]; + foreach ($this->getCoordinates() as $coord) { + sscanf($coord, '%[A-Z]%d', $c, $r); + if ($r != $row) { + continue; + } + $columnList[] = Cell::columnIndexFromString($c); + } + + return Cell::stringFromColumnIndex(max($columnList) - 1); + } + + /** + * Get highest worksheet row. + * + * @param string $column Return the highest row for the specified column, + * or the highest row of any column if no column letter is passed + * + * @return int Highest row number + */ + public function getHighestRow($column = null) + { + if ($column == null) { + $colRow = $this->getHighestRowAndColumn(); + + return $colRow['row']; + } + + $rowList = [0]; + foreach ($this->getCoordinates() as $coord) { + sscanf($coord, '%[A-Z]%d', $c, $r); + if ($c != $column) { + continue; + } + $rowList[] = $r; + } + + return max($rowList); + } + + /** + * Generate a unique ID for cache referencing. + * + * @return string Unique Reference + */ + private function getUniqueID() + { + return uniqid('phpspreadsheet-', true) . '-'; + } + + /** + * Clone the cell collection. + * + * @param Worksheet $parent The new worksheet that we're copying to + * + * @return self + */ + public function cloneCellCollection(Worksheet $parent) + { + $this->storeCurrentCell(); + $newCollection = clone $this; + + $newCollection->parent = $parent; + if (($newCollection->currentCell !== null) && (is_object($newCollection->currentCell))) { + $newCollection->currentCell->attach($this); + } + + // Get old values + $oldKeys = $newCollection->getAllCacheKeys(); + $oldValues = $newCollection->cache->getMultiple($oldKeys); + $newValues = []; + $oldCachePrefix = $newCollection->cachePrefix; + + // Change prefix + $newCollection->cachePrefix = $newCollection->getUniqueID(); + foreach ($oldValues as $oldKey => $value) { + $newValues[str_replace($oldCachePrefix, $newCollection->cachePrefix, $oldKey)] = clone $value; + } + + // Store new values + $stored = $newCollection->cache->setMultiple($newValues); + if (!$stored) { + $newCollection->__destruct(); + throw new \PhpOffice\PhpSpreadsheet\Exception('Failed to copy cells in cache'); + } + + return $newCollection; + } + + /** + * Remove a row, deleting all cells in that row. + * + * @param string $row Row number to remove + */ + public function removeRow($row) + { + foreach ($this->getCoordinates() as $coord) { + sscanf($coord, '%[A-Z]%d', $c, $r); + if ($r == $row) { + $this->delete($coord); + } + } + } + + /** + * Remove a column, deleting all cells in that column. + * + * @param string $column Column ID to remove + */ + public function removeColumn($column) + { + foreach ($this->getCoordinates() as $coord) { + sscanf($coord, '%[A-Z]%d', $c, $r); + if ($c == $column) { + $this->delete($coord); + } + } + } + + /** + * Store cell data in cache for the current cell object if it's "dirty", + * and the 'nullify' the current cell object. + * + * @throws \PhpOffice\PhpSpreadsheet\Exception + */ + private function storeCurrentCell() + { + if ($this->currentCellIsDirty && !empty($this->currentCoordinate)) { + $this->currentCell->detach(); + + $stored = $this->cache->set($this->cachePrefix . $this->currentCoordinate, $this->currentCell); + if (!$stored) { + $this->__destruct(); + throw new \PhpOffice\PhpSpreadsheet\Exception("Failed to store cell {$this->currentCoordinate} in cache"); + } + $this->currentCellIsDirty = false; + } + + $this->currentCoordinate = null; + $this->currentCell = null; + } + + /** + * Add or update a cell identified by its coordinate into the collection. + * + * @param string $pCoord Coordinate of the cell to update + * @param Cell $cell Cell to update + * + * @throws \PhpOffice\PhpSpreadsheet\Exception + * + * @return Cell + */ + public function add($pCoord, Cell $cell) + { + if ($pCoord !== $this->currentCoordinate) { + $this->storeCurrentCell(); + } + $this->index[$pCoord] = true; + + $this->currentCoordinate = $pCoord; + $this->currentCell = $cell; + $this->currentCellIsDirty = true; + + return $cell; + } + + /** + * Get cell at a specific coordinate. + * + * @param string $pCoord Coordinate of the cell + * + * @throws \PhpOffice\PhpSpreadsheet\Exception + * + * @return Cell Cell that was found, or null if not found + */ + public function get($pCoord) + { + if ($pCoord === $this->currentCoordinate) { + return $this->currentCell; + } + $this->storeCurrentCell(); + + // Return null if requested entry doesn't exist in collection + if (!$this->has($pCoord)) { + return null; + } + + // Check if the entry that has been requested actually exists + $cell = $this->cache->get($this->cachePrefix . $pCoord); + if ($cell === null) { + throw new \PhpOffice\PhpSpreadsheet\Exception("Cell entry {$pCoord} no longer exists in cache. This probably means that the cache was cleared by someone else."); + } + + // Set current entry to the requested entry + $this->currentCoordinate = $pCoord; + $this->currentCell = $cell; + // Re-attach this as the cell's parent + $this->currentCell->attach($this); + + // Return requested entry + return $this->currentCell; + } + + /** + * Clear the cell collection and disconnect from our parent. + */ + public function unsetWorksheetCells() + { + if (!is_null($this->currentCell)) { + $this->currentCell->detach(); + $this->currentCell = null; + $this->currentCoordinate = null; + } + + // Flush the cache + $this->__destruct(); + + $this->index = []; + + // detach ourself from the worksheet, so that it can then delete this object successfully + $this->parent = null; + } + + /** + * Destroy this cell collection. + */ + public function __destruct() + { + $this->cache->deleteMultiple($this->getAllCacheKeys()); + } + + /** + * Returns all known cache keys. + * + * @return string + */ + private function getAllCacheKeys() + { + $keys = []; + foreach ($this->getCoordinates() as $coordinate) { + $keys[] = $this->cachePrefix . $coordinate; + } + + return $keys; + } +} diff --git a/src/PhpSpreadsheet/Collection/CellsFactory.php b/src/PhpSpreadsheet/Collection/CellsFactory.php new file mode 100644 index 00000000..64a34b35 --- /dev/null +++ b/src/PhpSpreadsheet/Collection/CellsFactory.php @@ -0,0 +1,45 @@ +cache = []; + + return true; + } + + public function delete($key) + { + unset($this->cache[$key]); + + return true; + } + + public function deleteMultiple($keys) + { + foreach ($keys as $key) { + $this->delete($key); + } + + return true; + } + + public function get($key, $default = null) + { + if ($this->has($key)) { + return $this->cache[$key]; + } + + return $default; + } + + public function getMultiple($keys, $default = null) + { + $results = []; + foreach ($keys as $key) { + $results[$key] = $this->get($key, $default); + } + + return $results; + } + + public function has($key) + { + return array_key_exists($key, $this->cache); + } + + public function set($key, $value, $ttl = null) + { + $this->cache[$key] = $value; + + return true; + } + + public function setMultiple($values, $ttl = null) + { + foreach ($values as $key => $value) { + $this->set($key, $value); + } + + return true; + } +} diff --git a/src/PhpSpreadsheet/Helper/Migrator.php b/src/PhpSpreadsheet/Helper/Migrator.php index 9aa46c05..fa6ccd82 100644 --- a/src/PhpSpreadsheet/Helper/Migrator.php +++ b/src/PhpSpreadsheet/Helper/Migrator.php @@ -84,19 +84,7 @@ class Migrator 'PHPExcel_Writer_Excel2007_Workbook' => '\\PhpOffice\\PhpSpreadsheet\\Writer\\Xlsx\\Workbook', 'PHPExcel_Writer_Excel2007_Worksheet' => '\\PhpOffice\\PhpSpreadsheet\\Writer\\Xlsx\\Worksheet', 'PHPExcel_Writer_Excel2007_WriterPart' => '\\PhpOffice\\PhpSpreadsheet\\Writer\\Xlsx\\WriterPart', - 'PHPExcel_CachedObjectStorage_APC' => '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\APC', - 'PHPExcel_CachedObjectStorage_CacheBase' => '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\CacheBase', - 'PHPExcel_CachedObjectStorage_DiscISAM' => '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\DiscISAM', - 'PHPExcel_CachedObjectStorage_ICache' => '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\ICache', - 'PHPExcel_CachedObjectStorage_Igbinary' => '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\Igbinary', - 'PHPExcel_CachedObjectStorage_Memcache' => '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\Memcache', - 'PHPExcel_CachedObjectStorage_Memory' => '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\Memory', - 'PHPExcel_CachedObjectStorage_MemoryGZip' => '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\MemoryGZip', - 'PHPExcel_CachedObjectStorage_MemorySerialized' => '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\MemorySerialized', - 'PHPExcel_CachedObjectStorage_PHPTemp' => '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\PHPTemp', - 'PHPExcel_CachedObjectStorage_SQLite' => '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\SQLite3', - 'PHPExcel_CachedObjectStorage_SQLite3' => '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\SQLite3', - 'PHPExcel_CachedObjectStorage_Wincache' => '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorage\\Wincache', + 'PHPExcel_CachedObjectStorage_CacheBase' => '\\PhpOffice\\PhpSpreadsheet\\Collection\\Cells', 'PHPExcel_CalcEngine_CyclicReferenceStack' => '\\PhpOffice\\PhpSpreadsheet\\CalcEngine\\CyclicReferenceStack', 'PHPExcel_CalcEngine_Logger' => '\\PhpOffice\\PhpSpreadsheet\\CalcEngine\\Logger', 'PHPExcel_Calculation_Functions' => '\\PhpOffice\\PhpSpreadsheet\\Calculation\\Functions', @@ -201,7 +189,7 @@ class Migrator 'PHPExcel_Writer_PDF' => '\\PhpOffice\\PhpSpreadsheet\\Writer\\Pdf', 'PHPExcel_Writer_Excel5' => '\\PhpOffice\\PhpSpreadsheet\\Writer\\Xls', 'PHPExcel_Writer_Excel2007' => '\\PhpOffice\\PhpSpreadsheet\\Writer\\Xlsx', - 'PHPExcel_CachedObjectStorageFactory' => '\\PhpOffice\\PhpSpreadsheet\\CachedObjectStorageFactory', + 'PHPExcel_CachedObjectStorageFactory' => '\\PhpOffice\\PhpSpreadsheet\\Collection\\CellsFactory', 'PHPExcel_Calculation' => '\\PhpOffice\\PhpSpreadsheet\\Calculation', 'PHPExcel_Cell' => '\\PhpOffice\\PhpSpreadsheet\\Cell', 'PHPExcel_Chart' => '\\PhpOffice\\PhpSpreadsheet\\Chart', diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index f4073a5f..5de4d727 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -387,9 +387,9 @@ class ReferenceHelper public function insertNewBefore($pBefore = 'A1', $pNumCols = 0, $pNumRows = 0, Worksheet $pSheet = null) { $remove = ($pNumCols < 0 || $pNumRows < 0); - $aCellCollection = $pSheet->getCellCollection(); + $allCoordinates = $pSheet->getCoordinates(); - // Get coordinates of $pBefore + // Get coordinate of $pBefore $beforeColumn = 'A'; $beforeRow = 1; list($beforeColumn, $beforeRow) = Cell::coordinateFromString($pBefore); @@ -427,39 +427,39 @@ class ReferenceHelper } } - // Loop through cells, bottom-up, and change cell coordinates + // Loop through cells, bottom-up, and change cell coordinate if ($remove) { // It's faster to reverse and pop than to use unshift, especially with large cell collections - $aCellCollection = array_reverse($aCellCollection); + $allCoordinates = array_reverse($allCoordinates); } - while ($cellID = array_pop($aCellCollection)) { - $cell = $pSheet->getCell($cellID); + while ($coordinate = array_pop($allCoordinates)) { + $cell = $pSheet->getCell($coordinate); $cellIndex = Cell::columnIndexFromString($cell->getColumn()); if ($cellIndex - 1 + $pNumCols < 0) { continue; } - // New coordinates - $newCoordinates = Cell::stringFromColumnIndex($cellIndex - 1 + $pNumCols) . ($cell->getRow() + $pNumRows); + // New coordinate + $newCoordinate = Cell::stringFromColumnIndex($cellIndex - 1 + $pNumCols) . ($cell->getRow() + $pNumRows); // Should the cell be updated? Move value and cellXf index from one cell to another. if (($cellIndex >= $beforeColumnIndex) && ($cell->getRow() >= $beforeRow)) { // Update cell styles - $pSheet->getCell($newCoordinates)->setXfIndex($cell->getXfIndex()); + $pSheet->getCell($newCoordinate)->setXfIndex($cell->getXfIndex()); // Insert this cell at its new location if ($cell->getDataType() == Cell\DataType::TYPE_FORMULA) { // Formula should be adjusted - $pSheet->getCell($newCoordinates) + $pSheet->getCell($newCoordinate) ->setValue($this->updateFormulaReferences($cell->getValue(), $pBefore, $pNumCols, $pNumRows, $pSheet->getTitle())); } else { // Formula should not be adjusted - $pSheet->getCell($newCoordinates)->setValue($cell->getValue()); + $pSheet->getCell($newCoordinate)->setValue($cell->getValue()); } // Clear the original cell - $pSheet->getCellCacheController()->deleteCacheData($cellID); + $pSheet->getCellCollection()->delete($coordinate); } else { /* We don't need to update styles for rows/columns before our insertion position, but we do still need to adjust any formulae in those cells */ @@ -818,8 +818,8 @@ class ReferenceHelper } foreach ($spreadsheet->getWorksheetIterator() as $sheet) { - foreach ($sheet->getCellCollection(false) as $cellID) { - $cell = $sheet->getCell($cellID); + foreach ($sheet->getCoordinates(false) as $coordinate) { + $cell = $sheet->getCell($coordinate); if (($cell !== null) && ($cell->getDataType() == Cell\DataType::TYPE_FORMULA)) { $formula = $cell->getValue(); if (strpos($formula, $oldName) !== false) { @@ -886,10 +886,10 @@ class ReferenceHelper private function updateSingleCellReference($pCellReference = 'A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0) { if (strpos($pCellReference, ':') === false && strpos($pCellReference, ',') === false) { - // Get coordinates of $pBefore + // Get coordinate of $pBefore list($beforeColumn, $beforeRow) = Cell::coordinateFromString($pBefore); - // Get coordinates of $pCellReference + // Get coordinate of $pCellReference list($newColumn, $newRow) = Cell::coordinateFromString($pCellReference); // Verify which parts should be updated diff --git a/src/PhpSpreadsheet/Settings.php b/src/PhpSpreadsheet/Settings.php index d0b4838c..62371bdd 100644 --- a/src/PhpSpreadsheet/Settings.php +++ b/src/PhpSpreadsheet/Settings.php @@ -2,6 +2,9 @@ namespace PhpOffice\PhpSpreadsheet; +use PhpOffice\PhpSpreadsheet\Collection\Memory; +use Psr\SimpleCache\CacheInterface; + /** * Copyright (c) 2006 - 2016 PhpSpreadsheet. * @@ -37,7 +40,6 @@ class Settings private static $chartRenderers = [ self::CHART_RENDERER_JPGRAPH, ]; - private static $pdfRenderers = [ self::PDF_RENDERER_TCPDF, self::PDF_RENDERER_DOMPDF, @@ -77,37 +79,11 @@ class Settings private static $libXmlLoaderOptions = null; /** - * Return the name of the method that is currently configured for cell cacheing. + * The cache implementation to be used for cell collection. * - * @return string Name of the cacheing method + * @var CacheInterface */ - public static function getCacheStorageMethod() - { - return CachedObjectStorageFactory::getCacheStorageMethod(); - } - - /** - * Return the name of the class that is currently being used for cell cacheing. - * - * @return string Name of the class currently being used for cacheing - */ - public static function getCacheStorageClass() - { - return CachedObjectStorageFactory::getCacheStorageClass(); - } - - /** - * Set the method that should be used for cell caching. - * - * @param string $method Name of the caching method - * @param array $arguments Optional configuration arguments for the caching method - * - * @return bool Success or failure - */ - public static function setCacheStorageMethod($method = CachedObjectStorageFactory::CACHE_IN_MEMORY, $arguments = []) - { - return CachedObjectStorageFactory::initialize($method, $arguments); - } + private static $cache; /** * Set the locale code to use for formula translations and any special formatting. @@ -256,4 +232,28 @@ class Settings return self::$libXmlLoaderOptions; } + + /** + * Sets the implementation of cache that should be used for cell collection. + * + * @param CacheInterface $cache + */ + public static function setCache(CacheInterface $cache) + { + self::$cache = $cache; + } + + /** + * Gets the implementation of cache that should be used for cell collection. + * + * @return CacheInterface + */ + public static function getCache() + { + if (!self::$cache) { + self::$cache = new Memory(); + } + + return self::$cache; + } } diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php index 91f433d5..1893922f 100644 --- a/src/PhpSpreadsheet/Spreadsheet.php +++ b/src/PhpSpreadsheet/Spreadsheet.php @@ -786,8 +786,8 @@ class Spreadsheet $pSheet->rebindParent($this); // update the cellXfs - foreach ($pSheet->getCellCollection(false) as $cellID) { - $cell = $pSheet->getCell($cellID); + foreach ($pSheet->getCoordinates(false) as $coordinate) { + $cell = $pSheet->getCell($coordinate); $cell->setXfIndex($cell->getXfIndex() + $countCellXfs); } @@ -1010,8 +1010,8 @@ class Spreadsheet // then update cellXf indexes for cells foreach ($this->workSheetCollection as $worksheet) { - foreach ($worksheet->getCellCollection(false) as $cellID) { - $cell = $worksheet->getCell($cellID); + foreach ($worksheet->getCoordinates(false) as $coordinate) { + $cell = $worksheet->getCell($coordinate); $xfIndex = $cell->getXfIndex(); if ($xfIndex > $pIndex) { // decrease xf index by 1 @@ -1114,8 +1114,8 @@ class Spreadsheet foreach ($this->getWorksheetIterator() as $sheet) { // from cells - foreach ($sheet->getCellCollection(false) as $cellID) { - $cell = $sheet->getCell($cellID); + foreach ($sheet->getCoordinates(false) as $coordinate) { + $cell = $sheet->getCell($coordinate); ++$countReferencesCellXf[$cell->getXfIndex()]; } @@ -1158,8 +1158,8 @@ class Spreadsheet // update the xfIndex for all cells, row dimensions, column dimensions foreach ($this->getWorksheetIterator() as $sheet) { // for all cells - foreach ($sheet->getCellCollection(false) as $cellID) { - $cell = $sheet->getCell($cellID); + foreach ($sheet->getCoordinates(false) as $coordinate) { + $cell = $sheet->getCell($coordinate); $cell->setXfIndex($map[$cell->getXfIndex()]); } diff --git a/src/PhpSpreadsheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet.php index 4111bddb..002e929a 100644 --- a/src/PhpSpreadsheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet.php @@ -3,6 +3,8 @@ namespace PhpOffice\PhpSpreadsheet; use ArrayObject; +use PhpOffice\PhpSpreadsheet\Collection\Cells; +use PhpOffice\PhpSpreadsheet\Collection\CellsFactory; /** * Copyright (c) 2006 - 2016 PhpSpreadsheet. @@ -53,9 +55,9 @@ class Worksheet implements IComparable private $parent; /** - * Cacheable collection of cells. + * Collection of cells. * - * @var CachedObjectStorage_xxx + * @var Cells */ private $cellCollection; @@ -340,7 +342,7 @@ class Worksheet implements IComparable $this->setCodeName($this->getTitle()); $this->setSheetState(self::SHEETSTATE_VISIBLE); - $this->cellCollection = CachedObjectStorageFactory::getInstance($this); + $this->cellCollection = CellsFactory::getInstance($this); // Set page setup $this->pageSetup = new Worksheet\PageSetup(); // Set page margins @@ -387,11 +389,11 @@ class Worksheet implements IComparable } /** - * Return the cache controller for the cell collection. + * Return the cell collection. * - * @return CachedObjectStorage_xxx + * @return Cells */ - public function getCellCacheController() + public function getCellCollection() { return $this->cellCollection; } @@ -461,37 +463,23 @@ class Worksheet implements IComparable } /** - * Get collection of cells. + * Get a sorted list of all cell coordinates currently held in the collection by row and column. * - * @param bool $pSorted Also sort the cell collection? + * @param bool $sorted Also sort the cell collection? * - * @return Cell[] + * @return string[] */ - public function getCellCollection($pSorted = true) + public function getCoordinates($sorted = true) { - if ($pSorted) { - // Re-order cell collection - return $this->sortCellCollection(); - } - if ($this->cellCollection !== null) { - return $this->cellCollection->getCellList(); + if ($this->cellCollection == null) { + return []; } - return []; - } - - /** - * Sort collection of cells. - * - * @return Worksheet - */ - public function sortCellCollection() - { - if ($this->cellCollection !== null) { - return $this->cellCollection->getSortedCellList(); + if ($sorted) { + return $this->cellCollection->getSortedCoordinates(); } - return []; + return $this->cellCollection->getCoordinates(); } /** @@ -737,11 +725,11 @@ class Worksheet implements IComparable } // loop through all cells in the worksheet - foreach ($this->getCellCollection(false) as $cellID) { - $cell = $this->getCell($cellID, false); + foreach ($this->getCoordinates(false) as $coordinate) { + $cell = $this->getCell($coordinate, false); if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) { //Determine if cell is in merge range - $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentAddress()]); + $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]); //By default merged cells should be ignored $isMergedButProceed = false; @@ -1201,8 +1189,8 @@ class Worksheet implements IComparable public function getCell($pCoordinate = 'A1', $createIfNotExists = true) { // Check cell collection - if ($this->cellCollection->isDataSet(strtoupper($pCoordinate))) { - return $this->cellCollection->getCacheData($pCoordinate); + if ($this->cellCollection->has(strtoupper($pCoordinate))) { + return $this->cellCollection->get($pCoordinate); } // Worksheet reference? @@ -1251,8 +1239,8 @@ class Worksheet implements IComparable $columnLetter = Cell::stringFromColumnIndex($pColumn); $coordinate = $columnLetter . $pRow; - if ($this->cellCollection->isDataSet($coordinate)) { - return $this->cellCollection->getCacheData($coordinate); + if ($this->cellCollection->has($coordinate)) { + return $this->cellCollection->get($coordinate); } // Create new cell object, if required @@ -1268,10 +1256,8 @@ class Worksheet implements IComparable */ private function createNewCell($pCoordinate) { - $cell = $this->cellCollection->addCacheData( - $pCoordinate, - new Cell(null, Cell\DataType::TYPE_NULL, $this) - ); + $cell = new Cell(null, Cell\DataType::TYPE_NULL, $this); + $this->cellCollection->add($pCoordinate, $cell); $this->cellCollectionIsSorted = false; // Coordinates @@ -1344,7 +1330,7 @@ class Worksheet implements IComparable $aCoordinates = Cell::coordinateFromString($pCoordinate); // Cell exists? - return $this->cellCollection->isDataSet($pCoordinate); + return $this->cellCollection->has($pCoordinate); } /** @@ -2129,7 +2115,7 @@ class Worksheet implements IComparable $objReferenceHelper = ReferenceHelper::getInstance(); $objReferenceHelper->insertNewBefore('A' . ($pRow + $pNumRows), 0, -$pNumRows, $this); for ($r = 0; $r < $pNumRows; ++$r) { - $this->getCellCacheController()->removeRow($highestRow); + $this->getCellCollection()->removeRow($highestRow); --$highestRow; } } else { @@ -2157,7 +2143,7 @@ class Worksheet implements IComparable $objReferenceHelper = ReferenceHelper::getInstance(); $objReferenceHelper->insertNewBefore($pColumn . '1', -$pNumCols, 0, $this); for ($c = 0; $c < $pNumCols; ++$c) { - $this->getCellCacheController()->removeColumn($highestColumn); + $this->getCellCollection()->removeColumn($highestColumn); $highestColumn = Cell::stringFromColumnIndex(Cell::columnIndexFromString($highestColumn) - 2); } } else { @@ -2568,9 +2554,9 @@ class Worksheet implements IComparable $cRef = ($returnCellRef) ? $col : ++$c; // Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen // so we test and retrieve directly against cellCollection - if ($this->cellCollection->isDataSet($col . $row)) { + if ($this->cellCollection->has($col . $row)) { // Cell exists - $cell = $this->cellCollection->getCacheData($col . $row); + $cell = $this->cellCollection->get($col . $row); if ($cell->getValue() !== null) { if ($cell->getValue() instanceof RichText) { $returnValue[$rRef][$cRef] = $cell->getValue()->getPlainText(); @@ -2688,7 +2674,7 @@ class Worksheet implements IComparable public function garbageCollect() { // Flush cache - $this->cellCollection->getCacheData('A1'); + $this->cellCollection->get('A1'); // Lookup highest column and highest row if cells are cleaned $colRow = $this->cellCollection->getHighestRowAndColumn(); @@ -2973,8 +2959,7 @@ class Worksheet implements IComparable if (is_object($val) || (is_array($val))) { if ($key == 'cellCollection') { - $newCollection = clone $this->cellCollection; - $newCollection->copyCellCollection($this); + $newCollection = $this->cellCollection->cloneCellCollection($this); $this->cellCollection = $newCollection; } elseif ($key == 'drawingCollection') { $newCollection = new ArrayObject(); diff --git a/src/PhpSpreadsheet/Writer/Xls.php b/src/PhpSpreadsheet/Writer/Xls.php index b7845ae3..2e228ad2 100644 --- a/src/PhpSpreadsheet/Writer/Xls.php +++ b/src/PhpSpreadsheet/Writer/Xls.php @@ -148,8 +148,8 @@ class Xls extends BaseWriter implements IWriter // add fonts from rich text eleemnts for ($i = 0; $i < $countSheets; ++$i) { - foreach ($this->writerWorksheets[$i]->phpSheet->getCellCollection() as $cellID) { - $cell = $this->writerWorksheets[$i]->phpSheet->getCell($cellID); + foreach ($this->writerWorksheets[$i]->phpSheet->getCoordinates() as $coordinate) { + $cell = $this->writerWorksheets[$i]->phpSheet->getCell($coordinate); $cVal = $cell->getValue(); if ($cVal instanceof \PhpOffice\PhpSpreadsheet\RichText) { $elements = $cVal->getRichTextElements(); diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php index 75d33633..f4a36b4a 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -400,13 +400,12 @@ class Worksheet extends BIFFwriter } // Write Cells - foreach ($phpSheet->getCellCollection() as $cellID) { - $cell = $phpSheet->getCell($cellID); + foreach ($phpSheet->getCoordinates() as $coordinate) { + $cell = $phpSheet->getCell($coordinate); $row = $cell->getRow() - 1; $column = \PhpOffice\PhpSpreadsheet\Cell::columnIndexFromString($cell->getColumn()) - 1; // Don't break Excel! -// if ($row + 1 > 65536 or $column + 1 > 256) { if ($row > 65535 || $column > 255) { break; } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php b/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php index fc780307..7a03dc66 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php @@ -53,8 +53,8 @@ class StringTable extends WriterPart $aFlippedStringTable = $this->flipStringTable($aStringTable); // Loop through cells - foreach ($pSheet->getCellCollection() as $cellID) { - $cell = $pSheet->getCell($cellID); + foreach ($pSheet->getCoordinates() as $coordinate) { + $cell = $pSheet->getCell($coordinate); $cellValue = $cell->getValue(); if (!is_object($cellValue) && ($cellValue !== null) && diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 4bea3a71..b1e8973c 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -995,9 +995,9 @@ class Worksheet extends WriterPart // Loop through cells $cellsByRow = []; - foreach ($pSheet->getCellCollection() as $cellID) { - $cellAddress = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($cellID); - $cellsByRow[$cellAddress[1]][] = $cellID; + foreach ($pSheet->getCoordinates() as $coordinate) { + $cellAddress = \PhpOffice\PhpSpreadsheet\Cell::coordinateFromString($coordinate); + $cellsByRow[$cellAddress[1]][] = $coordinate; } $currentRow = 0; diff --git a/tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php b/tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php index 4ac37aba..2f8b29a3 100644 --- a/tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php +++ b/tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php @@ -2,10 +2,10 @@ namespace PhpOffice\PhpSpreadsheetTests\Cell; -use PhpOffice\PhpSpreadsheet\CachedObjectStorage\Memory; use PhpOffice\PhpSpreadsheet\Cell; use PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder; use PhpOffice\PhpSpreadsheet\Cell\DataType; +use PhpOffice\PhpSpreadsheet\Collection\Cells; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Worksheet; @@ -42,12 +42,12 @@ class AdvancedValueBinderTest extends \PHPUnit_Framework_TestCase public function testCurrency($value, $valueBinded, $format, $thousandsSeparator, $decimalSeparator, $currencyCode) { $sheet = $this->getMockBuilder(Worksheet::class) - ->setMethods(['getStyle', 'getNumberFormat', 'setFormatCode', 'getCellCacheController']) + ->setMethods(['getStyle', 'getNumberFormat', 'setFormatCode', 'getCellCollection']) ->getMock(); - $cache = $this->getMockBuilder(Memory::class) + $cellCollection = $this->getMockBuilder(Cells::class) ->disableOriginalConstructor() ->getMock(); - $cache->expects($this->any()) + $cellCollection->expects($this->any()) ->method('getParent') ->will($this->returnValue($sheet)); @@ -62,8 +62,8 @@ class AdvancedValueBinderTest extends \PHPUnit_Framework_TestCase ->with($format) ->will($this->returnSelf()); $sheet->expects($this->any()) - ->method('getCellCacheController') - ->will($this->returnValue($cache)); + ->method('getCellCollection') + ->will($this->returnValue($cellCollection)); StringHelper::setCurrencyCode($currencyCode); StringHelper::setDecimalSeparator($decimalSeparator); diff --git a/tests/PhpSpreadsheetTests/Collection/CellsTest.php b/tests/PhpSpreadsheetTests/Collection/CellsTest.php new file mode 100644 index 00000000..6b91ec6f --- /dev/null +++ b/tests/PhpSpreadsheetTests/Collection/CellsTest.php @@ -0,0 +1,118 @@ +getActiveSheet(); + $collection = $sheet->getCellCollection(); + + // Assert empty state + $this->assertEquals([], $collection->getCoordinates(), 'cell list should be empty'); + $this->assertEquals([], $collection->getSortedCoordinates(), 'sorted cell list should be empty'); + $this->assertNull($collection->get('B2'), 'getting non-existing cell must return null'); + $this->assertFalse($collection->has('B2'), 'non-existing cell should be non-existent'); + + // Add one cell + $cell1 = $sheet->getCell('B2'); + $this->assertSame($cell1, $collection->add('B2', $cell1), 'adding a cell should return the cell'); + + // Assert cell presence + $this->assertEquals(['B2'], $collection->getCoordinates(), 'cell list should contains the cell'); + $this->assertEquals(['B2'], $collection->getSortedCoordinates(), 'sorted cell list contains the cell'); + $this->assertSame($cell1, $collection->get('B2'), 'should get exact same object'); + $this->assertTrue($collection->has('B2'), 'cell should exists'); + + // Add a second cell + $cell2 = $sheet->getCell('A1'); + $this->assertSame($cell2, $collection->add('A1', $cell2), 'adding a second cell should return the cell'); + $this->assertEquals(['B2', 'A1'], $collection->getCoordinates(), 'cell list should contains the cell'); + $this->assertEquals(['A1', 'B2'], $collection->getSortedCoordinates(), 'sorted cell list contains the cell'); + + // Assert collection copy + $sheet2 = $spreadsheet->createSheet(); + $collection2 = $collection->cloneCellCollection($sheet2); + $this->assertTrue($collection2->has('A1')); + $copiedCell2 = $collection2->get('A1'); + $this->assertNotSame($cell2, $copiedCell2, 'copied cell should not be the same object any more'); + $this->assertSame($collection2, $copiedCell2->getParent(), 'copied cell should be owned by the copied collection'); + $this->assertSame('A1', $copiedCell2->getCoordinate(), 'copied cell should keep attributes'); + + // Assert deletion + $collection->delete('B2'); + $this->assertFalse($collection->has('B2'), 'cell should have been deleted'); + $this->assertEquals(['A1'], $collection->getCoordinates(), 'cell list should contains the cell'); + + // Assert update + $cell2 = $sheet->getCell('A1'); + $this->assertSame($sheet->getCellCollection(), $collection); + $this->assertSame($cell2, $collection->update($cell2), 'should update existing cell'); + + $cell3 = $sheet->getCell('C3'); + $this->assertSame($cell3, $collection->update($cell3), 'should silently add non-existing cell'); + $this->assertEquals(['A1', 'C3'], $collection->getCoordinates(), 'cell list should contains the cell'); + } + + public function testCacheLastCell() + { + $workbook = new Spreadsheet(); + $cells = ['A1', 'A2']; + $sheet = $workbook->getActiveSheet(); + $sheet->setCellValue('A1', 1); + $sheet->setCellValue('A2', 2); + $this->assertEquals($cells, $sheet->getCoordinates(), 'list should include last added cell'); + } + + public function testCanGetCellAfterAnotherIsDeleted() + { + $workbook = new Spreadsheet(); + $sheet = $workbook->getActiveSheet(); + $collection = $sheet->getCellCollection(); + $sheet->setCellValue('A1', 1); + $sheet->setCellValue('A2', 1); + $collection->delete('A1'); + $sheet->setCellValue('A3', 1); + $this->assertNotNull($collection->get('A2'), 'should be able to get back the cell even when another cell was deleted while this one was the current one'); + } + + /** + * @expectedException \PhpOffice\PhpSpreadsheet\Exception + */ + public function testThrowsWhenCellCannotBeRetrievedFromCache() + { + $collection = $this->getMockBuilder(Cells::class) + ->setConstructorArgs([new Worksheet(), new Memory()]) + ->setMethods(['has']) + ->getMock(); + + $collection->method('has') + ->willReturn(true); + + $collection->get('A2'); + } + + /** + * @expectedException \PhpOffice\PhpSpreadsheet\Exception + */ + public function testThrowsWhenCellCannotBeStoredInCache() + { + $cache = $this->createMock(Memory::class); + $cell = $this->createMock(Cell::class); + $cache->method('set') + ->willReturn(false); + + $collection = new Cells(new Worksheet(), $cache); + + $collection->add('A1', $cell); + $collection->add('A2', $cell); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/OdsTest.php b/tests/PhpSpreadsheetTests/Reader/OdsTest.php index 8556c906..ccda9958 100644 --- a/tests/PhpSpreadsheetTests/Reader/OdsTest.php +++ b/tests/PhpSpreadsheetTests/Reader/OdsTest.php @@ -205,7 +205,7 @@ class OdsTest extends \PHPUnit_Framework_TestCase public function testReadBoldItalicUnderline() { - $this->markTestSkipped('Features not implemented yet'); + $this->markTestIncomplete('Features not implemented yet'); $spreadsheet = $this->loadOOCalcTestFile(); $firstSheet = $spreadsheet->getSheet(0); diff --git a/tests/PhpSpreadsheetTests/Worksheet/AutoFilterTest.php b/tests/PhpSpreadsheetTests/Worksheet/AutoFilterTest.php index 6850b2fe..d1416b2d 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/AutoFilterTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/AutoFilterTest.php @@ -2,7 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Worksheet; -use PhpOffice\PhpSpreadsheet\CachedObjectStorage\Memory; +use PhpOffice\PhpSpreadsheet\Collection\Cells; use PhpOffice\PhpSpreadsheet\Worksheet; use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter; use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column; @@ -12,19 +12,19 @@ class AutoFilterTest extends \PHPUnit_Framework_TestCase private $testInitialRange = 'H2:O256'; private $testAutoFilterObject; private $mockWorksheetObject; - private $mockCacheController; + private $cellCollection; public function setUp() { $this->mockWorksheetObject = $this->getMockBuilder(Worksheet::class) ->disableOriginalConstructor() ->getMock(); - $this->mockCacheController = $this->getMockBuilder(Memory::class) + $this->cellCollection = $this->getMockBuilder(Cells::class) ->disableOriginalConstructor() ->getMock(); $this->mockWorksheetObject->expects($this->any()) - ->method('getCellCacheController') - ->will($this->returnValue($this->mockCacheController)); + ->method('getCellCollection') + ->will($this->returnValue($this->cellCollection)); $this->testAutoFilterObject = new AutoFilter($this->testInitialRange, $this->mockWorksheetObject); } diff --git a/tests/PhpSpreadsheetTests/Worksheet/CellCollectionTest.php b/tests/PhpSpreadsheetTests/Worksheet/CellCollectionTest.php deleted file mode 100644 index 164c0ea8..00000000 --- a/tests/PhpSpreadsheetTests/Worksheet/CellCollectionTest.php +++ /dev/null @@ -1,24 +0,0 @@ -getActiveSheet(); - $worksheet->setCellValue('A1', 1); - $worksheet->setCellValue('A2', 2); - $this->assertEquals($cells, $worksheet->getCellCollection(), "Cache method \"$method\"."); - CachedObjectStorageFactory::finalize(); - } - } -}