Passed
Push — master ( 6aab01...0294bc )
by Aimeos
09:44
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
	 * Removes old entries from the storage.
52
	 *
53
	 * @param iterable $siteids List of IDs for sites whose entries should be deleted
54
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
55
	 */
56
	public function clear( iterable $siteids ) : \Aimeos\MShop\Common\Manager\Iface
57
	{
58
		return $this;
59
	}
60
61
62
	/**
63
	 * Creates a search critera object
64
	 *
65
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
66
	 * @param bool $site TRUE for adding site criteria to limit items by the site of related items
67
	 * @return \Aimeos\Base\Criteria\Iface New search criteria object
68
	 */
69
	public function filter( ?bool $default = false, bool $site = false ) : \Aimeos\Base\Criteria\Iface
70
	{
71
		$db = $this->getResourceName();
72
		$conn = $this->context->db( $db );
73
		$config = $this->context->config();
74
75
		if( ( $adapter = $config->get( 'resource/' . $db . '/adapter' ) ) === null ) {
76
			$adapter = $config->get( 'resource/db/adapter' );
77
		}
78
79
		switch( $adapter )
80
		{
81
			case 'pgsql':
82
				$search = new \Aimeos\Base\Criteria\PgSQL( $conn ); break;
83
			default:
84
				$search = new \Aimeos\Base\Criteria\SQL( $conn ); break;
85
		}
86
87
		return $search;
88
	}
89
90
91
	/**
92
	 * Adds or updates an item object or a list of them.
93
	 *
94
	 * @param \Aimeos\Map|\Aimeos\MShop\Common\Item\Iface[]|\Aimeos\MShop\Common\Item\Iface $items Item or list of items whose data should be saved
95
	 * @param bool $fetch True if the new ID should be returned in the item
96
	 * @return \Aimeos\Map|\Aimeos\MShop\Common\Item\Iface Saved item or items
97
	 */
98
	public function save( $items, bool $fetch = true )
99
	{
100
		if( is_iterable( $items ) )
101
		{
102
			foreach( $items as $id => $item ) {
103
				$items[$id] = $this->object()->saveItem( $item, $fetch );
104
			}
105
			return map( $items );
106
		}
107
108
		return $this->object()->saveItem( $items, $fetch );
109
	}
110
111
112
	/**
113
	 * Searches for all items matching the given critera.
114
	 *
115
	 * @param \Aimeos\Base\Criteria\Iface $filter Criteria object with conditions, sortations, etc.
116
	 * @param string[] $ref List of domains to fetch list items and referenced items for
117
	 * @param int &$total Number of items that are available in total
118
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Common\Item\Iface with ids as keys
119
	 */
120
	public function search( \Aimeos\Base\Criteria\Iface $filter, array $ref = [], int &$total = null ) : \Aimeos\Map
121
	{
122
		return $this->object()->search( $filter, $ref, $total );
123
	}
124
125
126
	/**
127
	 * Starts a database transaction on the connection identified by the given name
128
	 *
129
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
130
	 */
131
	public function begin() : \Aimeos\MShop\Common\Manager\Iface
132
	{
133
		return $this->beginTransation( $this->getResourceName() );
134
	}
135
136
137
	/**
138
	 * Commits the running 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 commit() : \Aimeos\MShop\Common\Manager\Iface
143
	{
144
		return $this->commitTransaction( $this->getResourceName() );
145
	}
146
147
148
	/**
149
	 * Rolls back 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 rollback() : \Aimeos\MShop\Common\Manager\Iface
154
	{
155
		return $this->rollbackTransaction( $this->getResourceName() );
156
	}
157
158
159
	/**
160
	 * Returns the additional column/search definitions
161
	 *
162
	 * @return array Associative list of column names as keys and items implementing \Aimeos\Base\Criteria\Attribute\Iface
163
	 */
164
	public function getSaveAttributes() : array
165
	{
166
		return [];
167
	}
