Passed
Push — master ( dae079...f281af )
by Aimeos
05:19
created

lib/mshoplib/src/MShop/Index/Manager/DBBase.php (1 issue)

Severity
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2020
6
 * @package MShop
7
 * @subpackage Index
8
 */
9
10
11
namespace Aimeos\MShop\Index\Manager;
12
13
14
/**
15
 * Base class for all database based index managers
16
 *
17
 * @package MShop
18
 * @subpackage Index
19
 */
20
abstract class DBBase
21
	extends \Aimeos\MShop\Common\Manager\Base
22
	implements \Aimeos\MShop\Product\Manager\Iface
23
{
24
	private $manager;
25
26
27
	/**
28
	 * Initializes the manager object
29
	 *
30
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context object
31
	 */
32
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
33
	{
34
		parent::__construct( $context );
35
36
		$this->setResourceName( 'db-product' );
37
		$this->manager = \Aimeos\MShop::create( $this->getContext(), 'product' );
38
	}
39
40
41
	/**
42
	 * Removes old entries from the storage.
43
	 *
44
	 * @param string[] $siteids List of IDs for sites whose entries should be deleted
45
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
46
	 */
47
	public function clear( array $siteids ) : \Aimeos\MShop\Common\Manager\Iface
48
	{
49
		foreach( $this->getSubManagers() as $submanager ) {
50
			$submanager->clear( $siteids );
51
		}
52
53
		return $this;
54
	}
55
56
57
	/**
58
	 * Creates a new empty item instance
59
	 *
60
	 * @param array $values Values the item should be initialized with
61
	 * @return \Aimeos\MShop\Product\Item\Iface New product item object
62
	 */
63
	public function createItem( array $values = [] ) : \Aimeos\MShop\Common\Item\Iface
64
	{
65
		return $this->manager->createItem( $values );
66
	}
67
68
69
	/**
70
	 * Creates a search object and optionally sets its base criteria
71
	 *
72
	 * @param bool $default True to add the default criteria
73
	 * @return \Aimeos\MW\Criteria\Iface Criteria object
74
	 */
75
	public function createSearch( bool $default = false ) : \Aimeos\MW\Criteria\Iface
76
	{
77
		return $this->manager->createSearch( $default );
78
	}
79
80
81
	/**
82
	 * Removes multiple items.
83
	 *
84
	 * @param \Aimeos\MShop\Common\Item\Iface[]|string[] $itemIds List of item objects or IDs of the items
85
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
86
	 */
87
	public function deleteItems( array $itemIds ) : \Aimeos\MShop\Common\Manager\Iface
88
	{
89
		if( empty( $itemIds ) ) { return $this; }
90
91
		foreach( $this->getSubManagers() as $submanager ) {
92
			$submanager->deleteItems( $itemIds );
93
		}
94
95
		return $this;
96
	}
97
98
99
	/**
100
	 * Returns the item specified by its code and domain/type if necessary
101
	 *
102
	 * @param string $code Code of the item
103
	 * @param string[] $ref List of domains to fetch list items and referenced items for
104
	 * @param string|null $domain Domain of the item if necessary to identify the item uniquely
105
	 * @param string|null $type Type code of the item if necessary to identify the item uniquely
106
	 * @param bool $default True to add default criteria
107
	 * @return \Aimeos\MShop\Common\Item\Iface Item object
108
	 */
109
	public function findItem( string $code, array $ref = [], string $domain = 'product', string $type = null,
110
		bool $default = false ) : \Aimeos\MShop\Common\Item\Iface
111
	{
112
		return $this->manager->findItem( $code, $ref, $domain, $type, $default );
113
	}
114
115
116
	/**
117
	 * Returns the product item for the given ID
118
	 *
119
	 * @param string $id Id of item
120
	 * @param string[] $ref List of domains to fetch list items and referenced items for
121
	 * @param bool $default Add default criteria
122
	 * @return \Aimeos\MShop\Product\Item\Iface Product item object
123
	 */
124
	public function getItem( string $id, array $ref = [], bool $default = false ) : \Aimeos\MShop\Common\Item\Iface
125
	{
126
		return $this->manager->getItem( $id, $ref, $default );
127
	}
128
129
130
	/**
131
	 * Returns a list of attribute objects describing the available criteria for searching
132
	 *
133
	 * @param bool $withsub True to return attributes of sub-managers too
134
	 * @return array List of items implementing \Aimeos\MW\Criteria\Attribute\Iface
135
	 */
136
	public function getSearchAttributes( bool $withsub = true ) : array
137
	{
138
		return $this->manager->getSearchAttributes( $withsub );
139
	}
140
141
142
	/**
143
	 * Rebuilds the customer index
144
	 *
145
	 * @param \Aimeos\MShop\Product\Item\Iface[] $items Associative list of product IDs and items values
146
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
147
	 */
148
	public function rebuild( array $items = [] ) : \Aimeos\MShop\Index\Manager\Iface
149
	{
150
		foreach( $this->getSubManagers() as $submanager ) {
151
			$submanager->rebuild( $items );
152
		}
153
154
		return $this;
155
	}
156
157
158
	/**
159
	 * Stores a new item into the index
160
	 *
161
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item
162
	 * @param bool $fetch True if the new ID should be set in the item
163
	 * @return \Aimeos\MShop\Product\Item\Iface Saved item
164
	 */
165
	public function saveItem( \Aimeos\MShop\Product\Item\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Product\Item\Iface
166
	{
167
		$item = $this->manager->saveItem( $item, true );
168
		$this->rebuild( [$item->getId() => $item] );
169
170
		return $item;
171
	}
172
173
174
	/**
175
	 * Adds or updates a list of items
176
	 *
177
	 * @param \Aimeos\MShop\Product\Item\Iface[] $items List of items whose data should be saved
178
	 * @param bool $fetch True if the new ID should be returned in the item
179
	 * @return \Aimeos\MShop\Product\Item\Iface[] Saved items
180
	 */
181
	public function saveItems( array $items, bool $fetch = true ) : array
182
	{
183
		$list = [];
184
185
		foreach( $this->manager->saveItems( $items, true ) as $item ) {
186
			$list[$item->getId()] = $item;
187
		}
188
189
		$this->rebuild( $list );
190
		return $list;
191
	}
192
193
194
	/**
195
	 * Removes all entries not touched after the given timestamp
196
	 *
197
	 * @param string $timestamp Timestamp in ISO format (YYYY-MM-DD HH:mm:ss)
198
	 * @param string $path Configuration path to the SQL statement to execute
199
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
200
	 */
201
	protected function cleanupBase( string $timestamp, string $path ) : \Aimeos\MShop\Index\Manager\Iface
202
	{
203
		$context = $this->getContext();
204
		$siteid = $context->getLocale()->getSiteId();
205
206
207
		$this->begin();
208
209
		$dbm = $context->getDatabaseManager();
210
		$dbname = $this->getResourceName();
211
		$conn = $dbm->acquire( $dbname );
212
213
		try
214
		{
215
			$stmt = $this->getCachedStatement( $conn, $path );
216
217
			$stmt->bind( 1, $timestamp ); // ctime
218
			$stmt->bind( 2, $siteid );
219
220
			$stmt->execute()->finish();
221
222
			$dbm->release( $conn, $dbname );
223
		}
224
		catch( \Exception $e )
225
		{
226
			$dbm->release( $conn, $dbname );
227
			$this->rollback();
228
			throw $e;
229
		}
230
231
		$this->commit();
232
233
		foreach( $this->getSubManagers() as $submanager ) {
234
			$submanager->cleanup( $timestamp );
235
		}
236
237
		return $this;
238
	}
239
240
241
	/**
242
	 * Removes several items from the index
243
	 *
244
	 * @param string[] $ids List of product IDs
245
	 * @param string $path Configuration path to the SQL statement to execute
246
	 * @param bool $siteidcheck If siteid should be used in the statement
247
	 * @param string $name Name of the ID column
248
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
249
	 */
250
	protected function deleteItemsBase( array $ids, string $path, bool $siteidcheck = true,
251
		string $name = 'prodid' ) : \Aimeos\MShop\Common\Manager\Iface
252
	{
253
		if( empty( $ids ) ) { return $this; }
254
255
		foreach( $this->getSubManagers() as $submanager ) {
256
			$submanager->deleteItems( $ids );
257
		}
258
259
		return parent::deleteItemsBase( $ids, $path, $siteidcheck, $name );
260
	}
261
262
263
	/**
264
	 * Returns the product manager instance
265
	 *
266
	 * @return \Aimeos\MShop\Product\Manager\Iface Product manager object
267
	 */
268
	protected function getManager() : \Aimeos\MShop\Common\Manager\Iface
269
	{
270
		return $this->manager;
271
	}
272
273
274
	/**
275
	 * Returns the string replacements for the SQL statements
276
	 *
277
	 * @param \Aimeos\MW\Criteria\Iface $search Search critera object
278
	 * @param \Aimeos\MW\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items
279
	 * @param \Aimeos\MW\Criteria\Plugin\Iface[] $plugins Associative list of item keys and criteria plugin objects
280
	 * @param string[] $joins Associative list of SQL joins
281
	 * @param \Aimeos\MW\Criteria\Attribute\Iface[] $columns Additional columns to retrieve values from
282
	 * @return array Array of keys, find and replace arrays
283
	 */
284
	protected function getSQLReplacements( \Aimeos\MW\Criteria\Iface $search, array $attributes, array $plugins,
285
		array $joins, array $columns = [] ) : array
286
	{
287
		$list = [];
288
		$types = $this->getSearchTypes( $attributes );
289
		$funcs = $this->getSearchFunctions( $attributes );
290
		$translations = $this->getSearchTranslations( $attributes );
291
292
		$colstring = '';
293
		foreach( $columns as $name => $entry ) {
294
			$colstring .= $entry->getInternalCode() . ', ';
295
		}
296
297
		$find = array( ':columns', ':joins', ':cond', ':start', ':size' );
298
		$replace = array(
299
			$colstring,
300
			implode( "\n", array_unique( $joins ) ),
301
			$search->getConditionSource( $types, $translations, $plugins, $funcs ),
302
			$search->getSliceStart(),
303
			$search->getSliceSize(),
304
		);
305
306
		if( empty( $search->getSortations() ) && ( $attribute = reset( $attributes ) ) !== false )
307
		{
308
			$search = ( clone $search )->setSortations( [$search->sort( '+', $attribute->getCode() )] );
309
		}
310
		elseif( !empty( $search->getSortations() ) )
311
		{
312
			$names = $search->translate( $search->getSortations(), [], $funcs );
0 ignored issues
show
The call to Aimeos\MW\Criteria\Iface::translate() has too many arguments starting with $funcs. ( Ignorable by Annotation )

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

312
			/** @scrutinizer ignore-call */ 
313
   $names = $search->translate( $search->getSortations(), [], $funcs );

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
313
			$cols = $search->translate( $search->getSortations(), $translations, $funcs );
314
315
			$list = $translations = [];
316
			foreach( $cols as $idx => $col )
317
			{
318
				$list[] = 'MIN(' . $col . ') AS "s' . $idx . '"';
319
				$translations[$names[$idx]] = '"s' . $idx . '"';
320
			}
321
		}
322
323
		$find[] = ':mincols';
324
		$replace[] = !empty( $list ) ? ', ' . implode( ', ', $list ) : '';
325
326
		$find[] = ':order';
327
		$replace[] = $search->getSortationSource( $types, $translations, $funcs );
328
329
		return [$find, $replace];
330
	}
331
332
333
	/**
334
	 * Optimizes the catalog customer index if necessary
335
	 *
336
	 * @param string $path Configuration path to the SQL statements to execute
337
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
338
	 */
339
	protected function optimizeBase( string $path ) : \Aimeos\MShop\Index\Manager\Iface
340
	{
341
		$context = $this->getContext();
342
343
		$dbm = $context->getDatabaseManager();
344
		$dbname = $this->getResourceName();
345
		$conn = $dbm->acquire( $dbname );
346
347
		try
348
		{
349
			foreach( (array) $this->getSqlConfig( $path ) as $sql ) {
350
				$conn->create( $sql )->execute()->finish();
351
			}
352
353
			$dbm->release( $conn, $dbname );
354
		}
355
		catch( \Exception $e )
356
		{
357
			$dbm->release( $conn, $dbname );
358
			throw $e;
359
		}
360
361
		foreach( $this->getSubManagers() as $submanager ) {
362
			$submanager->optimize();
363
		}
364
365
		return $this;
366
	}
367
368
369
	/**
370
	 * Searches for items matching the given criteria.
371
	 *
372
	 * @param \Aimeos\MW\Criteria\Iface $search Search criteria
373
	 * @param string[] $ref List of domains to fetch list items and referenced items for
374
	 * @param int &$total Total number of items matched by the given criteria
375
	 * @param string $cfgPathSearch Configuration path to the search SQL statement
376
	 * @param string $cfgPathCount Configuration path to the count SQL statement
377
	 * @return \Aimeos\MShop\Product\Item\Iface[] List of product items
378
	 */
379
	protected function searchItemsIndexBase( \Aimeos\MW\Criteria\Iface $search,
380
		array $ref, int &$total = null, string $cfgPathSearch, string $cfgPathCount ) : \Aimeos\Map
381
	{
382
		$list = $ids = [];
383
		$context = $this->getContext();
384
385
		$dbm = $context->getDatabaseManager();
386
		$dbname = $this->getResourceName();
387
		$conn = $dbm->acquire( $dbname );
388
389
		try
390
		{
391
			$required = array( 'product' );
392
393
			/** mshop/index/manager/sitemode
394
			 * Mode how items from levels below or above in the site tree are handled
395
			 *
396
			 * By default, only items from the current site are fetched from the
397
			 * storage. If the ai-sites extension is installed, you can create a
398
			 * tree of sites. Then, this setting allows you to define for the
399
			 * whole index domain if items from parent sites are inherited,
400
			 * sites from child sites are aggregated or both.
401
			 *
402
			 * Available constants for the site mode are:
403
			 * * 0 = only items from the current site
404
			 * * 1 = inherit items from parent sites
405
			 * * 2 = aggregate items from child sites
406
			 * * 3 = inherit and aggregate items at the same time
407
			 *
408
			 * You also need to set the mode in the locale manager
409
			 * (mshop/locale/manager/standard/sitelevel) to one of the constants.
410
			 * If you set it to the same value, it will work as described but you
411
			 * can also use different modes. For example, if inheritance and
412
			 * aggregation is configured the locale manager but only inheritance
413
			 * in the domain manager because aggregating items makes no sense in
414
			 * this domain, then items wil be only inherited. Thus, you have full
415
			 * control over inheritance and aggregation in each domain.
416
			 *
417
			 * @param int Constant from Aimeos\MShop\Locale\Manager\Base class
418
			 * @category Developer
419
			 * @since 2018.01
420
			 * @see mshop/locale/manager/standard/sitelevel
421
			 */
422
			$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
423
			$level = $context->getConfig()->get( 'mshop/index/manager/sitemode', $level );
424
425
			$results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount, $required, $total, $level );
426
427
			while( ( $row = $results->fetch() ) !== null ) {
428
				$ids[] = $row['id'];
429
			}
430
431
			$dbm->release( $conn, $dbname );
432
		}
433
		catch( \Exception $e )
434
		{
435
			$dbm->release( $conn, $dbname );
436
			throw $e;
437
		}
438
439
		$manager = \Aimeos\MShop::create( $context, 'product' );
440
		$prodSearch = $manager->createSearch();
441
		$prodSearch->setConditions( $prodSearch->compare( '==', 'product.id', $ids ) );
442
		$prodSearch->setSlice( 0, $search->getSliceSize() );
443
		$items = $manager->searchItems( $prodSearch, $ref );
444
445
		foreach( $ids as $id )
446
		{
447
			if( isset( $items[$id] ) ) {
448
				$list[$id] = $items[$id];
449
			}
450
		}
451
452
		return map( $list );
453
	}
454
455
456
	/**
457
	 * Returns the sub-manager instances available for the manager
458
	 *
459
	 * @return array Associative list of the sub-domain as key and the manager object as value
460
	 */
461
	abstract protected function getSubManagers() : array;
462
}
463