summaryrefslogtreecommitdiff
path: root/vendor/symfony/cache/Traits/FilesystemCommonTrait.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/symfony/cache/Traits/FilesystemCommonTrait.php')
-rw-r--r--vendor/symfony/cache/Traits/FilesystemCommonTrait.php191
1 files changed, 191 insertions, 0 deletions
diff --git a/vendor/symfony/cache/Traits/FilesystemCommonTrait.php b/vendor/symfony/cache/Traits/FilesystemCommonTrait.php
new file mode 100644
index 0000000..3e8c3b1
--- /dev/null
+++ b/vendor/symfony/cache/Traits/FilesystemCommonTrait.php
@@ -0,0 +1,191 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Cache\Traits;
13
14use Symfony\Component\Cache\Exception\InvalidArgumentException;
15
16/**
17 * @author Nicolas Grekas <p@tchwork.com>
18 *
19 * @internal
20 */
21trait FilesystemCommonTrait
22{
23 private string $directory;
24 private string $tmpSuffix;
25
26 private function init(string $namespace, ?string $directory): void
27 {
28 if (!isset($directory[0])) {
29 $directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache';
30 } else {
31 $directory = realpath($directory) ?: $directory;
32 }
33 if (isset($namespace[0])) {
34 if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
35 throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
36 }
37 $directory .= \DIRECTORY_SEPARATOR.$namespace;
38 } else {
39 $directory .= \DIRECTORY_SEPARATOR.'@';
40 }
41 if (!is_dir($directory)) {
42 @mkdir($directory, 0777, true);
43 }
44 $directory .= \DIRECTORY_SEPARATOR;
45 // On Windows the whole path is limited to 258 chars
46 if ('\\' === \DIRECTORY_SEPARATOR && \strlen($directory) > 234) {
47 throw new InvalidArgumentException(sprintf('Cache directory too long (%s).', $directory));
48 }
49
50 $this->directory = $directory;
51 }
52
53 protected function doClear(string $namespace): bool
54 {
55 $ok = true;
56
57 foreach ($this->scanHashDir($this->directory) as $file) {
58 if ('' !== $namespace && !str_starts_with($this->getFileKey($file), $namespace)) {
59 continue;
60 }
61
62 $ok = ($this->doUnlink($file) || !file_exists($file)) && $ok;
63 }
64
65 return $ok;
66 }
67
68 protected function doDelete(array $ids): bool
69 {
70 $ok = true;
71
72 foreach ($ids as $id) {
73 $file = $this->getFile($id);
74 $ok = (!is_file($file) || $this->doUnlink($file) || !file_exists($file)) && $ok;
75 }
76
77 return $ok;
78 }
79
80 protected function doUnlink(string $file): bool
81 {
82 return @unlink($file);
83 }
84
85 private function write(string $file, string $data, ?int $expiresAt = null): bool
86 {
87 $unlink = false;
88 set_error_handler(static fn ($type, $message, $file, $line) => throw new \ErrorException($message, 0, $type, $file, $line));
89 try {
90 $tmp = $this->directory.$this->tmpSuffix ??= str_replace('/', '-', base64_encode(random_bytes(6)));
91 try {
92 $h = fopen($tmp, 'x');
93 } catch (\ErrorException $e) {
94 if (!str_contains($e->getMessage(), 'File exists')) {
95 throw $e;
96 }
97
98 $tmp = $this->directory.$this->tmpSuffix = str_replace('/', '-', base64_encode(random_bytes(6)));
99 $h = fopen($tmp, 'x');
100 }
101 fwrite($h, $data);
102 fclose($h);
103 $unlink = true;
104
105 if (null !== $expiresAt) {
106 touch($tmp, $expiresAt ?: time() + 31556952); // 1 year in seconds
107 }
108
109 if ('\\' === \DIRECTORY_SEPARATOR) {
110 $success = copy($tmp, $file);
111 $unlink = true;
112 } else {
113 $success = rename($tmp, $file);
114 $unlink = !$success;
115 }
116
117 return $success;
118 } finally {
119 restore_error_handler();
120
121 if ($unlink) {
122 @unlink($tmp);
123 }
124 }
125 }
126
127 private function getFile(string $id, bool $mkdir = false, ?string $directory = null): string
128 {
129 // Use xxh128 to favor speed over security, which is not an issue here
130 $hash = str_replace('/', '-', base64_encode(hash('xxh128', static::class.$id, true)));
131 $dir = ($directory ?? $this->directory).strtoupper($hash[0].\DIRECTORY_SEPARATOR.$hash[1].\DIRECTORY_SEPARATOR);
132
133 if ($mkdir && !is_dir($dir)) {
134 @mkdir($dir, 0777, true);
135 }
136
137 return $dir.substr($hash, 2, 20);
138 }
139
140 private function getFileKey(string $file): string
141 {
142 return '';
143 }
144
145 private function scanHashDir(string $directory): \Generator
146 {
147 if (!is_dir($directory)) {
148 return;
149 }
150
151 $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
152
153 for ($i = 0; $i < 38; ++$i) {
154 if (!is_dir($directory.$chars[$i])) {
155 continue;
156 }
157
158 for ($j = 0; $j < 38; ++$j) {
159 if (!is_dir($dir = $directory.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) {
160 continue;
161 }
162
163 foreach (@scandir($dir, \SCANDIR_SORT_NONE) ?: [] as $file) {
164 if ('.' !== $file && '..' !== $file) {
165 yield $dir.\DIRECTORY_SEPARATOR.$file;
166 }
167 }
168 }
169 }
170 }
171
172 public function __sleep(): array
173 {
174 throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
175 }
176
177 public function __wakeup(): void
178 {
179 throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
180 }
181
182 public function __destruct()
183 {
184 if (method_exists(parent::class, '__destruct')) {
185 parent::__destruct();
186 }
187 if (isset($this->tmpSuffix) && is_file($this->directory.$this->tmpSuffix)) {
188 unlink($this->directory.$this->tmpSuffix);
189 }
190 }
191}