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

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

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

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

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

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