Differences Between: [Versions 402 and 403]
1 <?php 2 3 declare(strict_types=1); 4 5 namespace OpenSpout\Reader\XLSX\Manager\SharedStringsCaching; 6 7 use OpenSpout\Common\Helper\FileSystemHelper; 8 use OpenSpout\Reader\Exception\SharedStringNotFoundException; 9 10 /** 11 * This class implements the file-based caching strategy for shared strings. 12 * Shared strings are stored in small files (with a max number of strings per file). 13 * This strategy is slower than an in-memory strategy but is used to avoid out of memory crashes. 14 * 15 * @internal 16 */ 17 final class FileBasedStrategy implements CachingStrategyInterface 18 { 19 /** 20 * Value to use to escape the line feed character ("\n"). 21 */ 22 public const ESCAPED_LINE_FEED_CHARACTER = '_x000A_'; 23 24 /** @var FileSystemHelper Helper to perform file system operations */ 25 private FileSystemHelper $fileSystemHelper; 26 27 /** @var string Temporary folder where the temporary files will be created */ 28 private string $tempFolder; 29 30 /** 31 * @var int Maximum number of strings that can be stored in one temp file 32 * 33 * @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE 34 */ 35 private int $maxNumStringsPerTempFile; 36 37 /** @var null|resource Pointer to the last temp file a shared string was written to */ 38 private $tempFilePointer; 39 40 /** 41 * @var string Path of the temporary file whose contents is currently stored in memory 42 * 43 * @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE 44 */ 45 private string $inMemoryTempFilePath = ''; 46 47 /** 48 * @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE 49 * 50 * @var string[] Contents of the temporary file that was last read 51 */ 52 private array $inMemoryTempFileContents; 53 54 /** 55 * @param string $tempFolder Temporary folder where the temporary files to store shared strings will be stored 56 * @param int $maxNumStringsPerTempFile Maximum number of strings that can be stored in one temp file 57 */ 58 public function __construct(string $tempFolder, int $maxNumStringsPerTempFile) 59 { 60 $this->fileSystemHelper = new FileSystemHelper($tempFolder); 61 $this->tempFolder = $this->fileSystemHelper->createFolder($tempFolder, uniqid('sharedstrings')); 62 63 $this->maxNumStringsPerTempFile = $maxNumStringsPerTempFile; 64 65 $this->tempFilePointer = null; 66 } 67 68 /** 69 * Adds the given string to the cache. 70 * 71 * @param string $sharedString The string to be added to the cache 72 * @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file 73 */ 74 public function addStringForIndex(string $sharedString, int $sharedStringIndex): void 75 { 76 $tempFilePath = $this->getSharedStringTempFilePath($sharedStringIndex); 77 78 if (!file_exists($tempFilePath)) { 79 if (null !== $this->tempFilePointer) { 80 fclose($this->tempFilePointer); 81 } 82 $resource = fopen($tempFilePath, 'w'); 83 \assert(false !== $resource); 84 $this->tempFilePointer = $resource; 85 } 86 87 // The shared string retrieval logic expects each cell data to be on one line only 88 // Encoding the line feed character allows to preserve this assumption 89 $lineFeedEncodedSharedString = $this->escapeLineFeed($sharedString); 90 91 fwrite($this->tempFilePointer, $lineFeedEncodedSharedString.PHP_EOL); 92 } 93 94 /** 95 * Closes the cache after the last shared string was added. 96 * This prevents any additional string from being added to the cache. 97 */ 98 public function closeCache(): void 99 { 100 // close pointer to the last temp file that was written 101 if (null !== $this->tempFilePointer) { 102 fclose($this->tempFilePointer); 103 } 104 } 105 106 /** 107 * Returns the string located at the given index from the cache. 108 * 109 * @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file 110 * 111 * @return string The shared string at the given index 112 * 113 * @throws \OpenSpout\Reader\Exception\SharedStringNotFoundException If no shared string found for the given index 114 */ 115 public function getStringAtIndex(int $sharedStringIndex): string 116 { 117 $tempFilePath = $this->getSharedStringTempFilePath($sharedStringIndex); 118 $indexInFile = $sharedStringIndex % $this->maxNumStringsPerTempFile; 119 120 if (!file_exists($tempFilePath)) { 121 throw new SharedStringNotFoundException("Shared string temp file not found: {$tempFilePath} ; for index: {$sharedStringIndex}"); 122 } 123 124 if ($this->inMemoryTempFilePath !== $tempFilePath) { 125 $tempFilePath = realpath($tempFilePath); 126 \assert(false !== $tempFilePath); 127 $contents = file_get_contents($tempFilePath); 128 \assert(false !== $contents); 129 $this->inMemoryTempFileContents = explode(PHP_EOL, $contents); 130 $this->inMemoryTempFilePath = $tempFilePath; 131 } 132 133 $sharedString = null; 134 135 // Using isset here because it is way faster than array_key_exists... 136 if (isset($this->inMemoryTempFileContents[$indexInFile])) { 137 $escapedSharedString = $this->inMemoryTempFileContents[$indexInFile]; 138 $sharedString = $this->unescapeLineFeed($escapedSharedString); 139 } 140 141 if (null === $sharedString) { 142 throw new SharedStringNotFoundException("Shared string not found for index: {$sharedStringIndex}"); 143 } 144 145 return rtrim($sharedString, PHP_EOL); 146 } 147 148 /** 149 * Destroys the cache, freeing memory and removing any created artifacts. 150 */ 151 public function clearCache(): void 152 { 153 $this->fileSystemHelper->deleteFolderRecursively($this->tempFolder); 154 } 155 156 /** 157 * Returns the path for the temp file that should contain the string for the given index. 158 * 159 * @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file 160 * 161 * @return string The temp file path for the given index 162 */ 163 private function getSharedStringTempFilePath(int $sharedStringIndex): string 164 { 165 $numTempFile = (int) ($sharedStringIndex / $this->maxNumStringsPerTempFile); 166 167 return $this->tempFolder.\DIRECTORY_SEPARATOR.'sharedstrings'.$numTempFile; 168 } 169 170 /** 171 * Escapes the line feed characters (\n). 172 */ 173 private function escapeLineFeed(string $unescapedString): string 174 { 175 return str_replace("\n", self::ESCAPED_LINE_FEED_CHARACTER, $unescapedString); 176 } 177 178 /** 179 * Unescapes the line feed characters (\n). 180 */ 181 private function unescapeLineFeed(string $escapedString): string 182 { 183 return str_replace(self::ESCAPED_LINE_FEED_CHARACTER, "\n", $escapedString); 184 } 185 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body