DB   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 322
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 32
eloc 105
c 5
b 0
f 0
dl 0
loc 322
rs 9.84

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A sql() 0 7 2
A has() 0 20 3
A cleanup() 0 14 2
B getMultiple() 0 40 7
A deleteMultiple() 0 26 4
A clear() 0 9 2
B setMultiple() 0 46 7
A deleteByTags() 0 26 4
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
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 ignore-type  annotation

123
			$stmt = $this->conn->create( /** @scrutinizer ignore-type */ $sql );
Loading history...
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
Bug introduced by
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 ignore-type  annotation

162
			$stmt = $this->conn->create( /** @scrutinizer ignore-type */ $sql );
Loading history...
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
Bug introduced by
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 ignore-type  annotation

205
			$stmt = $this->conn->create( /** @scrutinizer ignore-type */ $sql )
Loading history...
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