Passed
Push — master ( b9f346...e7b30c )
by Aimeos
06:21
created

DBBase::saveItems()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 10
rs 10
1
<?php
2
3
/**
4
 * @license LGPLv3, https://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 get( string $id, array $ref = [], bool $default = false ) : \Aimeos\MShop\Common\Item\Iface
125
	{
126
		return $this->manager->get( $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
	 * Removes the products from the product index.
160
	 *
161
	 * @param array|string $ids Product ID or list of IDs
162
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
163
	 */
164
	 public function remove( $ids ) : \Aimeos\MShop\Index\Manager\Iface
165
	 {
166
		foreach( $this->getSubManagers() as $submanager ) {
167
			$submanager->remove( $ids );
168
		}
169
170
		return $this;
171
	 }
172
173
174
	/**
175
	 * Stores a new item into the index
176
	 *
177
	 * @param \Aimeos\MShop\Product\Item\Iface $item Product item
178
	 * @param bool $fetch True if the new ID should be set in the item
179
	 * @return \Aimeos\MShop\Product\Item\Iface Saved item
180
	 */
181
	public function saveItem( \Aimeos\MShop\Product\Item\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Product\Item\Iface
182
	{
183
		$item = $this->manager->saveItem( $item, true );
184
		$this->rebuild( [$item->getId() => $item] );
185
186
		return $item;
187
	}
188
189
190
	/**
191
	 * Adds or updates a list of items
192
	 *
193
	 * @param \Aimeos\MShop\Product\Item\Iface[] $items List of items whose data should be saved
194
	 * @param bool $fetch True if the new ID should be returned in the item
195
	 * @return \Aimeos\MShop\Product\Item\Iface[] Saved items
196
	 */
197
	public function saveItems( array $items, bool $fetch = true ) : array
198
	{
199
		$list = [];
200
201
		foreach( $this->manager->saveItems( $items, true ) as $item ) {
202
			$list[$item->getId()] = $item;
203
		}
204
205
		$this->rebuild( $list );
206
		return $list;
207
	}
208
209
210
	/**
211
	 * Removes all entries not touched after the given timestamp
212
	 *
213
	 * @param string $timestamp Timestamp in ISO format (YYYY-MM-DD HH:mm:ss)
214
	 * @param string $path Configuration path to the SQL statement to execute
215
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
216
	 */
217
	protected function cleanupBase( string $timestamp, string $path ) : \Aimeos\MShop\Index\Manager\Iface
218
	{
219
		$context = $this->getContext();
220
		$siteid = $context->getLocale()->getSiteId();
221
222
223
		$this->begin();
224
225
		$dbm = $context->getDatabaseManager();
226
		$dbname = $this->getResourceName();
227
		$conn = $dbm->acquire( $dbname );
228
229
		try
230
		{
231
			$stmt = $this->getCachedStatement( $conn, $path );
232
233
			$stmt->bind( 1, $timestamp ); // ctime
234
			$stmt->bind( 2, $siteid );
235
236
			$stmt->execute()->finish();
237
238
			$dbm->release( $conn, $dbname );
239
		}
240
		catch( \Exception $e )
241
		{
242
			$dbm->release( $conn, $dbname );
243
			$this->rollback();
244
			throw $e;
245
		}
246
247
		$this->commit();
248
249
		foreach( $this->getSubManagers() as $submanager ) {
250
			$submanager->cleanup( $timestamp );
251
		}
252
253
		return $this;
254
	}
255
256
257
	/**
258
	 * Removes several items from the index
259
	 *
260
	 * @param string[] $ids List of product IDs
261
	 * @param string $path Configuration path to the SQL statement to execute
262
	 * @param bool $siteidcheck If siteid should be used in the statement
263
	 * @param string $name Name of the ID column
264
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
265
	 */
266
	protected function deleteItemsBase( array $ids, string $path, bool $siteidcheck = true,
267
		string $name = 'prodid' ) : \Aimeos\MShop\Common\Manager\Iface
268
	{
269
		if( empty( $ids ) ) { return $this; }
270
271
		foreach( $this->getSubManagers() as $submanager ) {
272
			$submanager->deleteItems( $ids );
273
		}
274
275
		return parent::deleteItemsBase( $ids, $path, $siteidcheck, $name );
276
	}
277
278
279
	/**
280
	 * Returns the product manager instance
281
	 *
282
	 * @return \Aimeos\MShop\Product\Manager\Iface Product manager object
283
	 */
284
	protected function getManager() : \Aimeos\MShop\Common\Manager\Iface
285
	{
286
		return $this->manager;
287
	}
288
289
290
	/**
291
	 * Returns the string replacements for the SQL statements
292
	 *
293
	 * @param \Aimeos\MW\Criteria\Iface $search Search critera object
294
	 * @param \Aimeos\MW\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items
295
	 * @param \Aimeos\MW\Criteria\Plugin\Iface[] $plugins Associative list of item keys and criteria plugin objects
296
	 * @param string[] $joins Associative list of SQL joins
297
	 * @param \Aimeos\MW\Criteria\Attribute\Iface[] $columns Additional columns to retrieve values from
298
	 * @return array Array of keys, find and replace arrays
299
	 */
300
	protected function getSQLReplacements( \Aimeos\MW\Criteria\Iface $search, array $attributes, array $plugins,
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

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