168
169
170
	/**
171
	 * Injects the reference of the outmost object
172
	 *
173
	 * @param \Aimeos\MShop\Common\Manager\Iface $object Reference to the outmost manager or decorator
174
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
175
	 */
176
	public function setObject( \Aimeos\MShop\Common\Manager\Iface $object ) : \Aimeos\MShop\Common\Manager\Iface
177
	{
178
		$this->object = $object;
179
		return $this;
180
	}
181
182
183
	/**
184
	 * Adds additional column names to SQL statement
185
	 *
186
	 * @param string[] $columns List of column names
187
	 * @param string $sql Insert or update SQL statement
188
	 * @param bool $mode True for insert, false for update statement
189
	 * @return string Modified insert or update SQL statement
190
	 */
191
	protected function addSqlColumns( array $columns, string $sql, bool $mode = true ) : string
192
	{
193
		$names = $values = '';
194
195
		if( $mode )
196
		{
197
			foreach( $columns as $name ) {
198
				$names .= '"' . $name . '", '; $values .= '?, ';
199
			}
200
		}
201
		else
202
		{
203
			foreach( $columns as $name ) {
204
				$names .= '"' . $name . '" = ?, ';
205
			}
206
		}
207
208
		return str_replace( [':names', ':values'], [$names, $values], $sql );
209
	}
210
211
212
	/**
213
	 * Counts the number products that are available for the values of the given key.
214
	 *
215
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria
216
	 * @param array|string $keys Search key or list of keys for aggregation
217
	 * @param string $cfgPath Configuration key for the SQL statement
218
	 * @param string[] $required List of domain/sub-domain names like "catalog.index" that must be additionally joined
219
	 * @param string|null $value Search key for aggregating the value column
220
	 * @param string|null $type Type of aggregation, e.g.  "sum", "min", "max" or NULL for "count"
221
	 * @return \Aimeos\Map List of ID values as key and the number of counted products as value
222
	 * @todo 2018.01 Reorder Parameter list
223
	 */
224
	protected function aggregateBase( \Aimeos\Base\Criteria\Iface $search, $keys, string $cfgPath,
225
		array $required = [], string $value = null, string $type = null ) : \Aimeos\Map
