Completed
Push — master ( 815faa...197710 )
by Aimeos
07:55
created

DB::getMultipleByTags()   B

Complexity

Conditions 3
Paths 19

Size

Total Lines 41
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 26
nc 19
nop 1
dl 0
loc 41
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2014
6
 * @copyright Aimeos (aimeos.org), 2015-2016
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
Coding Style introduced by
Expected 0 spaces between "Base" and comma; 1 found
Loading history...
23
	implements \Aimeos\MW\Cache\Iface
24
{
25
	private $sql;
26
	private $dbm;
27
	private $dbname;
28
	private $siteid;
29
	private $searchConfig;
30
31
32
	/**
33
	 * Initializes the object instance.
34
	 *
35
	 * The config['search] array must contain these key/array pairs suitable for \Aimeos\MW\Criteria\Attribute\Standard:
36
	 *	[cache.id] => Array containing the codes/types/labels for the unique ID
37
	 *	[cache.siteid] => Array containing the codes/types/labels for the site ID
38
	 *	[cache.value] => Array containing the codes/types/labels for the cached value
39
	 *	[cache.expire] => Array containing the codes/types/labels for the expiration date
40
	 *	[cache.tag.name] => Array containing the codes/types/labels for the tag name
41
	 *
42
	 * The config['sql] array must contain these statement:
43
	 *	[delete] =>
44
	 *		DELETE FROM cachetable WHERE siteid = ? AND :cond
45
	 *	[deletebytag] =>
46
	 *		DELETE FROM cachetable WHERE siteid = ? AND id IN (
47
	 *			SELECT tid FROM cachetagtable WHERE tsiteid = ? AND :cond
48
	 *		)
49
	 *	[get] =>
50
	 *		SELECT id, value, expire FROM cachetable WHERE siteid = ? AND :cond
51
	 *	[getbytag] =>
52
	 *		SELECT id, value, expire FROM cachetable
53
	 *		JOIN cachetagtable ON tid = id
54
	 *		WHERE siteid = ? AND tsiteid = ? AND :cond
55
	 *	[set] =>
56
	 *		INSERT INTO cachetable ( id, siteid, expire, value ) VALUES ( ?, ?, ?, ? )
57
	 *	[settag] =>
58
	 *		INSERT INTO cachetagtable ( tid, tsiteid, tname ) VALUES ( ?, ?, ? )
59
	 *
60
	 * For using a different database connection, the name of the database connection
61
	 * can be also given in the "config" parameter. In this case, use e.g.
62
	 *  config['dbname'] = 'db-cache'
63
	 *
64
	 * If a site ID is given, the cache is partitioned for different
65
	 * sites. This also includes access control so cached values can be only
66
	 * retrieved from the same site. Specify a site ID with
67
	 *  config['siteid'] = 123
68
	 *
69
	 * @param array $config Associative list with SQL statements, search attribute definitions and database name
70
	 * @param \Aimeos\MW\DB\Manager\Iface $dbm Database manager
71
	 */
72
	public function __construct( array $config, \Aimeos\MW\DB\Manager\Iface $dbm )
73
	{
74
		if( !isset( $config['search'] ) ) {
75
			throw new \Aimeos\MW\Cache\Exception( 'Search config is missing' );
76
		}
77
78
		if( !isset( $config['sql'] ) ) {
79
			throw new \Aimeos\MW\Cache\Exception( 'SQL config is missing' );
80
		}
81
82
		$this->checkSearchConfig( $config['search'] );
83
		$this->checkSqlConfig( $config['sql'] );
84
85
		$this->dbname = ( isset( $config['dbname'] ) ? $config['dbname'] : 'db' );
86
		$this->siteid = ( isset( $config['siteid'] ) ? $config['siteid'] : null );
87
		$this->searchConfig = $config['search'];
88
		$this->sql = $config['sql'];
89
		$this->dbm = $dbm;
90
	}
91
92
93
	/**
94
	 * Removes all expired cache entries.
95
	 *
96
	 * @throws \Aimeos\MW\Cache\Exception If the cache server doesn't respond
97
	 */
98
	public function cleanup()
99
	{
100
		$conn = $this->dbm->acquire( $this->dbname );
101
102
		try
103
		{
104
			$date = date( 'Y-m-d H:i:00' );
105
			$search = new \Aimeos\MW\Criteria\SQL( $conn );
106
			$search->setConditions( $search->compare( '<', $this->searchConfig['cache.expire']['code'], $date ) );
107
108
			$types = $this->getSearchTypes( $this->searchConfig );
109
			$translations = $this->getSearchTranslations( $this->searchConfig );
110
			$conditions = $search->getConditionString( $types, $translations );
111
112
			$stmt = $conn->create( str_replace( ':cond', $conditions, $this->sql['delete'] ) );
113
			$stmt->bind( 1, $this->siteid, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
114
			$stmt->execute()->finish();
115
116
			$this->dbm->release( $conn, $this->dbname );
117
		}
118
		catch( \Exception $e )
119
		{
120
			$this->dbm->release( $conn, $this->dbname );
121
			throw $e;
122
		}
123
	}
124
125
126
	/**
127
	 * Removes the cache entries identified by the given keys.
128
	 *
129
	 * @param iterable $keys List of key strings that identify the cache entries
130
	 * 	that should be removed
131
	 * @throws \Aimeos\MW\Cache\Exception If the cache server doesn't respond
132
	 */
133
	public function deleteMultiple( $keys )
134
	{
135
		$conn = $this->dbm->acquire( $this->dbname );
136
137
		try
138
		{
139
			$search = new \Aimeos\MW\Criteria\SQL( $conn );
140
			$search->setConditions( $search->compare( '==', $this->searchConfig['cache.id']['code'], $keys ) );
141
142
			$types = $this->getSearchTypes( $this->searchConfig );
143
			$translations = $this->getSearchTranslations( $this->searchConfig );
144
			$conditions = $search->getConditionString( $types, $translations );
145
146
			$stmt = $conn->create( str_replace( ':cond', $conditions, $this->sql['delete'] ) );
147
			$stmt->bind( 1, $this->siteid, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
148
			$stmt->execute()->finish();
149
150
			$this->dbm->release( $conn, $this->dbname );
151
		}
152
		catch( \Exception $e )
153
		{
154
			$this->dbm->release( $conn, $this->dbname );
155
			throw $e;
156
		}
157
	}
158
159
160
	/**
161
	 * Removes the cache entries identified by the given tags.
162
	 *
163
	 * @param string[] $tags List of tag strings that are associated to one or more
164
	 * 	cache entries that should be removed
165
	 * @throws \Aimeos\MW\Cache\Exception If the cache server doesn't respond
166
	 */
167
	public function deleteByTags( array $tags )
168
	{
169
		$conn = $this->dbm->acquire( $this->dbname );
170
171
		try
172
		{
173
			$search = new \Aimeos\MW\Criteria\SQL( $conn );
174
			$search->setConditions( $search->compare( '==', $this->searchConfig['cache.tag.name']['code'], $tags ) );
175
176
			$types = $this->getSearchTypes( $this->searchConfig );
177
			$translations = $this->getSearchTranslations( $this->searchConfig );
178
			$conditions = $search->getConditionString( $types, $translations );
179
180
			$stmt = $conn->create( str_replace( ':cond', $conditions, $this->sql['deletebytag'] ) );
181
			$stmt->bind( 1, $this->siteid, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
182
			$stmt->bind( 2, $this->siteid, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
183
			$stmt->execute()->finish();
184
185
			$this->dbm->release( $conn, $this->dbname );
186
		}
187
		catch( \Exception $e )
188
		{
189
			$this->dbm->release( $conn, $this->dbname );
190
			throw $e;
191
		}
192
	}
193
194
195
	/**
196
	 * Removes all entries of the site from the cache.
197
	 *
198
	 * @throws \Aimeos\MW\Cache\Exception If the cache server doesn't respond
199
	 */
200
	public function clear()
201
	{
202
		$conn = $this->dbm->acquire( $this->dbname );
203
204
		try
205
		{
206
			$stmt = $conn->create( str_replace( ':cond', '1=1', $this->sql['delete'] ) );
207
			$stmt->bind( 1, $this->siteid, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
208
			$stmt->execute()->finish();
209
210
			$this->dbm->release( $conn, $this->dbname );
211
		}
212
		catch( \Exception $e )
213
		{
214
			$this->dbm->release( $conn, $this->dbname );
215
			throw $e;
216
		}
217
	}
218
219
220
	/**
221
	 * Returns the cached values for the given cache keys if available.
222
	 *
223
	 * @param iterable $keys List of key strings for the requested cache entries
224
	 * @param mixed $default Default value to return for keys that do not exist
225
	 * @return array Associative list of key/value pairs for the requested cache
226
	 * 	entries. If a cache entry doesn't exist, neither its key nor a value
227
	 * 	will be in the result list
228
	 * @throws \Aimeos\MW\Cache\Exception If the cache server doesn't respond
229
	 */
230
	public function getMultiple( $keys, $default = null )
231
	{
232
		$list = array();
233
		$conn = $this->dbm->acquire( $this->dbname );
234
235
		try
236
		{
237
			$search = new \Aimeos\MW\Criteria\SQL( $conn );
238
			$expires = array(
239
				$search->compare( '>', 'cache.expire', date( 'Y-m-d H:i:00' ) ),
240
				$search->compare( '==', 'cache.expire', null ),
241
			);
242
			$expr = array(
243
				$search->compare( '==', 'cache.id', $keys ),
244
				$search->combine( '||', $expires ),
245
			);
246
			$search->setConditions( $search->combine( '&&', $expr ) );
247
248
			$types = $this->getSearchTypes( $this->searchConfig );
249
			$translations = $this->getSearchTranslations( $this->searchConfig );
250
			$conditions = $search->getConditionString( $types, $translations );
251
252
			$stmt = $conn->create( str_replace( ':cond', $conditions, $this->sql['get'] ) );
253
			$stmt->bind( 1, $this->siteid, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
254
			$result = $stmt->execute();
255
256
			while( ( $row = $result->fetch() ) !== false ) {
257
				$list[ $row['id'] ] = $row['value'];
258
			}
259
260
			$this->dbm->release( $conn, $this->dbname );
261
		}
262
		catch( \Exception $e )
263
		{
264
			$this->dbm->release( $conn, $this->dbname );
265
			throw $e;
266
		}
267
268
		foreach( $keys as $key )
269
		{
270
			if( !isset( $list[$key] ) ) {
271
				$list[$key] = $default;
272
			}
273
		}
274
275
		return $list;
276
	}
277
278
279
	/**
280
	 * Returns the cached keys and values associated to the given tags if available.
281
	 *
282
	 * @param string[] $tags List of tag strings associated to the requested cache entries
283
	 * @return array Associative list of key/value pairs for the requested cache
284
	 * 	entries. If a tag isn't associated to any cache entry, nothing is returned
285
	 * 	for that tag
286
	 * @throws \Aimeos\MW\Cache\Exception If the cache server doesn't respond
287
	 */
288
	public function getMultipleByTags( array $tags )
289
	{
290
		$list = array();
291
		$conn = $this->dbm->acquire( $this->dbname );
292
293
		try
294
		{
295
			$search = new \Aimeos\MW\Criteria\SQL( $conn );
296
			$expires = array(
297
				$search->compare( '>', 'cache.expire', date( 'Y-m-d H:i:00' ) ),
298
				$search->compare( '==', 'cache.expire', null ),
299
			);
300
			$expr = array(
301
				$search->compare( '==', 'cache.tag.name', $tags ),
302
				$search->combine( '||', $expires ),
303
			);
304
			$search->setConditions( $search->combine( '&&', $expr ) );
305
306
			$types = $this->getSearchTypes( $this->searchConfig );
307
			$translations = $this->getSearchTranslations( $this->searchConfig );
308
			$conditions = $search->getConditionString( $types, $translations );
309
310
			$stmt = $conn->create( str_replace( ':cond', $conditions, $this->sql['getbytag'] ) );
311
			$stmt->bind( 1, $this->siteid, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
312
			$stmt->bind( 2, $this->siteid, \Aimeos\MW\DB\Statement\Base::PARAM_INT );
313
			$result = $stmt->execute();
314
315
			while( ( $row = $result->fetch() ) !== false ) {
316
				$list[ $row['id'] ] = $row['value'];
317
			}
318
319
			$this->dbm->release( $conn, $this->dbname );
320
		}
321
		catch( \Exception $e )
322
		{
323
			$this->dbm->release( $conn, $this->dbname );
324
			throw $e;
325
		}
326
327
		return $list;
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
	 * @param iterable $pairs Associative list of key/value pairs. Both must be
336
	 * 	a string
337
	 * @param int|string|array $expires Associative list of keys and datetime
0 ignored issues
show
Documentation introduced by
Should the type for parameter $expires not be integer|string|array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
338
	 *  string or integer TTL pairs.
339
	 * @param array $tags Associative list of key/tag or key/tags pairs that
340
	 *  should be associated to the values identified by their key. The value
341
	 *  associated to the key can either be a tag string or an array of tag strings
342
	 * @return null
343
	 * @throws \Aimeos\MW\Cache\Exception If the cache server doesn't respond
344
	 */
345
	public function setMultiple( $pairs, $expires = null, array $tags = array() )
346
	{
347
		// Remove existing entries first to avoid duplicate key conflicts
348
		$this->deleteMultiple( array_keys( $pairs ) );
0 ignored issues
show
Documentation introduced by
array_keys($pairs) is of type array, but the function expects a object<Aimeos\MW\Cache\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

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