PhpFilesTrait.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Cache\Traits;
  11. use Symfony\Component\Cache\Exception\CacheException;
  12. use Symfony\Component\Cache\Exception\InvalidArgumentException;
  13. use Symfony\Component\VarExporter\VarExporter;
  14. /**
  15. * @author Piotr Stankowski <git@trakos.pl>
  16. * @author Nicolas Grekas <p@tchwork.com>
  17. * @author Rob Frawley 2nd <rmf@src.run>
  18. *
  19. * @internal
  20. */
  21. trait PhpFilesTrait
  22. {
  23. use FilesystemCommonTrait {
  24. doClear as private doCommonClear;
  25. doDelete as private doCommonDelete;
  26. }
  27. private $includeHandler;
  28. private $appendOnly;
  29. private $values = [];
  30. private $files = [];
  31. private static $startTime;
  32. public static function isSupported()
  33. {
  34. self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
  35. return \function_exists('opcache_invalidate') && ('cli' !== \PHP_SAPI || filter_var(ini_get('opcache.enable_cli'), FILTER_VALIDATE_BOOLEAN)) && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN);
  36. }
  37. /**
  38. * @return bool
  39. */
  40. public function prune()
  41. {
  42. $time = time();
  43. $pruned = true;
  44. $getExpiry = true;
  45. set_error_handler($this->includeHandler);
  46. try {
  47. foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
  48. try {
  49. if (\is_array($expiresAt = include $file)) {
  50. $expiresAt = $expiresAt[0];
  51. }
  52. } catch (\ErrorException $e) {
  53. $expiresAt = $time;
  54. }
  55. if ($time >= $expiresAt) {
  56. $pruned = $this->doUnlink($file) && !file_exists($file) && $pruned;
  57. }
  58. }
  59. } finally {
  60. restore_error_handler();
  61. }
  62. return $pruned;
  63. }
  64. /**
  65. * {@inheritdoc}
  66. */
  67. protected function doFetch(array $ids)
  68. {
  69. if ($this->appendOnly) {
  70. $now = 0;
  71. $missingIds = [];
  72. } else {
  73. $now = time();
  74. $missingIds = $ids;
  75. $ids = [];
  76. }
  77. $values = [];
  78. begin:
  79. $getExpiry = false;
  80. foreach ($ids as $id) {
  81. if (null === $value = $this->values[$id] ?? null) {
  82. $missingIds[] = $id;
  83. } elseif ('N;' === $value) {
  84. $values[$id] = null;
  85. } elseif (!\is_object($value)) {
  86. $values[$id] = $value;
  87. } elseif (!$value instanceof LazyValue) {
  88. // calling a Closure is for @deprecated BC and should be removed in Symfony 5.0
  89. $values[$id] = $value();
  90. } elseif (false === $values[$id] = include $value->file) {
  91. unset($values[$id], $this->values[$id]);
  92. $missingIds[] = $id;
  93. }
  94. if (!$this->appendOnly) {
  95. unset($this->values[$id]);
  96. }
  97. }
  98. if (!$missingIds) {
  99. return $values;
  100. }
  101. set_error_handler($this->includeHandler);
  102. try {
  103. $getExpiry = true;
  104. foreach ($missingIds as $k => $id) {
  105. try {
  106. $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
  107. if (\is_array($expiresAt = include $file)) {
  108. [$expiresAt, $this->values[$id]] = $expiresAt;
  109. } elseif ($now < $expiresAt) {
  110. $this->values[$id] = new LazyValue($file);
  111. }
  112. if ($now >= $expiresAt) {
  113. unset($this->values[$id], $missingIds[$k]);
  114. }
  115. } catch (\ErrorException $e) {
  116. unset($missingIds[$k]);
  117. }
  118. }
  119. } finally {
  120. restore_error_handler();
  121. }
  122. $ids = $missingIds;
  123. $missingIds = [];
  124. goto begin;
  125. }
  126. /**
  127. * {@inheritdoc}
  128. */
  129. protected function doHave($id)
  130. {
  131. if ($this->appendOnly && isset($this->values[$id])) {
  132. return true;
  133. }
  134. set_error_handler($this->includeHandler);
  135. try {
  136. $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
  137. $getExpiry = true;
  138. if (\is_array($expiresAt = include $file)) {
  139. [$expiresAt, $value] = $expiresAt;
  140. } elseif ($this->appendOnly) {
  141. $value = new LazyValue($file);
  142. }
  143. } catch (\ErrorException $e) {
  144. return false;
  145. } finally {
  146. restore_error_handler();
  147. }
  148. if ($this->appendOnly) {
  149. $now = 0;
  150. $this->values[$id] = $value;
  151. } else {
  152. $now = time();
  153. }
  154. return $now < $expiresAt;
  155. }
  156. /**
  157. * {@inheritdoc}
  158. */
  159. protected function doSave(array $values, $lifetime)
  160. {
  161. $ok = true;
  162. $expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX';
  163. $allowCompile = self::isSupported();
  164. foreach ($values as $key => $value) {
  165. unset($this->values[$key]);
  166. $isStaticValue = true;
  167. if (null === $value) {
  168. $value = "'N;'";
  169. } elseif (\is_object($value) || \is_array($value)) {
  170. try {
  171. $value = VarExporter::export($value, $isStaticValue);
  172. } catch (\Exception $e) {
  173. throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
  174. }
  175. } elseif (\is_string($value)) {
  176. // Wrap "N;" in a closure to not confuse it with an encoded `null`
  177. if ('N;' === $value) {
  178. $isStaticValue = false;
  179. }
  180. $value = var_export($value, true);
  181. } elseif (!is_scalar($value)) {
  182. throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value)));
  183. } else {
  184. $value = var_export($value, true);
  185. }
  186. if (!$isStaticValue) {
  187. // We cannot use a closure here because of https://bugs.php.net/76982
  188. $value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value);
  189. $value = "<?php\n\nnamespace Symfony\Component\VarExporter\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};\n";
  190. } else {
  191. $value = "<?php return [{$expiry}, {$value}];\n";
  192. }
  193. $file = $this->files[$key] = $this->getFile($key, true);
  194. // Since OPcache only compiles files older than the script execution start, set the file's mtime in the past
  195. $ok = $this->write($file, $value, self::$startTime - 10) && $ok;
  196. if ($allowCompile) {
  197. @opcache_invalidate($file, true);
  198. @opcache_compile_file($file);
  199. }
  200. }
  201. if (!$ok && !is_writable($this->directory)) {
  202. throw new CacheException(sprintf('Cache directory is not writable (%s)', $this->directory));
  203. }
  204. return $ok;
  205. }
  206. /**
  207. * {@inheritdoc}
  208. */
  209. protected function doClear($namespace)
  210. {
  211. $this->values = [];
  212. return $this->doCommonClear($namespace);
  213. }
  214. /**
  215. * {@inheritdoc}
  216. */
  217. protected function doDelete(array $ids)
  218. {
  219. foreach ($ids as $id) {
  220. unset($this->values[$id]);
  221. }
  222. return $this->doCommonDelete($ids);
  223. }
  224. protected function doUnlink($file)
  225. {
  226. if (self::isSupported()) {
  227. @opcache_invalidate($file, true);
  228. }
  229. return @unlink($file);
  230. }
  231. }
  232. /**
  233. * @internal
  234. */
  235. class LazyValue
  236. {
  237. public $file;
  238. public function __construct($file)
  239. {
  240. $this->file = $file;
  241. }
  242. }