226
	{
227
		/** mshop/common/manager/aggregate/limit
228
		 * Limits the number of records that are used when aggregating items
229
		 *
230
		 * As counting huge amount of records (several 10 000 records) takes a long time,
231
		 * the limit can cut down response times so the counts are available more quickly
232
		 * in the front-end and the server load is reduced.
233
		 *
234
		 * Using a low limit can lead to incorrect numbers if the amount of found items
235
		 * is very high. Approximate item counts are normally not a problem but it can
236
		 * lead to the situation that visitors see that no items are available despite
237
		 * the fact that there would be at least one.
238
		 *
239
		 * @param integer Number of records
240
		 * @since 2021.04
241
		 */
242
		$limit = $this->context->config()->get( 'mshop/common/manager/aggregate/limit', 10000 );
243
		$keys = (array) $keys;
244
245
		if( !count( $keys ) )
246
		{
247
			$msg = $this->context()->translate( 'mshop', 'At least one key is required for aggregation' );
248
			throw new \Aimeos\MShop\Exception( $msg );
249
		}
250
251
		$conn = $this->context->db( $this->getResourceName() );
252
253
			$total = null;
254
			$cols = $map = [];
255
			$search = clone $search;
256
			$search->slice( $search->getOffset(), min( $search->getLimit(), $limit ) );
257
258
			$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
259
			$attrList = $this->object()->getSearchAttributes();
260
261
			if( $value === null && ( $value = key( $attrList ) ) === null )
262
			{
263
				$msg = $this->context()->translate( 'mshop', 'No search keys available' );
264
				throw new \Aimeos\MShop\Exception( $msg );
265
			}
266
267
			if( ( $pos = strpos( $valkey = $value, '(' ) ) !== false ) {
268
				$value = substr( $value, 0, $pos );
269
			}
270
271
			if( !isset( $attrList[$value] ) )
272
			{
273
				$msg = $this->context()->translate( 'mshop', 'Unknown search key "%1$s"' );
274
				throw new \Aimeos\MShop\Exception( sprintf( $msg, $value ) );
275
			}
276
277
			foreach( $keys as $string )
278
			{
279
				if( !isset( $attrList[$string] ) )
280
				{
281
					$msg = $this->context()->translate( 'mshop', 'Unknown search key "%1$s"' );
282
					throw new \Aimeos\MShop\Exception( sprintf( $msg, $string ) );
283
				}
284
285
				$cols[] = $attrList[$string]->getInternalCode();
286
				$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

286
				$acols[] = /** @scrutinizer ignore-type */ $attrList[$string]->getInternalCode() . ' AS "' . $string . '"';
Loading history...
287
288
				/** @todo Required to get the joins, but there should be a better way */
289
				$search->add( [$string => null], '!=' );
290
			}
291
			$search->add( [$valkey => null], '!=' );
292
293
			$sql = $this->getSqlConfig( $cfgPath );
294
			$sql = str_replace( ':cols', join( ', ', $cols ), $sql );
295
			$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 277. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
296
			$sql = str_replace( ':keys', '"' . join( '", "', $keys ) . '"', $sql );
297
			$sql = str_replace( ':val', $attrList[$value]->getInternalCode(), $sql );
298
			$sql = str_replace( ':type', $type ?: 'count', $sql );
299
300
			$results = $this->searchItemsBase( $conn, $search, $sql, '', $required, $total, $level );
301
302
			while( ( $row = $results->fetch() ) !== null )
303
			{
304
				$row = $this->transform( $row );
305
306
				$temp = &$map;
307
				$last = array_pop( $row );
308
309
				foreach( $row as $val ) {
310
					$temp[$val] = $temp[$val] ?? [];
311
					$temp = &$temp[$val];
312
				}
313
				$temp = $last;
314
			}
315
316
		return map( $map );
317
	}
318
319
320
	/**
321
	 * Returns the newly created ID for the last record which was inserted.
322
	 *
323
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection used to insert the new record
324
	 * @param string $cfgpath Configuration path to the SQL statement for retrieving the new ID of the last inserted record
325
	 * @return string ID of the last record that was inserted by using the given connection
326
	 * @throws \Aimeos\MShop\Exception if there's no ID of the last record available
327
	 */
328
	protected function newId( \Aimeos\Base\DB\Connection\Iface $conn, string $cfgpath ) : string
329
	{
330
		$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

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

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

546
		throw new \Aimeos\MShop\Exception( sprintf( $msg, /** @scrutinizer ignore-type */ print_r( $pairs, true ) ) );
Loading history...
547
	}
548
549
550
	/**
551
	 * Returns the cached statement for the given key or creates a new prepared statement.
552
	 * If no SQL string is given, the key is used to retrieve the SQL string from the configuration.
553
	 *
554
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
555
	 * @param string $cfgkey Unique key for the SQL
556
	 * @param string|null $sql SQL string if it shouldn't be retrieved from the configuration
557
	 * @return \Aimeos\Base\DB\Statement\Iface Database statement object
558
	 */
559
	protected function getCachedStatement( \Aimeos\Base\DB\Connection\Iface $conn, string $cfgkey,
560
		string $sql = null ) : \Aimeos\Base\DB\Statement\Iface
561
	{
562
		if( !isset( $this->stmts['stmt'][$cfgkey] )
563
			|| !isset( $this->stmts['conn'][$cfgkey] )
564
			|| $conn !== $this->stmts['conn'][$cfgkey]
565
		) {
566
			if( $sql === null ) {
567
				$sql = $this->getSqlConfig( $cfgkey );
568
			}
569
570
			$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

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