1 | <?php |
||||
2 | |||||
3 | /** |
||||
4 | * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0 |
||||
5 | * @copyright Aimeos (aimeos.org), 2015-2025 |
||||
6 | * @package Base |
||||
7 | * @subpackage Cache |
||||
8 | */ |
||||
9 | |||||
10 | |||||
11 | namespace Aimeos\Base\Cache; |
||||
12 | |||||
13 | |||||
14 | /** |
||||
15 | * Database cache class. |
||||
16 | * |
||||
17 | * @package Base |
||||
18 | * @subpackage Cache |
||||
19 | */ |
||||
20 | class DB |
||||
21 | extends \Aimeos\Base\Cache\Base |
||||
22 | implements \Aimeos\Base\Cache\Iface |
||||
23 | { |
||||
24 | private \Aimeos\Base\DB\Connection\Iface $conn; |
||||
25 | private array $sql; |
||||
26 | |||||
27 | |||||
28 | /** |
||||
29 | * Initializes the object instance. |
||||
30 | * |
||||
31 | * The config array must contain these statement: |
||||
32 | * [delete] => |
||||
33 | * DELETE FROM cachetable WHERE :cond |
||||
34 | * [deletebytag] => |
||||
35 | * DELETE FROM cachetable WHERE id IN ( |
||||
36 | * SELECT tid FROM cachetagtable WHERE :cond |
||||
37 | * ) |
||||
38 | * [get] => |
||||
39 | * SELECT id, value, expire FROM cachetable WHERE :cond |
||||
40 | * [set] => |
||||
41 | * INSERT INTO cachetable ( id, expire, value ) VALUES ( ?, ?, ? ) |
||||
42 | * [settag] => |
||||
43 | * INSERT INTO cachetagtable ( tid, tname ) VALUES ( ?, ? ) |
||||
44 | * |
||||
45 | * For using a different database connection, the name of the database connection |
||||
46 | * can be also given in the "config" parameter. In this case, use e.g. |
||||
47 | * config['dbname'] = 'db-cache' |
||||
48 | * |
||||
49 | * @param array $config Associative list with SQL statements |
||||
50 | * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection |
||||
51 | */ |
||||
52 | public function __construct( array $config, \Aimeos\Base\DB\Connection\Iface $conn ) |
||||
53 | { |
||||
54 | $this->sql = $config; |
||||
55 | $this->conn = $conn; |
||||
56 | } |
||||
57 | |||||
58 | |||||
59 | /** |
||||
60 | * Removes all expired cache entries. |
||||
61 | * |
||||
62 | * @inheritDoc |
||||
63 | * |
||||
64 | * @return bool True on success and false on failure |
||||
65 | */ |
||||
66 | public function cleanup() : bool |
||||
67 | { |
||||
68 | try |
||||
69 | { |
||||
70 | $this->conn->create( $this->sql( 'cleanup' ) ) |
||||
71 | ->bind( 1, date( 'Y-m-d H:i:00' ) ) |
||||
72 | ->execute()->finish(); |
||||
73 | } |
||||
74 | catch( \Exception $e ) |
||||
75 | { |
||||
76 | return false; |
||||
77 | } |
||||
78 | |||||
79 | return true; |
||||
80 | } |
||||
81 | |||||
82 | |||||
83 | /** |
||||
84 | * Removes all entries of the site from the cache. |
||||
85 | * |
||||
86 | * @inheritDoc |
||||
87 | * |
||||
88 | * @return bool True on success and false on failure |
||||
89 | */ |
||||
90 | public function clear() : bool |
||||
91 | { |
||||
92 | try { |
||||
93 | $this->conn->create( $this->sql( 'clear' ) )->execute()->finish(); |
||||
94 | } catch( \Exception $e ) { |
||||
95 | return false; |
||||
96 | } |
||||
97 | |||||
98 | return true; |
||||
99 | } |
||||
100 | |||||
101 | |||||
102 | /** |
||||
103 | * Removes the cache entries identified by the given keys. |
||||
104 | * |
||||
105 | * @inheritDoc |
||||
106 | * |
||||
107 | * @param iterable $keys List of key strings that identify the cache entries that should be removed |
||||
108 | * @return bool True if the items were successfully removed. False if there was an error. |
||||
109 | * @throws \Psr\SimpleCache\InvalidArgumentException |
||||
110 | */ |
||||
111 | public function deleteMultiple( iterable $keys ) : bool |
||||
112 | { |
||||
113 | try |
||||
114 | { |
||||
115 | if( ( $cnt = count( $keys ) ) === 0 ) { |
||||
116 | return true; |
||||
117 | } |
||||
118 | |||||
119 | $pos = 1; |
||||
120 | $sql = $this->sql( 'delete' ); |
||||
121 | $sql = substr_replace( $sql, str_repeat( ',?', $cnt - 1 ), strrpos( $sql, '?' ) + 1, 0 ); |
||||
122 | |||||
123 | $stmt = $this->conn->create( $sql ); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
124 | |||||
125 | foreach( $keys as $key ) { |
||||
126 | $stmt->bind( $pos++, $key ); |
||||
127 | } |
||||
128 | |||||
129 | $stmt->execute()->finish(); |
||||
130 | } |
||||
131 | catch( \Exception $e ) |
||||
132 | { |
||||
133 | return false; |
||||
134 | } |
||||
135 | |||||
136 | return true; |
||||
137 | } |
||||
138 | |||||
139 | |||||
140 | /** |
||||
141 | * Removes the cache entries identified by the given tags. |
||||
142 | * |
||||
143 | * @inheritDoc |
||||
144 | * |
||||
145 | * @param iterable $tags List of tag strings that are associated to one or |
||||
146 | * more cache entries that should be removed |
||||
147 | * @return bool True if the items were successfully removed. False if there was an error. |
||||
148 | * @throws \Psr\SimpleCache\InvalidArgumentException |
||||
149 | */ |
||||
150 | public function deleteByTags( iterable $tags ) : bool |
||||
151 | { |
||||
152 | try |
||||
153 | { |
||||
154 | if( ( $cnt = count( $tags ) ) === 0 ) { |
||||
155 | return true; |
||||
156 | } |
||||
157 | |||||
158 | $pos = 1; |
||||
159 | $sql = $this->sql( 'deletebytag' ); |
||||
160 | $sql = substr_replace( $sql, str_repeat( ',?', $cnt - 1 ), strrpos( $sql, '?' ) + 1, 0 ); |
||||
161 | |||||
162 | $stmt = $this->conn->create( $sql ); |
||||
0 ignored issues
–
show
It seems like
$sql can also be of type array ; however, parameter $sql of Aimeos\Base\DB\Connection\Iface::create() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
163 | |||||
164 | foreach( $tags as $tag ) { |
||||
165 | $stmt->bind( $pos++, $tag ); |
||||
166 | } |
||||
167 | |||||
168 | $stmt->execute()->finish(); |
||||
169 | } |
||||
170 | catch( \Exception $e ) |
||||
171 | { |
||||
172 | return false; |
||||
173 | } |
||||
174 | |||||
175 | return true; |
||||
176 | } |
||||
177 | |||||
178 | |||||
179 | /** |
||||
180 | * Returns the cached values for the given cache keys if available. |
||||
181 | * |
||||
182 | * @inheritDoc |
||||
183 | * |
||||
184 | * @param string[] $keys List of key strings for the requested cache entries |
||||
185 | * @param mixed $default Default value to return for keys that do not exist |
||||
186 | * @return iterable Associative list of key/value pairs for the requested cache |
||||
187 | * entries. If a cache entry doesn't exist, neither its key nor a value |
||||
188 | * will be in the result list |
||||
189 | * @throws \Aimeos\Base\Cache\Exception If the cache server doesn't respond |
||||
190 | */ |
||||
191 | public function getMultiple( iterable $keys, $default = null ) : iterable |
||||
192 | { |
||||
193 | try |
||||
194 | { |
||||
195 | if( ( $cnt = count( $keys ) ) === 0 ) { |
||||
196 | return true; |
||||
197 | } |
||||
198 | |||||
199 | $pos = 2; |
||||
200 | $list = []; |
||||
201 | |||||
202 | $sql = $this->sql( 'get' ); |
||||
203 | $sql = substr_replace( $sql, str_repeat( ',?', $cnt - 1 ), strrpos( $sql, '?' ) + 1, 0 ); |
||||
204 | |||||
205 | $stmt = $this->conn->create( $sql ) |
||||
0 ignored issues
–
show
It seems like
$sql can also be of type array ; however, parameter $sql of Aimeos\Base\DB\Connection\Iface::create() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
206 | ->bind( 1, date( 'Y-m-d H:i:00' ) ); |
||||
207 | |||||
208 | foreach( $keys as $key ) { |
||||
209 | $stmt->bind( $pos++, $key ); |
||||
210 | } |
||||
211 | |||||
212 | $result = $stmt->execute(); |
||||
213 | |||||
214 | while( ( $row = $result->fetch() ) !== null ) { |
||||
215 | $list[$row['id']] = (string) $row['value']; |
||||
216 | } |
||||
217 | |||||
218 | foreach( $keys as $key ) |
||||
219 | { |
||||
220 | if( !isset( $list[$key] ) ) { |
||||
221 | $list[$key] = $default; |
||||
222 | } |
||||
223 | } |
||||
224 | } |
||||
225 | catch( \Exception $e ) |
||||
226 | { |
||||
227 | return []; |
||||
228 | } |
||||
229 | |||||
230 | return $list; |
||||
231 | } |
||||
232 | |||||
233 | |||||
234 | /** |
||||
235 | * Determines whether an item is present in the cache. |
||||
236 | * |
||||
237 | * @inheritDoc |
||||
238 | * |
||||
239 | * @param string $key The cache item key |
||||
240 | * @return bool True if cache entry is available, false if not |
||||
241 | * @throws \Psr\SimpleCache\InvalidArgumentException |
||||
242 | */ |
||||
243 | public function has( string $key ) : bool |
||||
244 | { |
||||
245 | try |
||||
246 | { |
||||
247 | $return = false; |
||||
248 | $result = $this->conn->create( $this->sql( 'get' ) ) |
||||
249 | ->bind( 1, date( 'Y-m-d H:i:00' ) ) |
||||
250 | ->bind( 2, $key ) |
||||
251 | ->execute(); |
||||
252 | |||||
253 | while( $result->fetch() ) { |
||||
254 | $return = true; |
||||
255 | } |
||||
256 | } |
||||
257 | catch( \Exception $e ) |
||||
258 | { |
||||
259 | return false; |
||||
260 | } |
||||
261 | |||||
262 | return $return; |
||||
263 | } |
||||
264 | |||||
265 | |||||
266 | /** |
||||
267 | * Adds or overwrites the given key/value pairs in the cache, which is much |
||||
268 | * more efficient than setting them one by one using the set() method. |
||||
269 | * |
||||
270 | * @inheritDoc |
||||
271 | * |
||||
272 | * @param iterable $pairs Associative list of key/value pairs. Both must be a string |
||||
273 | * @param \DateInterval|int|string|null $expires Date interval object, |
||||
274 | * date/time string in "YYYY-MM-DD HH:mm:ss" format or as integer TTL value |
||||
275 | * when the cache entry will expiry |
||||
276 | * @param iterable $tags List of tags that should be associated to the cache entries |
||||
277 | * @return bool True on success and false on failure. |
||||
278 | * @throws \Psr\SimpleCache\InvalidArgumentException |
||||
279 | */ |
||||
280 | public function setMultiple( iterable $pairs, $expires = null, iterable $tags = [] ) : bool |
||||
281 | { |
||||
282 | $keys = []; |
||||
283 | foreach( $pairs as $key => $v ) { |
||||
284 | $keys[] = $key; |
||||
285 | } |
||||
286 | |||||
287 | // Remove existing entries first to avoid duplicate key conflicts |
||||
288 | $this->deleteMultiple( $keys ); |
||||
289 | |||||
290 | try |
||||
291 | { |
||||
292 | $this->conn->begin(); |
||||
293 | $stmt = $this->conn->create( $this->sql( 'set' ) ); |
||||
294 | $stmtTag = $this->conn->create( $this->sql( 'settag' ) ); |
||||
295 | |||||
296 | foreach( $pairs as $key => $value ) |
||||
297 | { |
||||
298 | if( $expires instanceof \DateInterval ) { |
||||
299 | $expires = date_create()->add( $expires )->format( 'Y-m-d H:i:s' ); |
||||
300 | } elseif( is_int( $expires ) ) { |
||||
301 | $expires = date( 'Y-m-d H:i:s', time() + $expires ); |
||||
302 | } |
||||
303 | |||||
304 | $stmt->bind( 1, (string) $key ); |
||||
305 | $stmt->bind( 2, $expires ); |
||||
306 | $stmt->bind( 3, (string) $value ); |
||||
307 | $stmt->execute()->finish(); |
||||
308 | |||||
309 | foreach( $tags as $name ) |
||||
310 | { |
||||
311 | $stmtTag->bind( 1, (string) $key ); |
||||
312 | $stmtTag->bind( 2, (string) $name ); |
||||
313 | $stmtTag->execute()->finish(); |
||||
314 | } |
||||
315 | } |
||||
316 | |||||
317 | $this->conn->commit(); |
||||
318 | } |
||||
319 | catch( \Exception $e ) |
||||
320 | { |
||||
321 | $this->conn->rollback(); |
||||
322 | return false; |
||||
323 | } |
||||
324 | |||||
325 | return true; |
||||
326 | } |
||||
327 | |||||
328 | |||||
329 | /** |
||||
330 | * Retturns the SQL statement for the given name. |
||||
331 | * |
||||
332 | * @param string $name SQL statement |
||||
333 | * @throws \Aimeos\Base\Cache\Exception If SQL statement is not available |
||||
334 | */ |
||||
335 | protected function sql( string $name ) : string |
||||
336 | { |
||||
337 | if( isset( $this->sql[$name] ) ) { |
||||
338 | return $this->sql[$name]; |
||||
339 | } |
||||
340 | |||||
341 | throw new \Aimeos\Base\Cache\Exception( "SQL statement for $name is missing" ); |
||||
342 | } |
||||
343 | } |
||||
344 |