Passed
Push — master ( 494e53...aeeda4 )
by Aimeos
09:49
created

DBBase::optimizeBase()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 14
rs 10
c 0
b 0
f 0
cc 3
nc 4
nop 1
1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2022
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\ContextIface $context Context object
31
	 */
32
	public function __construct( \Aimeos\MShop\ContextIface $context )
33
	{
34
		parent::__construct( $context );
35
36
		$this->setResourceName( 'db-product' );
37
		$this->manager = \Aimeos\MShop::create( $this->context(), 'product' );
38
	}
39
40
41
	/**
42
	 * Removes old entries from the storage.
43
	 *
44
	 * @param iterable $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( iterable $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 create( array $values = [] ) : \Aimeos\MShop\Common\Item\Iface
64
	{
65
		return $this->manager->create( $values );
66
	}
67
68
69
	/**
70
	 * Creates a filter object.
71
	 *
72
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
73
	 * @param bool $site TRUE for adding site criteria to limit items by the site of related items
74
	 * @return \Aimeos\Base\Criteria\Iface Returns the filter object
75
	 */
76
	public function filter( ?bool $default = false, bool $site = false ) : \Aimeos\Base\Criteria\Iface
77
	{
78
		return $this->manager->filter( $default );
79
	}
80
81
82
	/**
83
	 * Removes multiple items.
84
	 *
85
	 * @param \Aimeos\MShop\Common\Item\Iface|\Aimeos\Map|array|string $items Item object, ID or a list of them
86
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
87
	 */
88
	public function delete( $itemIds ) : \Aimeos\MShop\Common\Manager\Iface
89
	{
90
		foreach( $this->getSubManagers() as $submanager ) {
91
			$submanager->delete( $itemIds );
92
		}
93
94
		return $this;
95
	}
96
97
98
	/**
99
	 * Returns the item specified by its code and domain/type if necessary
100
	 *
101
	 * @param string $code Code of the item
102
	 * @param string[] $ref List of domains to fetch list items and referenced items for
103
	 * @param string|null $domain Domain of the item if necessary to identify the item uniquely
104
	 * @param string|null $type Type code of the item if necessary to identify the item uniquely
105
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
106
	 * @return \Aimeos\MShop\Common\Item\Iface Item object
107
	 */
108
	public function find( string $code, array $ref = [], string $domain = 'product', string $type = null,
109
		?bool $default = false ) : \Aimeos\MShop\Common\Item\Iface
110
	{
111
		return $this->manager->find( $code, $ref, $domain, $type, $default );
112
	}
113
114
115
	/**
116
	 * Returns the product item for the given ID
117
	 *
118
	 * @param string $id Id of item
119
	 * @param string[] $ref List of domains to fetch list items and referenced items for
120
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
121
	 * @return \Aimeos\MShop\Product\Item\Iface Product item object
122
	 */
123
	public function get( string $id, array $ref = [], ?bool $default = false ) : \Aimeos\MShop\Common\Item\Iface
124
	{
125
		return $this->manager->get( $id, $ref, $default );
126
	}
127
128
129
	/**
130
	 * Returns a list of attribute objects describing the available criteria for searching
131
	 *
132
	 * @param bool $withsub True to return attributes of sub-managers too
133
	 * @return array List of items implementing \Aimeos\Base\Criteria\Attribute\Iface
134
	 */
135
	public function getSearchAttributes( bool $withsub = true ) : array
136
	{
137
		return $this->manager->getSearchAttributes( $withsub );
138
	}
139
140
141
	/**
142
	 * Iterates over all matched items and returns the found ones
143
	 *
144
	 * @param \Aimeos\MShop\Common\Cursor\Iface $cursor Cursor object with filter, domains and cursor
145
	 * @param string[] $ref List of domains whose items should be fetched too
146
	 * @return \Aimeos\Map|null List of items implementing \Aimeos\MShop\Common\Item\Iface with ids as keys
147
	 */
148
	public function iterate( \Aimeos\MShop\Common\Cursor\Iface $cursor, array $ref = [] ) : ?\Aimeos\Map
149
	{
150
		if( $cursor->value() === '' ) {
151
			return null;
152
		}
153
154
		$filter = $cursor->filter()->add( 'product.id', '>', (int) $cursor->value() )->order( 'product.id' );
155
		$items = $this->search( $filter, $ref );
156
157
		$cursor->setValue( $items->lastKey() ?: '' );
158
		return $items;
159
	}
160
161
162
	/**
163
	 * Updates the rating of the item
164
	 *
165
	 * @param string $id ID of the item
166
	 * @param string $rating Decimal value of the rating
167
	 * @param int $ratings Total number of ratings for the item
168
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
169
	 */
