Passed
Push — master ( f56612...54f7d5 )
by Aimeos
04:57
created

DBBase::iterateIndexBase()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
c 0
b 0
f 0
dl 0
loc 29
rs 9.2222
cc 6
nc 8
nop 3
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
	 * Updates the rating of the item
143
	 *
144
	 * @param string $id ID of the item
145
	 * @param string $rating Decimal value of the rating
146
	 * @param int $ratings Total number of ratings for the item
147
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
148
	 */
149
	public function rate( string $id, string $rating, int $ratings ) : \Aimeos\MShop\Common\Manager\Iface
150
	{
151
		$this->manager->rate( $id, $rating, $ratings );
152
		return $this;
153
	}
154
155
156
	/**
157
	 * Rebuilds the customer index
158
	 *
159
	 * @param \Aimeos\MShop\Product\Item\Iface[] $items Associative list of product IDs and items values
160
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
161
	 */
162
	public function rebuild( iterable $items = [] ) : \Aimeos\MShop\Index\Manager\Iface
163
	{
164
		foreach( $this->getSubManagers() as $submanager ) {
165
			$submanager->rebuild( $items );
166
		}
167
168
		return $this;
169
	}
170
171
172
	/**
173
	 * Removes the products from the product index.
174
	 *
175
	 * @param array|string $ids Product ID or list of IDs
176
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
177
	 */
178
	 public function remove( $ids ) : \Aimeos\MShop\Index\Manager\Iface
179
	 {
180
		foreach( $this->getSubManagers() as $submanager ) {
181
			$submanager->remove( $ids );
182
		}
183
184
		return $this;
185
	}
186
187
188
	/**
189
	 * Adds or updates an item object or a list of them.
190
	 *
191
	 * @param \Aimeos\Map|\Aimeos\MShop\Common\Item\Iface[]|\Aimeos\MShop\Common\Item\Iface $items Item or list of items whose data should be saved
192
	 * @param bool $fetch True if the new ID should be returned in the item
193
	 * @return \Aimeos\Map|\Aimeos\MShop\Common\Item\Iface Saved item or items
194
	 */
195
	public function save( $items, bool $fetch = true )
196
	{
197
		$this->rebuild( map( $this->manager->save( $items, true ) ) );
198
		return $items;
199
	}
200
201
202
	/**
203
	 * Updates if the product is in stock
204
	 *
205
	 * @param string $id ID of the procuct item
206
	 * @param int $value "0" or "1" if product is in stock or not
207
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
208
	 */
209
	public function stock( string $id, int $value ) : \Aimeos\MShop\Common\Manager\Iface
210
	{
211
		$this->manager->stock( $id, $value );
212
		return $this;
213
	}
214
215
216
	/**
217
	 * Removes all entries not touched after the given timestamp
218
	 *
219
	 * @param string $timestamp Timestamp in ISO format (YYYY-MM-DD HH:mm:ss)
220
	 * @param string $path Configuration path to the SQL statement to execute
221
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
222
	 */
223
	protected function cleanupBase( string $timestamp, string $path ) : \Aimeos\MShop\Index\Manager\Iface
224
	{
225
		$context = $this->context();
226
		$siteid = $context->locale()->getSiteId();
227
228
		$this->begin();
229
		$conn = $context->db( $this->getResourceName() );
230
231
		try
232
		{
233
			$stmt = $this->getCachedStatement( $conn, $path );
234
235
			$stmt->bind( 1, $timestamp ); // ctime
236
			$stmt->bind( 2, $siteid );
237
238
			$stmt->execute()->finish();
239
		}
240
		catch( \Exception $e )
241
		{
242
			$this->rollback();
243
			throw $e;
244
		}
245
246
		$this->commit();
247
248
		foreach( $this->getSubManagers() as $submanager ) {
249
			$submanager->cleanup( $timestamp );
250
		}
251
252
		return $this;
253
	}
254
255
256
	/**
257
	 * Removes several items from the index
258
	 *
259
	 * @param string[] $ids List of product IDs
260
	 * @param string $path Configuration path to the SQL statement to execute
261
	 * @param bool $siteidcheck If siteid should be used in the statement
262
	 * @param string $name Name of the ID column
263
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
264
	 */
265
	protected function deleteItemsBase( $ids, string $path, bool $siteidcheck = true,
266
		string $name = 'prodid' ) : \Aimeos\MShop\Common\Manager\Iface
267
	{
268
		foreach( $this->getSubManagers() as $submanager ) {
269
			$submanager->delete( $ids );
270
		}
271
272
		return parent::deleteItemsBase( $ids, $path, $siteidcheck, $name );
273
	}
