Passed
Push — master ( e78dfc...695473 )
by Aimeos
04:21
created

Base::searchItemsBase()   B

Complexity

Conditions 6
Paths 18

Size

Total Lines 48
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 26
nc 18
nop 8
dl 0
loc 48
rs 8.8817
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2011
6
 * @copyright Aimeos (aimeos.org), 2015-2022
7
 * @package MShop
8
 * @subpackage Common
9
 */
10
11
12
namespace Aimeos\MShop\Common\Manager;
13
14
use \Aimeos\MShop\Locale\Manager\Base as Locale;
15
16
17
/**
18
 * Provides common methods required by most of the manager classes.
19
 *
20
 * @package MShop
21
 * @subpackage Common
22
 */
23
abstract class Base
24
	extends \Aimeos\MW\Common\Manager\Base
25
	implements \Aimeos\Macro\Iface
26
{
27
	use \Aimeos\MShop\Common\Manager\Sub\Traits;
28
	use \Aimeos\Macro\Macroable;
29
	use Site;
30
31
32
	private $context;
33
	private $object;
34
	private $resourceName;
35
	private $stmts = [];
36
	private $search;
37
38
39
	/**
40
	 * Initialization of class.
41
	 *
42
	 * @param \Aimeos\MShop\ContextIface $context Context object
43
	 */
44
	public function __construct( \Aimeos\MShop\ContextIface $context )
45
	{
46
		$this->context = $context;
47
	}
48
49
50
	/**
51
	 * Returns the class names of the manager and used decorators.
52
	 *
53
	 * @return array List of class names
54
	 */
55
	public function classes() : array
56
	{
57
		return [get_class( $this )];
58
	}
59
60
61
	/**
62
	 * Removes old entries from the storage.
63
	 *
64
	 * @param iterable $siteids List of IDs for sites whose entries should be deleted
65
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
66
	 */
67
	public function clear( iterable $siteids ) : \Aimeos\MShop\Common\Manager\Iface
68
	{
69
		return $this;
70
	}
71
72
73
	/**
74
	 * Creates a search critera object
75
	 *
76
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
77
	 * @param bool $site TRUE for adding site criteria to limit items by the site of related items
78
	 * @return \Aimeos\Base\Criteria\Iface New search criteria object
79
	 */
80
	public function filter( ?bool $default = false, bool $site = false ) : \Aimeos\Base\Criteria\Iface
81
	{
82
		$db = $this->getResourceName();
83
		$conn = $this->context->db( $db );
84
		$config = $this->context->config();
85
86
		if( ( $adapter = $config->get( 'resource/' . $db . '/adapter' ) ) === null ) {
87
			$adapter = $config->get( 'resource/db/adapter' );
88
		}
89
90
		switch( $adapter )
91
		{
92
			case 'pgsql':
93
				$search = new \Aimeos\Base\Criteria\PgSQL( $conn ); break;
94
			default:
95
				$search = new \Aimeos\Base\Criteria\SQL( $conn ); break;
96
		}
97
98
		return $search;
99
	}
100
101
102
	/**
103
	 * Adds or updates an item object or a list of them.
104
	 *
105
	 * @param \Aimeos\Map|\Aimeos\MShop\Common\Item\Iface[]|\Aimeos\MShop\Common\Item\Iface $items Item or list of items whose data should be saved
106
	 * @param bool $fetch True if the new ID should be returned in the item
107
	 * @return \Aimeos\Map|\Aimeos\MShop\Common\Item\Iface Saved item or items
108
	 */
109
	public function save( $items, bool $fetch = true )
110
	{
111
		if( is_iterable( $items ) )
112
		{
113
			foreach( $items as $id => $item ) {
114
				$items[$id] = $this->saveItem( $item, $fetch );
0 ignored issues
show
Bug introduced by
The method saveItem() does not exist on Aimeos\MShop\Common\Manager\Base. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

114
				/** @scrutinizer ignore-call */ 
115
    $items[$id] = $this->saveItem( $item, $fetch );
Loading history...
115
			}
116
			return map( $items );
117
		}
118
119
		return $this->saveItem( $items, $fetch );
120
	}
121
122
123
	/**
124
	 * Searches for all items matching the given critera.
125
	 *
126
	 * @param \Aimeos\Base\Criteria\Iface $filter Criteria object with conditions, sortations, etc.
127
	 * @param string[] $ref List of domains to fetch list items and referenced items for
128
	 * @param int &$total Number of items that are available in total
129
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Common\Item\Iface with ids as keys
130
	 */
131
	public function search( \Aimeos\Base\Criteria\Iface $filter, array $ref = [], int &$total = null ) : \Aimeos\Map
132
	{
133
		return map();
134
	}
135
136
137
	/**
138
	 * Starts a database transaction on the connection identified by the given name
139
	 *
140
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
141
	 */
142
	public function begin() : \Aimeos\MShop\Common\Manager\Iface
143
	{
144
		return $this->beginTransation( $this->getResourceName() );
145
	}
146
147
148
	/**
149
	 * Commits the running database transaction on the connection identified by the given name
150
	 *
151
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
152
	 */
153
	public function commit() : \Aimeos\MShop\Common\Manager\Iface
154
	{
155
		return $this->commitTransaction( $this->getResourceName() );
156
	}
157
158
159
	/**
160
	 * Rolls back the running database transaction on the connection identified by the given name
161
	 *
162
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
163
	 */
164
	public function rollback() : \Aimeos\MShop\Common\Manager\Iface
165
	{
166
		return $this->rollbackTransaction( $this->getResourceName() );
167
	}
168
169
170
	/**
171
	 * Returns the additional column/search definitions
172
	 *
173
	 * @return array Associative list of column names as keys and items implementing \Aimeos\Base\Criteria\Attribute\Iface
174
	 */
175
	public function getSaveAttributes() : array
176
	{
177
		return [];
178
	}
179
180
181
	/**
182
	 * Injects the reference of the outmost object
183
	 *
184
	 * @param \Aimeos\MShop\Common\Manager\Iface $object Reference to the outmost manager or decorator
185
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
186
	 */
187
	public function setObject( \Aimeos\MShop\Common\Manager\Iface $object ) : \Aimeos\MShop\Common\Manager\Iface
188
	{
189
		$this->object = $object;
190
		return $this;
191
	}
192
193
194
	/**
195
	 * Adds additional column names to SQL statement
196
	 *
197
	 * @param string[] $columns List of column names
198
	 * @param string $sql Insert or update SQL statement
199
	 * @param bool $mode True for insert, false for update statement
200
	 * @return string Modified insert or update SQL statement
201
	 */
202
	protected function addSqlColumns( array $columns, string $sql, bool $mode = true ) : string
203
	{
204
		$names = $values = '';
205
206
		if( $mode )
207
		{
208
			foreach( $columns as $name ) {
209
				$names .= '"' . $name . '", '; $values .= '?, ';
210
			}
211
		}
212
		else
213
		{
214
			foreach( $columns as $name ) {
215
				$names .= '"' . $name . '" = ?, ';
216
			}
217
		}
218
219
		return str_replace( [':names', ':values'], [$names, $values], $sql );
220
	}
221
222
223
	/**
224
	 * Counts the number products that are available for the values of the given key.
225
	 *
226
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria
227
	 * @param array|string $keys Search key or list of keys for aggregation
228
	 * @param string $cfgPath Configuration key for the SQL statement
229
	 * @param string[] $required List of domain/sub-domain names like "catalog.index" that must be additionally joined
230
	 * @param string|null $value Search key for aggregating the value column
231
	 * @param string|null $type Type of aggregation, e.g.  "sum", "min", "max" or NULL for "count"
232
	 * @return \Aimeos\Map List of ID values as key and the number of counted products as value
233
	 * @todo 2018.01 Reorder Parameter list
234
	 */
235
	protected function aggregateBase( \Aimeos\Base\Criteria\Iface $search, $keys, string $cfgPath,
236
		array $required = [], string $value = null, string $type = null ) : \Aimeos\Map
237
	{
238
		/** mshop/common/manager/aggregate/limit
239
		 * Limits the number of records that are used when aggregating items
240
		 *
241
		 * As counting huge amount of records (several 10 000 records) takes a long time,
242
		 * the limit can cut down response times so the counts are available more quickly
243
		 * in the front-end and the server load is reduced.
244
		 *
245
		 * Using a low limit can lead to incorrect numbers if the amount of found items
246
		 * is very high. Approximate item counts are normally not a problem but it can
247
		 * lead to the situation that visitors see that no items are available despite
248
		 * the fact that there would be at least one.
249
		 *
250
		 * @param integer Number of records
251
		 * @since 2021.04
252
		 */
253
		$limit = $this->context->config()->get( 'mshop/common/manager/aggregate/limit', 10000 );
254
		$keys = (array) $keys;
255
256
		if( !count( $keys ) )
257
		{
258
			$msg = $this->context()->translate( 'mshop', 'At least one key is required for aggregation' );
259
			throw new \Aimeos\MShop\Exception( $msg );
260
		}
261
262
		$conn = $this->context->db( $this->getResourceName() );
263
264
			$total = null;
265
			$cols = $map = [];
266
			$search = clone $search;
267
			$search->slice( $search->getOffset(), min( $search->getLimit(), $limit ) );
268
269
			$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
270
			$attrList = $this->object()->getSearchAttributes();
271
272
			if( $value === null && ( $value = key( $attrList ) ) === null )
273
			{
274
				$msg = $this->context()->translate( 'mshop', 'No search keys available' );
275
				throw new \Aimeos\MShop\Exception( $msg );
276
			}
277
278
			if( ( $pos = strpos( $valkey = $value, '(' ) ) !== false ) {
279
				$value = substr( $value, 0, $pos );
280
			}
281
282
			if( !isset( $attrList[$value] ) )
283
			{
284
				$msg = $this->context()->translate( 'mshop', 'Unknown search key "%1$s"' );
285
				throw new \Aimeos\MShop\Exception( sprintf( $msg, $value ) );
286
			}
287
288
			foreach( $keys as $string )
289
			{
290
				if( !isset( $attrList[$string] ) )
291
				{
292
					$msg = $this->context()->translate( 'mshop', 'Unknown search key "%1$s"' );
293
					throw new \Aimeos\MShop\Exception( sprintf( $msg, $string ) );
294
				}
295
296
				$cols[] = $attrList[$string]->getInternalCode();
297
				$acols[] = $attrList[$string]->getInternalCode() . ' AS "' . $string . '"';
0 ignored issues
show
Bug introduced by
Are you sure $attrList[$string]->getInternalCode() of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

297
				$acols[] = /** @scrutinizer ignore-type */ $attrList[$string]->getInternalCode() . ' AS "' . $string . '"';
Loading history...
298
299
				/** @todo Required to get the joins, but there should be a better way */
300
				$search->add( [$string => null], '!=' );
301
			}
302
			$search->add( [$valkey => null], '!=' );
303
304
			$sql = $this->getSqlConfig( $cfgPath );
305
			$sql = str_replace( ':cols', join( ', ', $cols ), $sql );
306
			$sql = str_replace( ':acols', join( ', ', $acols ), $sql );
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $acols seems to be defined by a foreach iteration on line 288. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
307
			$sql = str_replace( ':keys', '"' . join( '", "', $keys ) . '"', $sql );
308
			$sql = str_replace( ':val', $attrList[$value]->getInternalCode(), $sql );
309
			$sql = str_replace( ':type', $type ?: 'count', $sql );
310
311
			$results = $this->searchItemsBase( $conn, $search, $sql, '', $required, $total, $level );
312
313
			while( ( $row = $results->fetch() ) !== null )
314
			{
315
				$row = $this->transform( $row );
316
317
				$temp = &$map;
318
				$last = array_pop( $row );
319
320
				foreach( $row as $val ) {
321
					$temp[$val] = $temp[$val] ?? [];
322
					$temp = &$temp[$val];
323
				}
324
				$temp = $last;
325
			}
326
327
		return map( $map );
328
	}
329
330
331
	/**
332
	 * Returns the newly created ID for the last record which was inserted.
333
	 *
334
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection used to insert the new record
335
	 * @param string $cfgpath Configuration path to the SQL statement for retrieving the new ID of the last inserted record
336
	 * @return string ID of the last record that was inserted by using the given connection
337
	 * @throws \Aimeos\MShop\Exception if there's no ID of the last record available
338
	 */
339
	protected function newId( \Aimeos\Base\DB\Connection\Iface $conn, string $cfgpath ) : string
340
	{
341
		$result = $conn->create( $this->getSqlConfig( $cfgpath ) )->execute();
0 ignored issues
show
Bug introduced by
It seems like $this->getSqlConfig($cfgpath) can also be of type array; however, parameter $sql of Aimeos\Base\DB\Connection\Iface::create() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

341
		$result = $conn->create( /** @scrutinizer ignore-type */ $this->getSqlConfig( $cfgpath ) )->execute();
Loading history...
342
343
		if( ( $row = $result->fetch( \Aimeos\Base\DB\Result\Base::FETCH_NUM ) ) === false )
344
		{
345
			$msg = $this->context()->translate( 'mshop', 'ID of last inserted database record not available' );
346
			throw new \Aimeos\MShop\Exception( $msg );
347
		}
348
		$result->finish();
349
350
		return $row[0];
351
	}
352
353
354
	/**
355
	 * Removes old entries from the storage.
356
	 *
357
	 * @param iterable $siteids List of IDs for sites whose entries should be deleted
358
	 * @param string $cfgpath Configuration key to the cleanup statement
359
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
360
	 */
361
	protected function clearBase( iterable $siteids, string $cfgpath ) : \Aimeos\MShop\Common\Manager\Iface
362
	{
363
		if( empty( $siteids ) ) {
364
			return $this;
365
		}
366
367
		$conn = $this->context->db( $this->getResourceName() );
368
369
		$sql = $this->getSqlConfig( $cfgpath );
370
		$sql = str_replace( ':cond', '1=1', $sql );
371
372
		$stmt = $conn->create( $sql );
373
374
		foreach( $siteids as $siteid )
375
		{
376
			$stmt->bind( 1, $siteid );
377
			$stmt->execute()->finish();
378
		}
379
380
		return $this;
381
	}
382
383
384
	/**
385
	 * Creates the criteria attribute items from the list of entries
386
	 *
387
	 * @param array $list Associative array of code as key and array with properties as values
388
	 * @return \Aimeos\Base\Criteria\Attribute\Standard[] List of criteria attribute items
389
	 */
390
	protected function createAttributes( array $list ) : array
391
	{
392
		$attr = [];
393
394
		foreach( $list as $key => $fields ) {
395
			$attr[$key] = new \Aimeos\Base\Criteria\Attribute\Standard( $fields );
396
		}
397
398
		return $attr;
399
	}
400
401
402
	/**
403
	 * Sets the base criteria "status".
404
	 * (setConditions overwrites the base criteria)
405
	 *
406
	 * @param string $domain Name of the domain/sub-domain like "product" or "product.list"
407
	 * @param bool|null $default TRUE for status=1, NULL for status>0, FALSE for no restriction
408
	 * @return \Aimeos\Base\Criteria\Iface Search critery object
409
	 */
410
	protected function filterBase( string $domain, ?bool $default = true ) : \Aimeos\Base\Criteria\Iface
411
	{
412
		$filter = self::filter();
0 ignored issues
show
Bug Best Practice introduced by
The method Aimeos\MShop\Common\Manager\Base::filter() is not static, but was called statically. ( Ignorable by Annotation )

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

412
		/** @scrutinizer ignore-call */ 
413
  $filter = self::filter();
Loading history...
413
414
		if( $default !== false ) {
415
			$filter->add( [$domain . '.status' => 1], $default ? '==' : '>=' );
416
		}
417
418
		return $filter;
419
	}
420
421
422
	/**
423
	 * Returns the context object.
424
	 *
425
	 * @return \Aimeos\MShop\ContextIface Context object
426
	 */
427
	protected function context() : \Aimeos\MShop\ContextIface
428
	{
429
		return $this->context;
430
	}
431
432
433
	/**
434
	 * Returns the outmost decorator of the decorator stack
435
	 *
436
	 * @return \Aimeos\MShop\Common\Manager\Iface Outmost decorator object
437
	 */
438
	protected function object() : \Aimeos\MShop\Common\Manager\Iface
439
	{
440
		if( $this->object !== null ) {
441
			return $this->object;
442
		}
443
444
		return $this;
445
	}
446
447
448
	/**
449
	 * Returns the search attribute objects used for searching.
450
	 *
451
	 * @param array $list Associative list of search keys and the lists of search definitions
452
	 * @param string $path Configuration path to the sub-domains for fetching the search definitions
453
	 * @param string[] $default List of sub-domains if no others are configured
454
	 * @param bool $withsub True to include search definitions of sub-domains, false if not
455
	 * @return \Aimeos\Base\Criteria\Attribute\Iface[] Associative list of search keys and criteria attribute items as values
456
	 * @since 2014.09
457
	 */
458
	protected function getSearchAttributesBase( array $list, string $path, array $default, bool $withsub ) : array
459
	{
460
		$attr = $this->createAttributes( $list );
461
462
		if( $withsub === true )
463
		{
464
			$domains = $this->context->config()->get( $path, $default );
465
466
			foreach( $domains as $domain ) {
467
				$attr += $this->object()->getSubManager( $domain )->getSearchAttributes( true );
468
			}
469
		}
470
471
		return $attr;
472
	}
473
474
475
	/**
476
	 * Returns the search results for the given SQL statement.
477
	 *
478
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
479
	 * @param string $sql SQL statement
480
	 * @return \Aimeos\Base\DB\Result\Iface Search result object
481
	 */
482
	protected function getSearchResults( \Aimeos\Base\DB\Connection\Iface $conn, string $sql ) : \Aimeos\Base\DB\Result\Iface
483
	{
484
		$time = microtime( true );
485
486
		$stmt = $conn->create( $sql );
487
		$result = $stmt->execute();
488
489
		$level = \Aimeos\Base\Logger\Iface::DEBUG;
490
		$time = ( microtime( true ) - $time ) * 1000;
491
		$msg = 'Time: ' . $time . "ms\n"
492
			. 'Class: ' . get_class( $this ) . "\n"
493
			. str_replace( ["\t", "\n\n"], ['', "\n"], trim( (string) $stmt ) );
494
495
		if( $time > 1000.0 )
496
		{
497
			$level = \Aimeos\Base\Logger\Iface::NOTICE;
498
			$msg .= "\n" . ( new \Exception() )->getTraceAsString();
499
		}
500
501
		$this->context->logger()->log( $msg, $level, 'core/sql' );
502
503
		return $result;
504
	}
505
506
507
	/**
508
	 * Returns the SQL statement for the given config path
509
	 *
510
	 * If available, the database specific SQL statement is returned, otherwise
511
	 * the ANSI SQL statement. The database type is determined via the resource
512
	 * adapter.
513
	 *
514
	 * @param string $path Configuration path to the SQL statement
515
	 * @return array|string ANSI or database specific SQL statement
516
	 */
517
	protected function getSqlConfig( string $path )
518
	{
519
		$config = $this->context()->config();
520
		$adapter = $config->get( 'resource/' . $this->getResourceName() . '/adapter' );
521
522
		return $config->get( $path . '/' . $adapter, $config->get( $path . '/ansi', $path ) );
523
	}
524
525
526
	/**
527
	 * Returns the item for the given search key/value pairs.
528
	 *
529
	 * @param array $pairs Search key/value pairs for the item
530
	 * @param string[] $ref List of domains whose items should be fetched too
531
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
532
	 * @return \Aimeos\MShop\Common\Item\Iface Requested item
533
	 * @throws \Aimeos\MShop\Exception if no item with the given ID found
534
	 */
535
	protected function findBase( array $pairs, array $ref, ?bool $default ) : \Aimeos\MShop\Common\Item\Iface
536
	{
537
		$expr = [];
538
		$criteria = $this->object()->filter( $default )->slice( 0, 1 );
539
540
		foreach( $pairs as $key => $value )
541
		{
542
			if( $value === null )
543
			{
544
				$msg = $this->context()->translate( 'mshop', 'Required value for "%1$s" is missing' );
545
				throw new \Aimeos\MShop\Exception( $msg );
546
			}
547
			$expr[] = $criteria->compare( '==', $key, $value );
548
		}
549
550
		$criteria->setConditions( $criteria->and( $expr ) );
551
552
		if( ( $item = $this->object()->search( $criteria, $ref )->first() ) ) {
553
			return $item;
554
		}
555
556
		$msg = $this->context()->translate( 'mshop', 'No item found for conditions: %1$s' );
557
		throw new \Aimeos\MShop\Exception( sprintf( $msg, print_r( $pairs, true ) ) );
0 ignored issues
show
Bug introduced by
It seems like print_r($pairs, true) can also be of type true; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

557
		throw new \Aimeos\MShop\Exception( sprintf( $msg, /** @scrutinizer ignore-type */ print_r( $pairs, true ) ) );
Loading history...
558
	}
559
560
561
	/**
562
	 * Returns the cached statement for the given key or creates a new prepared statement.
563
	 * If no SQL string is given, the key is used to retrieve the SQL string from the configuration.
564
	 *
565
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
566
	 * @param string $cfgkey Unique key for the SQL
567
	 * @param string|null $sql SQL string if it shouldn't be retrieved from the configuration
568
	 * @return \Aimeos\Base\DB\Statement\Iface Database statement object
569
	 */
570
	protected function getCachedStatement( \Aimeos\Base\DB\Connection\Iface $conn, string $cfgkey,
571
		string $sql = null ) : \Aimeos\Base\DB\Statement\Iface
572
	{
573
		if( !isset( $this->stmts['stmt'][$cfgkey] )
574
			|| !isset( $this->stmts['conn'][$cfgkey] )
575
			|| $conn !== $this->stmts['conn'][$cfgkey]
576
		) {
577
			if( $sql === null ) {
578
				$sql = $this->getSqlConfig( $cfgkey );
579
			}
580
581
			$this->stmts['stmt'][$cfgkey] = $conn->create( $sql );
0 ignored issues
show
Bug introduced by
It seems like $sql can also be of type array; however, parameter $sql of Aimeos\Base\DB\Connection\Iface::create() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

581
			$this->stmts['stmt'][$cfgkey] = $conn->create( /** @scrutinizer ignore-type */ $sql );
Loading history...
582
			$this->stmts['conn'][$cfgkey] = $conn;
583
		}
584
585
		return $this->stmts['stmt'][$cfgkey];
586
	}
587
588
589
	/**
590
	 * Returns the item for the given search key and ID.
591
	 *
592
	 * @param string $key Search key for the requested ID
593
	 * @param string $id Unique ID to search for
594
	 * @param string[] $ref List of domains whose items should be fetched too
595
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
596
	 * @return \Aimeos\MShop\Common\Item\Iface Requested item
597
	 * @throws \Aimeos\MShop\Exception if no item with the given ID found
598
	 */
599
	protected function getItemBase( string $key, string $id, array $ref, ?bool $default ) : \Aimeos\MShop\Common\Item\Iface
600
	{
601
		$criteria = $this->object()->filter( $default )->slice( 0, 1 );
602
		$expr = [
603
			$criteria->compare( '==', $key, $id ),
604
			$criteria->getConditions()
605
		];
606
		$criteria->setConditions( $criteria->and( $expr ) );
607
608
		if( ( $item = $this->object()->search( $criteria, $ref )->first() ) ) {
609
			return $item;
610
		}
611
612
		$msg = $this->context()->translate( 'mshop', 'Item with ID "%2$s" in "%1$s" not found' );
613
		throw new \Aimeos\MShop\Exception( sprintf( $msg, $key, $id ) );
614
	}
615
616
617
	/**
618
	 * Returns the SQL strings for joining dependent tables.
619
	 *
620
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of criteria attribute items
621
	 * @param string $prefix Search key prefix
622
	 * @return array List of JOIN SQL strings
623
	 */
624
	private function getJoins( array $attributes, string $prefix ) : array
625
	{
626
		$iface = \Aimeos\Base\Criteria\Attribute\Iface::class;
627
		$sep = $this->getKeySeparator();
628
		$name = $prefix . $sep . 'id';
629
630
		if( isset( $attributes[$prefix] ) && $attributes[$prefix] instanceof $iface ) {
631
			return $attributes[$prefix]->getInternalDeps();
632
		}
633
		elseif( isset( $attributes[$name] ) && $attributes[$name] instanceof $iface ) {
634
			return $attributes[$name]->getInternalDeps();
635
		}
636
		else if( isset( $attributes['id'] ) && $attributes['id'] instanceof $iface ) {
637
			return $attributes['id']->getInternalDeps();
638
		}
639
640
		return [];
641
	}
642
643
644
	/**
645
	 * Returns the available manager types
646
	 *
647
	 * @param string $type Main manager type
648
	 * @param string $path Configuration path to the sub-domains
649
	 * @param string[] $default List of sub-domains if no others are configured
650
	 * @param bool $withsub Return also the resource type of sub-managers if true
651
	 * @return string[] Type of the manager and submanagers, subtypes are separated by slashes
652
	 */
653
	protected function getResourceTypeBase( string $type, string $path, array $default, bool $withsub ) : array
654
	{
655
		$list = [$type];
656
657
		if( $withsub )
658
		{
659
			foreach( $this->context->config()->get( $path, $default ) as $domain ) {
660
				$list = array_merge( $list, $this->object()->getSubManager( $domain )->getResourceType( $withsub ) );
661
			}
662
		}
663
664
		return $list;
665
	}
666
667
668
	/**
669
	 * Returns the name of the resource or of the default resource.
670
	 *
671
	 * @return string Name of the resource
672
	 */
673
	protected function getResourceName() : string
674
	{
675
		if( $this->resourceName === null ) {
676
			$this->resourceName = $this->context->config()->get( 'resource/default', 'db' );
677
		}
678
679
		return $this->resourceName;
680
	}
681
682
683
	/**
684
	 * Sets the name of the database resource that should be used.
685
	 *
686
	 * @param string $name Name of the resource
687
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
688
	 */
689
	protected function setResourceName( string $name ) : \Aimeos\MShop\Common\Manager\Iface
690
	{
691
		$config = $this->context->config();
692
693
		if( $config->get( 'resource/' . $name ) === null ) {
694
			$this->resourceName = $config->get( 'resource/default', 'db' );
695
		} else {
696
			$this->resourceName = $name;
697
		}
698
699
		return $this;
700
	}
701
702
703
	/**
704
	 * Returns a search object singleton
705
	 *
706
	 * @return \Aimeos\Base\Criteria\Iface Search object
707
	 */
708
	protected function getSearch() : \Aimeos\Base\Criteria\Iface
709
	{
710
		if( !isset( $this->search ) ) {
711
			$this->search = $this->filter();
712
		}
713
714
		return $this->search;
715
	}
716
717
718
	/**
719
	 * Replaces the given marker with an expression
720
	 *
721
	 * @param string $column Name (including alias) of the column
722
	 * @param mixed $value Value used in the expression
723
	 * @param string $op Operator used in the expression
724
	 * @param int $type Type constant from \Aimeos\Base\DB\Statement\Base class
725
	 * @return string Created expression
726
	 */
727
	protected function toExpression( string $column, $value, string $op = '==',
728
		int $type = \Aimeos\Base\DB\Statement\Base::PARAM_STR ) : string
729
	{
730
		$types = ['marker' => $type];
731
		$translations = ['marker' => $column];
732
		$value = ( is_array( $value ) ? array_unique( $value ) : $value );
733
734
		return $this->getSearch()->compare( $op, 'marker', $value )->toSource( $types, $translations );
735
	}
736
737
738
	/**
739
	 * Returns the site coditions for the search request
740
	 *
741
	 * @param string[] $keys Sorted list of criteria keys
742
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items as values
743
	 * @param int $sitelevel Site level constant from \Aimeos\MShop\Locale\Manager\Base
744
	 * @return \Aimeos\Base\Criteria\Expression\Iface[] List of search conditions
745
	 * @since 2015.01
746
	 */
747
	protected function getSiteConditions( array $keys, array $attributes, int $sitelevel ) : array
748
	{
749
		$list = [];
750
		$sep = $this->getKeySeparator();
751
752
		foreach( $keys as $key )
753
		{
754
			$name = $key . $sep . 'siteid';
755
756
			if( isset( $attributes[$name] ) ) {
757
				$list[] = $this->siteCondition( $name, $sitelevel );
758
			}
759
		}
760
761
		return $list;
762
	}
763
764
765
	/**
766
	 * Returns the string replacements for the SQL statements
767
	 *
768
	 * @param \Aimeos\Base\Criteria\Iface $search Search critera object
769
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items as values
770
	 * @param \Aimeos\Base\Criteria\Plugin\Iface[] $plugins Associative list of search keys and criteria plugin items as values
771
	 * @param string[] $joins Associative list of SQL joins
772
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $columns Additional columns to retrieve values from
773
	 * @return array Array of keys, find and replace arrays
774
	 */
775
	protected function getSQLReplacements( \Aimeos\Base\Criteria\Iface $search, array $attributes, array $plugins,
776
		array $joins, array $columns = [] ) : array
777
	{
778
		$types = $this->getSearchTypes( $attributes );
779
		$funcs = $this->getSearchFunctions( $attributes );
780
		$translations = $this->getSearchTranslations( $attributes );
781
782
		$colstring = '';
783
		foreach( $columns as $name => $entry ) {
784
			$colstring .= $entry->getInternalCode() . ', ';
785
		}
786
787
		$find = array( ':columns', ':joins', ':cond', ':start', ':size' );
788
		$replace = array(
789
			$colstring,
790
			implode( "\n", array_unique( $joins ) ),
791
			$search->getConditionSource( $types, $translations, $plugins, $funcs ),
792
			$search->getOffset(),
793
			$search->getLimit(),
794
		);
795
796
		if( empty( $search->getSortations() ) && ( $attribute = reset( $attributes ) ) !== false ) {
797
			$search = ( clone $search )->setSortations( [$search->sort( '+', $attribute->getCode() )] );
798
		}
799
800
		$find[] = ':order';
801
		$replace[] = $search->getSortationSource( $types, $translations, $funcs );
802
803
		$find[] = ':group';
804
		$replace[] = implode( ', ', $search->translate( $search->getSortations(), $translations, $funcs ) ) . ', ';
805
806
		return [$find, $replace];
807
	}
808
809
810
	/**
811
	 * Returns the search result of the statement combined with the given criteria.
812
	 *
813
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
814
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria object
815
	 * @param string $cfgPathSearch Path to SQL statement in configuration for searching
816
	 * @param string $cfgPathCount Path to SQL statement in configuration for counting
817
	 * @param string[] $required Additional search keys to add conditions for even if no conditions are available
818
	 * @param int|null $total Contains the number of all records matching the criteria if not null
819
	 * @param int $sitelevel Constant from \Aimeos\MShop\Locale\Manager\Base for defining which site IDs should be used for searching
820
	 * @param \Aimeos\Base\Criteria\Plugin\Iface[] $plugins Associative list of search keys and criteria plugin items as values
821
	 * @return \Aimeos\Base\DB\Result\Iface SQL result object for accessing the found records
822
	 * @throws \Aimeos\MShop\Exception if no number of all matching records is available
823
	 */
824
	protected function searchItemsBase( \Aimeos\Base\DB\Connection\Iface $conn, \Aimeos\Base\Criteria\Iface $search,
825
		string $cfgPathSearch, string $cfgPathCount, array $required, int &$total = null,
826
		int $sitelevel = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL, array $plugins = [] ) : \Aimeos\Base\DB\Result\Iface
827
	{
828
		$joins = [];
829
		$conditions = $search->getConditions();
830
		$columns = $this->object()->getSaveAttributes();
831
		$attributes = $this->object()->getSearchAttributes();
832
		$keys = $this->getCriteriaKeyList( $search, $required );
833
834
		$basekey = array_shift( $required );
835
836
		foreach( $keys as $key )
837
		{
838
			if( $key !== $basekey ) {
839
				$joins = array_merge( $joins, $this->getJoins( $attributes, $key ) );
840
			}
841
		}
842
843
		$joins = array_unique( $joins );
844
		$cond = $this->getSiteConditions( $keys, $attributes, $sitelevel );
845
846
		if( $conditions !== null ) {
847
			$cond[] = $conditions;
848
		}
849
850
		$search = clone $search;
851
		$search->setConditions( $search->and( $cond ) );
852
853
		list( $find, $replace ) = $this->getSQLReplacements( $search, $attributes, $plugins, $joins, $columns );
854
855
		if( $total !== null )
856
		{
857
			$sql = str_replace( $find, $replace, $this->getSqlConfig( $cfgPathCount ) );
858
			$result = $this->getSearchResults( $conn, $sql );
859
			$row = $result->fetch();
860
			$result->finish();
861
862
			if( $row === null )
863
			{
864
				$msg = $this->context()->translate( 'mshop', 'Total results value not found' );
865
				throw new \Aimeos\MShop\Exception( $msg );
866
			}
867
868
			$total = (int) $row['count'];
869
		}
870
871
		return $this->getSearchResults( $conn, str_replace( $find, $replace, $this->getSqlConfig( $cfgPathSearch ) ) );
872
	}
873
874
875
	/**
876
	 * Deletes items.
877
	 *
878
	 * @param \Aimeos\MShop\Common\Item\Iface|\Aimeos\Map|array|string $items List of item objects or IDs of the items
879
	 * @param string $cfgpath Configuration path to the SQL statement
880
	 * @param bool $siteid If siteid should be used in the statement
881
	 * @param string $name Name of the ID column
882
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
883
	 */
884
	protected function deleteItemsBase( $items, string $cfgpath, bool $siteid = true,
885
		string $name = 'id' ) : \Aimeos\MShop\Common\Manager\Iface
886
	{
887
		if( map( $items )->isEmpty() ) {
888
			return $this;
889
		}
890
891
		$search = $this->object()->filter();
892
		$search->setConditions( $search->compare( '==', $name, $items ) );
893
894
		$types = array( $name => \Aimeos\Base\DB\Statement\Base::PARAM_STR );
895
		$translations = array( $name => '"' . $name . '"' );
896
897
		$cond = $search->getConditionSource( $types, $translations );
898
		$sql = str_replace( ':cond', $cond, $this->getSqlConfig( $cfgpath ) );
899
900
		$context = $this->context();
901
		$conn = $context->db( $this->getResourceName() );
902
903
		$stmt = $conn->create( $sql );
904
905
		if( $siteid ) {
906
			$stmt->bind( 1, $context->locale()->getSiteId() );
907
		}
908
909
		$stmt->execute()->finish();
910
911
		return $this;
912
	}
913
914
915
	/**
916
	 * Starts a database transaction on the connection identified by the given name.
917
	 *
918
	 * @param string $dbname Name of the database settings in the resource configuration
919
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
920
	 */
921
	protected function beginTransation( string $dbname = 'db' ) : \Aimeos\MShop\Common\Manager\Iface
922
	{
923
		$conn = $this->context->db( $dbname );
924
		$conn->begin();
925
926
		return $this;
927
	}
928
929
930
	/**
931
	 * Commits the running database transaction on the connection identified by the given name.
932
	 *
933
	 * @param string $dbname Name of the database settings in the resource configuration
934
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
935
	 */
936
	protected function commitTransaction( string $dbname = 'db' ) : \Aimeos\MShop\Common\Manager\Iface
937
	{
938
		$conn = $this->context->db( $dbname );
939
		$conn->commit();
940
941
		return $this;
942
	}
943
944
945
	/**
946
	 * Rolls back the running database transaction on the connection identified by the given name.
947
	 *
948
	 * @param string $dbname Name of the database settings in the resource configuration
949
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
950
	 */
951
	protected function rollbackTransaction( string $dbname = 'db' ) : \Aimeos\MShop\Common\Manager\Iface
952
	{
953
		$conn = $this->context->db( $dbname );
954
		$conn->rollback();
955
956
		return $this;
957
	}
958
959
960
	/**
961
	 * Transforms the application specific values to Aimeos standard values.
962
	 *
963
	 * @param array $values Associative list of key/value pairs from the storage
964
	 * @return array Associative list of key/value pairs with standard Aimeos values
965
	 */
966
	protected function transform( array $values ) : array
967
	{
968
		return $values;
969
	}
970
}
971