170
	public function rate( string $id, string $rating, int $ratings ) : \Aimeos\MShop\Common\Manager\Iface
171
	{
172
		$this->manager->rate( $id, $rating, $ratings );
173
		return $this;
174
	}
175
176
177
	/**
178
	 * Rebuilds the customer index
179
	 *
180
	 * @param \Aimeos\MShop\Product\Item\Iface[] $items Associative list of product IDs and items values
181
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
182
	 */
183
	public function rebuild( iterable $items = [] ) : \Aimeos\MShop\Index\Manager\Iface
184
	{
185
		foreach( $this->getSubManagers() as $submanager ) {
186
			$submanager->rebuild( $items );
187
		}
188
189
		return $this;
190
	}
191
192
193
	/**
194
	 * Removes the products from the product index.
195
	 *
196
	 * @param iterable|string $ids Product ID or list of IDs
197
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
198
	 */
199
	 public function remove( $ids ) : \Aimeos\MShop\Index\Manager\Iface
200
	 {
201
		foreach( $this->getSubManagers() as $submanager ) {
202
			$submanager->remove( $ids );
203
		}
204
205
		return $this;
206
	}
207
208
209
	/**
210
	 * Adds or updates an item object or a list of them.
211
	 *
212
	 * @param \Aimeos\Map|\Aimeos\MShop\Common\Item\Iface[]|\Aimeos\MShop\Common\Item\Iface $items Item or list of items whose data should be saved
213
	 * @param bool $fetch True if the new ID should be returned in the item
214
	 * @return \Aimeos\Map|\Aimeos\MShop\Common\Item\Iface Saved item or items
215
	 */
216
	public function save( $items, bool $fetch = true )
217
	{
218
		$this->rebuild( map( $this->manager->save( $items, true ) ) );
219
		return $items;
220
	}
221
222
223
	/**
224
	 * Updates if the product is in stock
225
	 *
226
	 * @param string $id ID of the procuct item
227
	 * @param int $value "0" or "1" if product is in stock or not
228
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
229
	 */
230
	public function stock( string $id, int $value ) : \Aimeos\MShop\Common\Manager\Iface
231
	{
232
		$this->manager->stock( $id, $value );
233
		return $this;
234
	}
235
236
237
	/**
238
	 * Removes all entries not touched after the given timestamp
239
	 *
240
	 * @param string $timestamp Timestamp in ISO format (YYYY-MM-DD HH:mm:ss)
241
	 * @param string $path Configuration path to the SQL statement to execute
242
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
243
	 */
244
	protected function cleanupBase( string $timestamp, string $path ) : \Aimeos\MShop\Index\Manager\Iface
245
	{
246
		$context = $this->context();
247
		$siteid = $context->locale()->getSiteId();
248
249
		$this->begin();
250
		$conn = $context->db( $this->getResourceName() );
251
252
		try
253
		{
254
			$stmt = $this->getCachedStatement( $conn, $path );
255
256
			$stmt->bind( 1, $timestamp ); // ctime
257
			$stmt->bind( 2, $siteid );
258
259
			$stmt->execute()->finish();
260
		}
261
		catch( \Exception $e )
262
		{
263
			$this->rollback();
264
			throw $e;
265
		}
266
267
		$this->commit();
268
269
		foreach( $this->getSubManagers() as $submanager ) {
270
			$submanager->cleanup( $timestamp );
271
		}
272
273
		return $this;
274
	}
275
276
277
	/**
278
	 * Removes several items from the index
279
	 *
280
	 * @param string[] $ids List of product IDs
281
	 * @param string $path Configuration path to the SQL statement to execute
282
	 * @param bool $siteidcheck If siteid should be used in the statement
283
	 * @param string $name Name of the ID column
284
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
285
	 */
286
	protected function deleteItemsBase( $ids, string $path, bool $siteidcheck = true,
287
		string $name = 'prodid' ) : \Aimeos\MShop\Common\Manager\Iface
288
	{
289
		foreach( $this->getSubManagers() as $submanager ) {
290
			$submanager->delete( $ids );
291
		}
292
293
		return parent::deleteItemsBase( $ids, $path, $siteidcheck, $name );
294
	}
295
296
297
	/**
298
	 * Returns the product manager instance
299
	 *
300
	 * @return \Aimeos\MShop\Product\Manager\Iface Product manager object
301
	 */
302
	protected function getManager() : \Aimeos\MShop\Common\Manager\Iface
303
	{
304
		return $this->manager;
305
	}