274
275
276
	/**
277
	 * Returns the product manager instance
278
	 *
279
	 * @return \Aimeos\MShop\Product\Manager\Iface Product manager object
280
	 */
281
	protected function getManager() : \Aimeos\MShop\Common\Manager\Iface
282
	{
283
		return $this->manager;
284
	}
285
286
287
	/**
288
	 * Returns the string replacements for the SQL statements
289
	 *
290
	 * @param \Aimeos\Base\Criteria\Iface $search Search critera object
291
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items
292
	 * @param \Aimeos\Base\Criteria\Plugin\Iface[] $plugins Associative list of item keys and criteria plugin objects
293
	 * @param string[] $joins Associative list of SQL joins
294
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $columns Additional columns to retrieve values from
295
	 * @return array Array of keys, find and replace arrays
296
	 */
297
	protected function getSQLReplacements( \Aimeos\Base\Criteria\Iface $search, array $attributes, array $plugins,
298
		array $joins, array $columns = [] ) : array
299
	{
300
		$list = [];
301
		$types = $this->getSearchTypes( $attributes );
302
		$funcs = $this->getSearchFunctions( $attributes );
303
		$translations = $this->getSearchTranslations( $attributes );
304
305
		$colstring = '';
306
		foreach( $columns as $name => $entry ) {
307
			$colstring .= $entry->getInternalCode() . ', ';
308
		}
309
310
		$find = array( ':columns', ':joins', ':cond', ':start', ':size' );
311
		$replace = array(
312
			$colstring,
313
			implode( "\n", array_unique( $joins ) ),
314
			$search->getConditionSource( $types, $translations, $plugins, $funcs ),
315
			$search->getOffset(),
316
			$search->getLimit(),
317
		);
318
319
		if( empty( $search->getSortations() ) && ( $attribute = reset( $attributes ) ) !== false )
320
		{
321
			$search = ( clone $search )->setSortations( [$search->sort( '+', $attribute->getCode() )] );
322
		}
323
		elseif( !empty( $search->getSortations() ) )
324
		{
325
			$names = $search->translate( $search->getSortations(), [], $funcs );
326
			$cols = $search->translate( $search->getSortations(), $translations, $funcs );
327
328
			$list = $translations = [];
329
			foreach( $cols as $idx => $col )
330
			{
331
				$list[] = 'MIN(' . $col . ') AS "s' . $idx . '"';
332
				$translations[$names[$idx]] = '"s' . $idx . '"';
333
			}
334
		}
335
336
		$find[] = ':mincols';
337
		$replace[] = !empty( $list ) ? ', ' . implode( ', ', $list ) : '';
338
339
		$find[] = ':order';
340
		$replace[] = $search->getSortationSource( $types, $translations, $funcs );
341
342
		return [$find, $replace];
343
	}
344
345
346
	/**
347
	 * Iterates over all matching items and returns the found ones
348
	 *
349
	 * @param \Aimeos\MShop\Common\Iterator\Iface $iterator Iterator object with conditions, sortations, etc.
350
	 * @param string[] $ref List of domains to fetch list items and referenced items for
351
	 * @param int $count Maximum number of items which should be returned
352
	 * @return \Aimeos\Map|null List of items implementing \Aimeos\MShop\Common\Item\Iface with ids as keys
353
	 */
354
	public function iterateIndexBase( \Aimeos\MShop\Common\Iterator\Iface $iterator, array $ref = [], int $count = 100 ) : ?\Aimeos\Map
355
	{
356
		$ids = $list = [];
357
358
		while( $count-- && $iterator->valid() )
359
		{
360
			$row = $iterator->current();
361
			$ids[] = $row['id'];
362
			$iterator->next();
363
		}
364
365
		if( empty( $ids ) )
366
		{
367
			$iterator->close();
368
			return null;
369
		}
370
371
		$manager = \Aimeos\MShop::create( $this->context(), 'product' );
372
		$prodSearch = $manager->filter()->add( 'product.id', '==', $ids )->slice( 0, count( $ids ) );
373
		$items = $manager->search( $prodSearch, $ref );
374
375
		foreach( $ids as $id )
376
		{
377
			if( isset( $items[$id] ) ) {
378
				$list[$id] = $items[$id];
379
			}
380
		}
381
382
		return map( $list );
383
	}
384
385
386
	/**
387
	 * Returns iterator for rows matching the given criteria.
388
	 *
389
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria
390
	 * @param string $cfgPathSearch Configuration path to the SQL statement for iterating over the results
391
	 * @return \Aimeos\MShop\Common\Iterator\Iface Iterator object
392
	 */
393
	protected function iteratorIndexBase( \Aimeos\Base\Criteria\Iface $search, string $cfgPathSearch ) : \Aimeos\MShop\Common\Iterator\Iface
