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(); - } - } -}