summaryrefslogtreecommitdiff
path: root/vendor/doctrine/orm/src/Cache/Region/FileLockRegion.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/doctrine/orm/src/Cache/Region/FileLockRegion.php')
-rw-r--r--vendor/doctrine/orm/src/Cache/Region/FileLockRegion.php194
1 files changed, 194 insertions, 0 deletions
diff --git a/vendor/doctrine/orm/src/Cache/Region/FileLockRegion.php b/vendor/doctrine/orm/src/Cache/Region/FileLockRegion.php
new file mode 100644
index 0000000..bedd6a6
--- /dev/null
+++ b/vendor/doctrine/orm/src/Cache/Region/FileLockRegion.php
@@ -0,0 +1,194 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Cache\Region;
6
7use Doctrine\ORM\Cache\CacheEntry;
8use Doctrine\ORM\Cache\CacheKey;
9use Doctrine\ORM\Cache\CollectionCacheEntry;
10use Doctrine\ORM\Cache\ConcurrentRegion;
11use Doctrine\ORM\Cache\Lock;
12use Doctrine\ORM\Cache\Region;
13use InvalidArgumentException;
14
15use function array_filter;
16use function array_map;
17use function chmod;
18use function file_get_contents;
19use function file_put_contents;
20use function fileatime;
21use function glob;
22use function is_dir;
23use function is_file;
24use function is_writable;
25use function mkdir;
26use function sprintf;
27use function time;
28use function unlink;
29
30use const DIRECTORY_SEPARATOR;
31use const LOCK_EX;
32
33/**
34 * Very naive concurrent region, based on file locks.
35 */
36class FileLockRegion implements ConcurrentRegion
37{
38 final public const LOCK_EXTENSION = 'lock';
39
40 /**
41 * @param numeric-string|int $lockLifetime
42 *
43 * @throws InvalidArgumentException
44 */
45 public function __construct(
46 private readonly Region $region,
47 private readonly string $directory,
48 private readonly string|int $lockLifetime,
49 ) {
50 if (! is_dir($directory) && ! @mkdir($directory, 0775, true)) {
51 throw new InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $directory));
52 }
53
54 if (! is_writable($directory)) {
55 throw new InvalidArgumentException(sprintf('The directory "%s" is not writable.', $directory));
56 }
57 }
58
59 private function isLocked(CacheKey $key, Lock|null $lock = null): bool
60 {
61 $filename = $this->getLockFileName($key);
62
63 if (! is_file($filename)) {
64 return false;
65 }
66
67 $time = $this->getLockTime($filename);
68 $content = $this->getLockContent($filename);
69
70 if ($content === false || $time === false) {
71 @unlink($filename);
72
73 return false;
74 }
75
76 if ($lock && $content === $lock->value) {
77 return false;
78 }
79
80 // outdated lock
81 if ($time + $this->lockLifetime <= time()) {
82 @unlink($filename);
83
84 return false;
85 }
86
87 return true;
88 }
89
90 private function getLockFileName(CacheKey $key): string
91 {
92 return $this->directory . DIRECTORY_SEPARATOR . $key->hash . '.' . self::LOCK_EXTENSION;
93 }
94
95 private function getLockContent(string $filename): string|false
96 {
97 return @file_get_contents($filename);
98 }
99
100 private function getLockTime(string $filename): int|false
101 {
102 return @fileatime($filename);
103 }
104
105 public function getName(): string
106 {
107 return $this->region->getName();
108 }
109
110 public function contains(CacheKey $key): bool
111 {
112 if ($this->isLocked($key)) {
113 return false;
114 }
115
116 return $this->region->contains($key);
117 }
118
119 public function get(CacheKey $key): CacheEntry|null
120 {
121 if ($this->isLocked($key)) {
122 return null;
123 }
124
125 return $this->region->get($key);
126 }
127
128 public function getMultiple(CollectionCacheEntry $collection): array|null
129 {
130 if (array_filter(array_map($this->isLocked(...), $collection->identifiers))) {
131 return null;
132 }
133
134 return $this->region->getMultiple($collection);
135 }
136
137 public function put(CacheKey $key, CacheEntry $entry, Lock|null $lock = null): bool
138 {
139 if ($this->isLocked($key, $lock)) {
140 return false;
141 }
142
143 return $this->region->put($key, $entry);
144 }
145
146 public function evict(CacheKey $key): bool
147 {
148 if ($this->isLocked($key)) {
149 @unlink($this->getLockFileName($key));
150 }
151
152 return $this->region->evict($key);
153 }
154
155 public function evictAll(): bool
156 {
157 // The check below is necessary because on some platforms glob returns false
158 // when nothing matched (even though no errors occurred)
159 $filenames = glob(sprintf('%s/*.%s', $this->directory, self::LOCK_EXTENSION)) ?: [];
160
161 foreach ($filenames as $filename) {
162 @unlink($filename);
163 }
164
165 return $this->region->evictAll();
166 }
167
168 public function lock(CacheKey $key): Lock|null
169 {
170 if ($this->isLocked($key)) {
171 return null;
172 }
173
174 $lock = Lock::createLockRead();
175 $filename = $this->getLockFileName($key);
176
177 if (@file_put_contents($filename, $lock->value, LOCK_EX) === false) {
178 return null;
179 }
180
181 chmod($filename, 0664);
182
183 return $lock;
184 }
185
186 public function unlock(CacheKey $key, Lock $lock): bool
187 {
188 if ($this->isLocked($key, $lock)) {
189 return false;
190 }
191
192 return @unlink($this->getLockFileName($key));
193 }
194}