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

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

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

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

574
		throw new \Aimeos\MShop\Exception( sprintf( $msg, /** @scrutinizer ignore-type */ print_r( $pairs, true ) ) );
Loading history...
575
	}
576
577
578
	/**
579
	 * Returns the cached statement for the given key or creates a new prepared statement.
580
	 * If no SQL string is given, the key is used to retrieve the SQL string from the configuration.
581
	 *
582
	 * @param \Aimeos\MW\DB\Connection\Iface $conn Database connection
583
	 * @param string $cfgkey Unique key for the SQL
584
	 * @param string|null $sql SQL string if it shouldn't be retrieved from the configuration
585
	 * @return \Aimeos\MW\DB\Statement\Iface Database statement object
586
	 */
587
	protected function getCachedStatement( \Aimeos\MW\DB\Connection\Iface $conn, string $cfgkey,
588
		string $sql = null ) : \Aimeos\MW\DB\Statement\Iface
589
	{
590
		if( !isset( $this->stmts['stmt'][$cfgkey] )
591
			|| !isset( $this->stmts['conn'][$cfgkey] )
592
			|| $conn !== $this->stmts['conn'][$cfgkey]
593
		) {
594
			if( $sql === null ) {
595
				$sql = $this->getSqlConfig( $cfgkey );
596
			}
597
598
			$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\MW\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

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