Passed
Push — master ( 018a81...fedeae )
by Aimeos
09:17
created

DB::clear()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 9
c 1
b 0
f 0
dl 0
loc 18
rs 9.9666
cc 2
nc 3
nop 0
1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2014
6
 * @copyright Aimeos (aimeos.org), 2015-2022
7
 * @package MW
8
 * @subpackage Cache
9
 */
10
11
12
namespace Aimeos\MW\Cache;
13
14
15
/**
16
 * Database cache class.
17
 *
18
 * @package MW
19
 * @subpackage Cache
20
 */
21
class DB
22
	extends \Aimeos\MW\Cache\Base
0 ignored issues
show
Bug introduced by
The type Aimeos\MW\Cache\Base was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
23
	implements \Aimeos\MW\Cache\Iface
0 ignored issues
show
Bug introduced by
The type Aimeos\MW\Cache\Iface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

348
		$this->deleteMultiple( array_keys( /** @scrutinizer ignore-type */ $pairs ) );
Loading history...
349
350
		$conn = $this->dbm->acquire( $this->dbname );
351
352
		try
353
		{
354
			$conn->begin();
355
			$stmt = $conn->create( $this->sql['set'] );
356
			$stmtTag = $conn->create( $this->sql['settag'] );
357
358
			foreach( $pairs as $key => $value )
359
			{
360
				if( $expires instanceof \DateInterval ) {
361
					$expires = date_create()->add( $expires )->format( 'Y-m-d H:i:s' );
362
				} elseif( is_int( $expires ) ) {
363
					$expires = date( 'Y-m-d H:i:s', time() + $expires );
364
				}
365
366
				$stmt->bind( 1, (string) $key );
367
				$stmt->bind( 2, $expires );
368
				$stmt->bind( 3, (string) $value );
369
				$stmt->execute()->finish();
370
371
				foreach( $tags as $name )
372
				{
373
					$stmtTag->bind( 1, (string) $key );
374
					$stmtTag->bind( 2, (string) $name );
375
					$stmtTag->execute()->finish();
376
				}
377
			}
378
379
			$conn->commit();
380
			$this->dbm->release( $conn, $this->dbname );
381
		}
382
		catch( \Exception $e )
383
		{
384
			$conn->rollback();
385
			$this->dbm->release( $conn, $this->dbname );
386
387
			error_log( __METHOD__ . ': ' . $e->getMessage() );
388
			return false;
389
		}
390
391
		return true;
392
	}
393
394
395
	/**
396
	 * Checks if all required search configurations are available.
397
	 *
398
	 * @param array $config Associative list of search configurations
399
	 * @throws \Aimeos\MW\Tree\Exception If one ore more search configurations are missing
400
	 */
401
	protected function checkSearchConfig( array $config )
402
	{
403
		$required = array( 'cache.id', 'cache.value', 'cache.expire', 'cache.tag.name' );
404
405
		foreach( $required as $key => $entry )
406
		{
407
			if( isset( $config[$entry] ) ) {
408
				unset( $required[$key] );
409
			}
410
		}
411
412
		if( count( $required ) > 0 )
413
		{
414
			$msg = 'Search config in given configuration are missing: "%1$s"';
415
			throw new \Aimeos\MW\Cache\Exception( sprintf( $msg, implode( ', ', $required ) ) );
416
		}
417
	}
418
419
420
	/**
421
	 * Checks if all required SQL statements are available.
422
	 *
423
	 * @param array $config Associative list of SQL statements
424
	 * @throws \Aimeos\MW\Tree\Exception If one ore more SQL statements are missing
425
	 */
426
	protected function checkSqlConfig( array $config )
427
	{
428
		$required = array( 'delete', 'deletebytag', 'get', 'set', 'settag' );
429
430
		foreach( $required as $key => $entry )
431
		{
432
			if( isset( $config[$entry] ) ) {
433
				unset( $required[$key] );
434
			}
435
		}
436
437
		if( count( $required ) > 0 )
438
		{
439
			$msg = 'SQL statements in given configuration are missing: "%1$s"';
440
			throw new \Aimeos\MW\Cache\Exception( sprintf( $msg, implode( ', ', $required ) ) );
441
		}
442
	}
443
}
444