306
307
308
	/**
309
	 * Returns the string replacements for the SQL statements
310
	 *
311
	 * @param \Aimeos\Base\Criteria\Iface $search Search critera object
312
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items
313
	 * @param \Aimeos\Base\Criteria\Plugin\Iface[] $plugins Associative list of item keys and criteria plugin objects
314
	 * @param string[] $joins Associative list of SQL joins
315
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $columns Additional columns to retrieve values from
316
	 * @return array Array of keys, find and replace arrays
317
	 */
318
	protected function getSQLReplacements( \Aimeos\Base\Criteria\Iface $search, array $attributes, array $plugins,
319
		array $joins, array $columns = [] ) : array
320
	{
321
		$list = [];
322
		$types = $this->getSearchTypes( $attributes );
323
		$funcs = $this->getSearchFunctions( $attributes );
324
		$translations = $this->getSearchTranslations( $attributes );
325
326
		$colstring = '';
327
		foreach( $columns as $name => $entry ) {
328
			$colstring .= $entry->getInternalCode() . ', ';
329
		}
330
331
		$find = array( ':columns', ':joins', ':cond', ':start', ':size' );
332
		$replace = array(
333
			$colstring,
334
			implode( "\n", array_unique( $joins ) ),
335
			$search->getConditionSource( $types, $translations, $plugins, $funcs ),
336
			$search->getOffset(),
337
			$search->getLimit(),
338
		);
339
340
		if( empty( $search->getSortations() ) && ( $attribute = reset( $attributes ) ) !== false )
341
		{
342
			$search = ( clone $search )->setSortations( [$search->sort( '+', $attribute->getCode() )] );
343
		}
344
		elseif( !empty( $search->getSortations() ) )
345
		{
346
			$names = $search->translate( $search->getSortations(), [], $funcs );
347
			$cols = $search->translate( $search->getSortations(), $translations, $funcs );
348
349
			$list = $translations = [];
350
			foreach( $cols as $idx => $col )
351
			{
352
				$list[] = 'MIN(' . $col . ') AS "s' . $idx . '"';
353
				$translations[$names[$idx]] = '"s' . $idx . '"';
354
			}
355
		}
356
357
		$find[] = ':mincols';
358
		$replace[] = !empty( $list ) ? ', ' . implode( ', ', $list ) : '';
359
360
		$find[] = ':order';
361
		$replace[] = $search->getSortationSource( $types, $translations, $funcs );
362
363
		return [$find, $replace];
364
	}
365
366
367
	/**
368
	 * Optimizes the catalog customer index if necessary
369
	 *
370
	 * @param string $path Configuration path to the SQL statements to execute
371
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
372
	 */
373
	protected function optimizeBase( string $path ) : \Aimeos\MShop\Index\Manager\Iface
374
	{
375
		$context = $this->context();
376
		$conn = $context->db( $this->getResourceName() );
377
378
		foreach( (array) $this->getSqlConfig( $path ) as $sql ) {
379
			$conn->create( $sql )->execute()->finish();
380
		}
381
382
		foreach( $this->getSubManagers() as $submanager ) {
383
			$submanager->optimize();
384
		}
385
386
		return $this;
387
	}
388
389
390
	/**
391
	 * Searches for items matching the given criteria.
392
	 *
393
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria
394
	 * @param string[] $ref List of domains to fetch list items and referenced items for
395
	 * @param int &$total Total number of items matched by the given criteria
396
	 * @param string $cfgPathSearch Configuration path to the search SQL statement
397
	 * @param string $cfgPathCount Configuration path to the count SQL statement
398
	 * @return \Aimeos\MShop\Product\Item\Iface[] List of product items
399
	 */
400
	protected function searchItemsIndexBase( \Aimeos\Base\Criteria\Iface $search,
401
		array $ref, int &$total = null, string $cfgPathSearch, string $cfgPathCount ) : \Aimeos\Map
402
	{
403
		$list = $ids = [];
404
		$context = $this->context();
405
		$conn = $context->db( $this->getResourceName() );
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/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/sitelevel
437
		 */
438
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
439
		$level = $context->config()->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
		$manager = \Aimeos\MShop::create( $context, 'product' );
448
		$prodSearch = $manager->filter();
449
		$prodSearch->setConditions( $prodSearch->compare( '==', 'product.id', $ids ) );
450
		$prodSearch->slice( 0, $search->getLimit() );
451
		$items = $manager->search( $prodSearch, $ref );
452
453
		foreach( $ids as $id )
454
		{
455
			if( isset( $items[$id] ) ) {
456
				$list[$id] = $items[$id];
457
			}
458
		}
459
460
		return map( $list );
461
	}
462
463
464
	/**
465
	 * Returns the sub-manager instances available for the manager
466
	 *
467
	 * @return array Associative list of the sub-domain as key and the manager object as value
468
	 */
469
	abstract protected function getSubManagers() : array;
470
}
471