394
	{
395
		$context = $this->context();
396
		$conn = $context->db( $this->getResourceName(), true );
397
398
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
399
		$level = $context->config()->get( 'mshop/index/manager/sitemode', $level );
400
401
		$total = null;
402
		$result = $this->searchItemsBase( $conn, $search, $cfgPathSearch, '', ['product'], $total, $level );
403
404
		return new \Aimeos\MShop\Common\Iterator\DB( $conn, $result );
405
	}
406
407
408
	/**
409
	 * Optimizes the catalog customer index if necessary
410
	 *
411
	 * @param string $path Configuration path to the SQL statements to execute
412
	 * @return \Aimeos\MShop\Index\Manager\Iface Manager object for chaining method calls
413
	 */
414
	protected function optimizeBase( string $path ) : \Aimeos\MShop\Index\Manager\Iface
415
	{
416
		$context = $this->context();
417
		$conn = $context->db( $this->getResourceName() );
418
419
		foreach( (array) $this->getSqlConfig( $path ) as $sql ) {
420
			$conn->create( $sql )->execute()->finish();
421
		}
422
423
		foreach( $this->getSubManagers() as $submanager ) {
424
			$submanager->optimize();
425
		}
426
427
		return $this;
428
	}
429
430
431
	/**
432
	 * Searches for items matching the given criteria.
433
	 *
434
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria
435
	 * @param string[] $ref List of domains to fetch list items and referenced items for
436
	 * @param int &$total Total number of items matched by the given criteria
437
	 * @param string $cfgPathSearch Configuration path to the search SQL statement
438
	 * @param string $cfgPathCount Configuration path to the count SQL statement
439
	 * @return \Aimeos\MShop\Product\Item\Iface[] List of product items
440
	 */
441
	protected function searchItemsIndexBase( \Aimeos\Base\Criteria\Iface $search,
442
		array $ref, int &$total = null, string $cfgPathSearch, string $cfgPathCount ) : \Aimeos\Map
443
	{
444
		$list = $ids = [];
445
		$context = $this->context();
446
		$conn = $context->db( $this->getResourceName() );
447
448
		$required = array( 'product' );
449
450
		/** mshop/index/manager/sitemode
451
		 * Mode how items from levels below or above in the site tree are handled
452
		 *
453
		 * By default, only items from the current site are fetched from the
454
		 * storage. If the ai-sites extension is installed, you can create a
455
		 * tree of sites. Then, this setting allows you to define for the
456
		 * whole index domain if items from parent sites are inherited,
457
		 * sites from child sites are aggregated or both.
458
		 *
459
		 * Available constants for the site mode are:
460
		 * * 0 = only items from the current site
461
		 * * 1 = inherit items from parent sites
462
		 * * 2 = aggregate items from child sites
463
		 * * 3 = inherit and aggregate items at the same time
464
		 *
465
		 * You also need to set the mode in the locale manager
466
		 * (mshop/locale/manager/sitelevel) to one of the constants.
467
		 * If you set it to the same value, it will work as described but you
468
		 * can also use different modes. For example, if inheritance and
469
		 * aggregation is configured the locale manager but only inheritance
470
		 * in the domain manager because aggregating items makes no sense in
471
		 * this domain, then items wil be only inherited. Thus, you have full
472
		 * control over inheritance and aggregation in each domain.
473
		 *
474
		 * @param int Constant from Aimeos\MShop\Locale\Manager\Base class
475
		 * @category Developer
476
		 * @since 2018.01
477
		 * @see mshop/locale/manager/sitelevel
478
		 */
479
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
480
		$level = $context->config()->get( 'mshop/index/manager/sitemode', $level );
481
482
		$results = $this->searchItemsBase( $conn, $search, $cfgPathSearch, $cfgPathCount, $required, $total, $level );
483
484
		while( ( $row = $results->fetch() ) !== null ) {
485
			$ids[] = $row['id'];
486
		}
487
488
		$manager = \Aimeos\MShop::create( $context, 'product' );
489
		$prodSearch = $manager->filter();
490
		$prodSearch->setConditions( $prodSearch->compare( '==', 'product.id', $ids ) );
491
		$prodSearch->slice( 0, $search->getLimit() );
492
		$items = $manager->search( $prodSearch, $ref );
493
494
		foreach( $ids as $id )
495
		{
496
			if( isset( $items[$id] ) ) {
497
				$list[$id] = $items[$id];
498
			}
499
		}
500
501
		return map( $list );
502
	}
503
504
505
	/**
506
	 * Returns the sub-manager instances available for the manager
507
	 *
508
	 * @return array Associative list of the sub-domain as key and the manager object as value
509
	 */
510
	abstract protected function getSubManagers() : array;
511
}
512