1 | <?php |
||
10 | class CachingStrategyFactory |
||
11 | { |
||
12 | /** |
||
13 | * The memory amount needed to store a string was obtained empirically from this data: |
||
14 | * |
||
15 | * ------------------------------------ |
||
16 | * | Number of chars⁺ | Memory needed | |
||
17 | * ------------------------------------ |
||
18 | * | 3,000 | 1 MB | |
||
19 | * | 15,000 | 2 MB | |
||
20 | * | 30,000 | 5 MB | |
||
21 | * | 75,000 | 11 MB | |
||
22 | * | 150,000 | 21 MB | |
||
23 | * | 300,000 | 43 MB | |
||
24 | * | 750,000 | 105 MB | |
||
25 | * | 1,500,000 | 210 MB | |
||
26 | * | 2,250,000 | 315 MB | |
||
27 | * | 3,000,000 | 420 MB | |
||
28 | * | 4,500,000 | 630 MB | |
||
29 | * ------------------------------------ |
||
30 | * |
||
31 | * ⁺ All characters were 1 byte long |
||
32 | * |
||
33 | * This gives a linear graph where each 1-byte character requires about 150 bytes to be stored. |
||
34 | * Given that some characters can take up to 4 bytes, we need 600 bytes per character to be safe. |
||
35 | * Also, there is on average about 20 characters per cell (this is entirely empirical data...). |
||
36 | * |
||
37 | * This means that in order to store one shared string in memory, the memory amount needed is: |
||
38 | * => 20 * 600 ≈ 12KB |
||
39 | */ |
||
40 | const AMOUNT_MEMORY_NEEDED_PER_STRING_IN_KB = 12; |
||
41 | |||
42 | /** |
||
43 | * To avoid running out of memory when extracting a huge number of shared strings, they can be saved to temporary files |
||
44 | * instead of in memory. Then, when accessing a string, the corresponding file contents will be loaded in memory |
||
45 | * and the string will be quickly retrieved. |
||
46 | * The performance bottleneck is not when creating these temporary files, but rather when loading their content. |
||
47 | * Because the contents of the last loaded file stays in memory until another file needs to be loaded, it works |
||
48 | * best when the indexes of the shared strings are sorted in the sheet data. |
||
49 | * 10,000 was chosen because it creates small files that are fast to be loaded in memory. |
||
50 | */ |
||
51 | const MAX_NUM_STRINGS_PER_TEMP_FILE = 10000; |
||
52 | |||
53 | /** @var CachingStrategyFactory|null Singleton instance */ |
||
54 | protected static $instance = null; |
||
55 | |||
56 | /** |
||
57 | * Private constructor for singleton |
||
58 | */ |
||
59 | 1 | private function __construct() |
|
62 | |||
63 | /** |
||
64 | * Returns the singleton instance of the factory |
||
65 | * |
||
66 | * @return CachingStrategyFactory |
||
67 | */ |
||
68 | 34 | public static function getInstance() |
|
69 | { |
||
70 | 34 | if (self::$instance === null) { |
|
71 | 1 | self::$instance = new CachingStrategyFactory(); |
|
72 | } |
||
73 | |||
74 | 34 | return self::$instance; |
|
75 | } |
||
76 | |||
77 | /** |
||
78 | * Returns the best caching strategy, given the number of unique shared strings |
||
79 | * and the amount of memory available. |
||
80 | * |
||
81 | * @param int|null $sharedStringsUniqueCount Number of unique shared strings (NULL if unknown) |
||
82 | * @param string|void $tempFolder Temporary folder where the temporary files to store shared strings will be stored |
||
83 | * @return CachingStrategyInterface The best caching strategy |
||
84 | */ |
||
85 | 41 | public function getBestCachingStrategy($sharedStringsUniqueCount, $tempFolder = null) |
|
93 | |||
94 | /** |
||
95 | * Returns whether it is safe to use in-memory caching, given the number of unique shared strings |
||
96 | * and the amount of memory available. |
||
97 | * |
||
98 | * @param int|null $sharedStringsUniqueCount Number of unique shared strings (NULL if unknown) |
||
99 | * @return bool |
||
100 | */ |
||
101 | 41 | protected function isInMemoryStrategyUsageSafe($sharedStringsUniqueCount) |
|
118 | |||
119 | /** |
||
120 | * Returns the PHP "memory_limit" in Kilobytes |
||
121 | * |
||
122 | * @return float |
||
123 | */ |
||
124 | 43 | protected function getMemoryLimitInKB() |
|
125 | { |
||
126 | 43 | $memoryLimitFormatted = $this->getMemoryLimitFromIni(); |
|
127 | 43 | $memoryLimitFormatted = strtolower(trim($memoryLimitFormatted)); |
|
128 | |||
129 | // No memory limit |
||
130 | 43 | if ($memoryLimitFormatted === '-1') { |
|
131 | 2 | return -1; |
|
132 | } |
||
133 | |||
134 | 41 | if (preg_match('/(\d+)([bkmgt])b?/', $memoryLimitFormatted, $matches)) { |
|
135 | 40 | $amount = intval($matches[1]); |
|
136 | 40 | $unit = $matches[2]; |
|
137 | |||
138 | switch ($unit) { |
||
139 | 40 | case 'b': return ($amount / 1024); |
|
140 | 39 | case 'k': return $amount; |
|
141 | 37 | case 'm': return ($amount * 1024); |
|
142 | 35 | case 'g': return ($amount * 1024 * 1024); |
|
143 | 2 | case 't': return ($amount * 1024 * 1024 * 1024); |
|
144 | } |
||
145 | } |
||
146 | |||
147 | 1 | return -1; |
|
148 | } |
||
149 | |||
150 | /** |
||
151 | * Returns the formatted "memory_limit" value |
||
152 | * |
||
153 | * @return string |
||
154 | */ |
||
155 | 32 | protected function getMemoryLimitFromIni() |
|
159 | } |
||
160 |