Moved readers to iterators
Instead of the hasNext() / next() syntax, readers now implements the PHP iterator pattern. It allows readers to be used with a foreach() loop. All readers now share the same structure (CSV is treated as having exactly one sheet): - one concrete Reader - one SheetIterator, exposed by the Reader - one or more Sheets, returned at every iteration - one RowIterator, exposed by the Sheet Introducing the concept of sheets for CSV may be kind of confusing but it makes Spout way more consistent. Also, this confusion may be resolved by creating a wrapper around the readers if needed. -- This commit does not delete the old files, not change the folder structure for Writers. This will be done in another commit.
This commit is contained in:
1 parent
322c3d0738
commit
ae3ee357ff
27 files changed
+3112
No files matched your search
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace Box\Spout\Reader\XLSX\Helper\SharedStringsCaching;
|
||||
|
||||
use Box\Spout\Common\Helper\FileSystemHelper;
|
||||
use Box\Spout\Common\Helper\GlobalFunctionsHelper;
|
||||
use Box\Spout\Reader\Exception\SharedStringNotFoundException;
|
||||
|
||||
/**
|
||||
* Class FileBasedStrategy
|
||||
*
|
||||
* This class implements the file-based caching strategy for shared strings.
|
||||
* Shared strings are stored in small files (with a max number of strings per file).
|
||||
* This strategy is slower than an in-memory strategy but is used to avoid out of memory crashes.
|
||||
*
|
||||
* @package Box\Spout\Reader\XLSX\Helper\SharedStringsCaching
|
||||
*/
|
||||
class FileBasedStrategy implements CachingStrategyInterface
|
||||
{
|
||||
/** Value to use to escape the line feed character ("\n") */
|
||||
const ESCAPED_LINE_FEED_CHARACTER = '_x000A_';
|
||||
|
||||
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
|
||||
protected $globalFunctionsHelper;
|
||||
|
||||
/** @var \Box\Spout\Common\Helper\FileSystemHelper Helper to perform file system operations */
|
||||
protected $fileSystemHelper;
|
||||
|
||||
/**
|
||||
* @var int Maximum number of strings that can be stored in one temp file
|
||||
* @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE
|
||||
*/
|
||||
protected $maxNumStringsPerTempFile;
|
||||
|
||||
/** @var resource Pointer to the last temp file a shared string was written to */
|
||||
protected $tempFilePointer;
|
||||
|
||||
/**
|
||||
* @var string Path of the temporary file whose contents is currently stored in memory
|
||||
* @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE
|
||||
*/
|
||||
protected $inMemoryTempFilePath;
|
||||
|
||||
/**
|
||||
* @var string Contents of the temporary file that was last read
|
||||
* @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE
|
||||
*/
|
||||
protected $inMemoryTempFileContents;
|
||||
|
||||
/**
|
||||
* @param string|null $tempFolder Temporary folder where the temporary files to store shared strings will be stored
|
||||
* @param int $maxNumStringsPerTempFile Maximum number of strings that can be stored in one temp file
|
||||
*/
|
||||
public function __construct($tempFolder, $maxNumStringsPerTempFile)
|
||||
{
|
||||
$rootTempFolder = ($tempFolder) ?: sys_get_temp_dir();
|
||||
$this->fileSystemHelper = new FileSystemHelper($rootTempFolder);
|
||||
$this->tempFolder = $this->fileSystemHelper->createFolder($rootTempFolder, uniqid('sharedstrings'));
|
||||
|
||||
$this->maxNumStringsPerTempFile = $maxNumStringsPerTempFile;
|
||||
|
||||
$this->globalFunctionsHelper = new GlobalFunctionsHelper();
|
||||
$this->tempFilePointer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given string to the cache.
|
||||
*
|
||||
* @param string $sharedString The string to be added to the cache
|
||||
* @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
|
||||
* @return void
|
||||
*/
|
||||
public function addStringForIndex($sharedString, $sharedStringIndex)
|
||||
{
|
||||
$tempFilePath = $this->getSharedStringTempFilePath($sharedStringIndex);
|
||||
|
||||
if (!$this->globalFunctionsHelper->file_exists($tempFilePath)) {
|
||||
if ($this->tempFilePointer) {
|
||||
$this->globalFunctionsHelper->fclose($this->tempFilePointer);
|
||||
}
|
||||
$this->tempFilePointer = $this->globalFunctionsHelper->fopen($tempFilePath, 'w');
|
||||
}
|
||||
|
||||
// The shared string retrieval logic expects each cell data to be on one line only
|
||||
// Encoding the line feed character allows to preserve this assumption
|
||||
$lineFeedEncodedSharedString = $this->escapeLineFeed($sharedString);
|
||||
|
||||
$this->globalFunctionsHelper->fwrite($this->tempFilePointer, $lineFeedEncodedSharedString . PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path for the temp file that should contain the string for the given index
|
||||
*
|
||||
* @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
|
||||
* @return string The temp file path for the given index
|
||||
*/
|
||||
protected function getSharedStringTempFilePath($sharedStringIndex)
|
||||
{
|
||||
$numTempFile = intval($sharedStringIndex / $this->maxNumStringsPerTempFile);
|
||||
return $this->tempFolder . '/sharedstrings' . $numTempFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the cache after the last shared string was added.
|
||||
* This prevents any additional string from being added to the cache.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function closeCache()
|
||||
{
|
||||
// close pointer to the last temp file that was written
|
||||
if ($this->tempFilePointer) {
|
||||
$this->globalFunctionsHelper->fclose($this->tempFilePointer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the string located at the given index from the cache.
|
||||
*
|
||||
* @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
|
||||
* @return string The shared string at the given index
|
||||
* @throws \Box\Spout\Reader\Exception\SharedStringNotFoundException If no shared string found for the given index
|
||||
*/
|
||||
public function getStringAtIndex($sharedStringIndex)
|
||||
{
|
||||
$tempFilePath = $this->getSharedStringTempFilePath($sharedStringIndex);
|
||||
$indexInFile = $sharedStringIndex % $this->maxNumStringsPerTempFile;
|
||||
|
||||
if (!$this->globalFunctionsHelper->file_exists($tempFilePath)) {
|
||||
throw new SharedStringNotFoundException("Shared string temp file not found: $tempFilePath ; for index: $sharedStringIndex");
|
||||
}
|
||||
|
||||
if ($this->inMemoryTempFilePath !== $tempFilePath) {
|
||||
// free memory
|
||||
unset($this->inMemoryTempFileContents);
|
||||
|
||||
$this->inMemoryTempFileContents = explode(PHP_EOL, $this->globalFunctionsHelper->file_get_contents($tempFilePath));
|
||||
$this->inMemoryTempFilePath = $tempFilePath;
|
||||
}
|
||||
|
||||
$sharedString = null;
|
||||
if (array_key_exists($indexInFile, $this->inMemoryTempFileContents)) {
|
||||
$escapedSharedString = $this->inMemoryTempFileContents[$indexInFile];
|
||||
$sharedString = $this->unescapeLineFeed($escapedSharedString);
|
||||
}
|
||||
|
||||
if ($sharedString === null) {
|
||||
throw new SharedStringNotFoundException("Shared string not found for index: $sharedStringIndex");
|
||||
}
|
||||
|
||||
return rtrim($sharedString, PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes the line feed characters (\n)
|
||||
*
|
||||
* @param string $unescapedString
|
||||
* @return string
|
||||
*/
|
||||
private function escapeLineFeed($unescapedString)
|
||||
{
|
||||
return str_replace("\n", self::ESCAPED_LINE_FEED_CHARACTER, $unescapedString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescapes the line feed characters (\n)
|
||||
*
|
||||
* @param string $escapedString
|
||||
* @return string
|
||||
*/
|
||||
private function unescapeLineFeed($escapedString)
|
||||
{
|
||||
return str_replace(self::ESCAPED_LINE_FEED_CHARACTER, "\n", $escapedString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the cache, freeing memory and removing any created artifacts
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearCache()
|
||||
{
|
||||
if ($this->tempFolder) {
|
||||
$this->fileSystemHelper->deleteFolderRecursively($this->tempFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user