Passed
Push — master ( baa5ed...e67b42 )
by Aimeos
05:46
created

DB   F

Complexity

Total Complexity 117

Size/Duplication

Total Lines 1169
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 2
Metric Value
wmc 117
eloc 328
c 4
b 0
f 2
dl 0
loc 1169
rs 2

30 Methods

Rating   Name   Duplication   Size   Complexity  
A addSqlColumns() 0 18 4
A filterBase() 0 24 5
A getSubManagers() 0 3 1
A getResourceName() 0 7 2
A transform() 0 3 1
A searchItemsBase() 0 39 4
A getCachedStatement() 0 16 5
A setResourceName() 0 11 2
A getResourceTypeBase() 0 12 3
A deleteItemsBase() 0 28 3
A getSearchResults() 0 22 2
A toExpression() 0 8 2
A getCriteriaKeys() 0 20 5
B aggregateBase() 0 74 10
A getCriteriaKeyList() 0 12 2
B getSQLReplacements() 0 36 8
B getJoins() 0 14 7
A getSearchAttributesBase() 0 14 3
A getSiteConditions() 0 17 4
B saveBase() 0 156 7
A newId() 0 14 2
A getSearch() 0 7 2
A aggregateResult() 0 23 3
A getCriteriaNames() 0 20 5
A getItemBase() 0 10 2
A getRequiredJoins() 0 12 3
A findBase() 0 23 4
A getSqlConfig() 0 26 5
A clearBase() 0 20 3
B cutNameTail() 0 36 8

How to fix   Complexity   

Complex Class

Complex classes like DB often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DB, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2023
6
 * @package MShop
7
 * @subpackage Common
8
 */
9
10
11
namespace Aimeos\MShop\Common\Manager;
12
13
14
/**
15
 * Method trait for managers
16
 *
17
 * @package MShop
18
 * @subpackage Common
19
 */
20
trait DB
21
{
22
	private ?\Aimeos\Base\Criteria\Iface $search;
23
	private ?string $resourceName = null;
24
	private array $cachedStmts = [];
25
26
27
	/**
28
	 * Returns the context object.
29
	 *
30
	 * @return \Aimeos\MShop\ContextIface Context object
31
	 */
32
	abstract protected function context() : \Aimeos\MShop\ContextIface;
33
34
35
	/**
36
	 * Creates the criteria attribute items from the list of entries
37
	 *
38
	 * @param array $list Associative array of code as key and array with properties as values
39
	 * @return \Aimeos\Base\Criteria\Attribute\Standard[] List of criteria attribute items
40
	 */
41
	abstract protected function createAttributes( array $list ) : array;
42
43
44
	/**
45
	 * Returns the full configuration key for the passed last part
46
	 *
47
	 * @param string $name Configuration last part
48
	 * @return string Full configuration key
49
	 */
50
	abstract protected function getConfigKey( string $name ) : string;
51
52
53
	/**
54
	 * Returns the alias of the used table
55
	 *
56
	 * @return string Table alias e.g. "mprolity"
57
	 */
58
	abstract protected function getAlias() : string;
59
60
61
	/**
62
	 * Returns the manager domain
63
	 *
64
	 * @return string Manager domain e.g. "product"
65
	 */
66
	abstract protected function getDomain() : string;
67
68
69
	/**
70
	 * Returns the attribute helper functions for searching defined by the manager.
71
	 *
72
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of search attribute items
73
	 * @return array Associative array of attribute code and helper function
74
	 */
75
	abstract protected function getSearchFunctions( array $attributes ) : array;
76
77
78
	/**
79
	 * Returns the attribute translations for searching defined by the manager.
80
	 *
81
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of search attribute items
82
	 * @return array Associative array of attribute code and internal attribute code
83
	 */
84
	abstract protected function getSearchTranslations( array $attributes ) : array;
85
86
87
	/**
88
	 * Returns the attribute types for searching defined by the manager.
89
	 *
90
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of search attribute items
91
	 * @return array Associative array of attribute code and internal attribute type
92
	 */
93
	abstract protected function getSearchTypes( array $attributes ) : array;
94
95
96
	/**
97
	 * Returns the name of the used table
98
	 *
99
	 * @return string Table name e.g. "mshop_product_lists_type"
100
	 */
101
	abstract protected function getTable() : string;
102
103
104
	/**
105
	 * Returns the outmost decorator of the decorator stack
106
	 *
107
	 * @return \Aimeos\MShop\Common\Manager\Iface Outmost decorator object
108
	 */
109
	abstract protected function object() : \Aimeos\MShop\Common\Manager\Iface;
110
111
112
	/**
113
	 * Returns the site expression for the given name
114
	 *
115
	 * @param string $name Name of the site condition
116
	 * @param int $sitelevel Site level constant from \Aimeos\MShop\Locale\Manager\Base
117
	 * @return \Aimeos\Base\Criteria\Expression\Iface Site search condition
118
	 */
119
	abstract protected function siteCondition( string $name, int $sitelevel ) : \Aimeos\Base\Criteria\Expression\Iface;
120
121
122
	/**
123
	 * Returns the site ID that should be used based on the site level
124
	 *
125
	 * @param string $siteId Site ID to check
126
	 * @param int $sitelevel Site level to check against
127
	 * @return string Site ID that should be use based on the site level
128
	 * @since 2022.04
129
	 */
130
	abstract protected function siteId( string $siteId, int $sitelevel ) : string;
131
132
133
	/**
134
	 * Adds additional column names to SQL statement
135
	 *
136
	 * @param string[] $columns List of column names
137
	 * @param string $sql Insert or update SQL statement
138
	 * @param bool $mode True for insert, false for update statement
139
	 * @return string Modified insert or update SQL statement
140
	 */
141
	protected function addSqlColumns( array $columns, string $sql, bool $mode = true ) : string
142
	{
143
		$names = $values = '';
144
145
		if( $mode )
146
		{
147
			foreach( $columns as $name ) {
148
				$names .= '"' . $name . '", '; $values .= '?, ';
149
			}
150
		}
151
		else
152
		{
153
			foreach( $columns as $name ) {
154
				$names .= '"' . $name . '" = ?, ';
155
			}
156
		}
157
158
		return str_replace( [':names', ':values'], [$names, $values], $sql );
159
	}
160
161
162
	/**
163
	 * Counts the number products that are available for the values of the given key.
164
	 *
165
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria
166
	 * @param array|string $keys Search key or list of keys for aggregation
167
	 * @param string $cfgPath Configuration key for the SQL statement
168
	 * @param string[] $required List of domain/sub-domain names like "catalog.index" that must be additionally joined
169
	 * @param string|null $value Search key for aggregating the value column
170
	 * @param string|null $type Type of aggregation, e.g.  "sum", "min", "max" or NULL for "count"
171
	 * @return \Aimeos\Map List of ID values as key and the number of counted products as value
172
	 * @todo 2018.01 Reorder Parameter list
173
	 */
174
	protected function aggregateBase( \Aimeos\Base\Criteria\Iface $search, $keys, string $cfgPath,
175
		array $required = [], string $value = null, string $type = null ) : \Aimeos\Map
176
	{
177
		/** mshop/common/manager/aggregate/limit
178
		 * Limits the number of records that are used when aggregating items
179
		 *
180
		 * As counting huge amount of records (several 10 000 records) takes a long time,
181
		 * the limit can cut down response times so the counts are available more quickly
182
		 * in the front-end and the server load is reduced.
183
		 *
184
		 * Using a low limit can lead to incorrect numbers if the amount of found items
185
		 * is very high. Approximate item counts are normally not a problem but it can
186
		 * lead to the situation that visitors see that no items are available despite
187
		 * the fact that there would be at least one.
188
		 *
189
		 * @param integer Number of records
190
		 * @since 2021.04
191
		 */
192
		$limit = $this->context()->config()->get( 'mshop/common/manager/aggregate/limit', 10000 );
193
194
		if( empty( $keys ) )
195
		{
196
			$msg = $this->context()->translate( 'mshop', 'At least one key is required for aggregation' );
197
			throw new \Aimeos\MShop\Exception( $msg );
198
		}
199
200
		$attrMap = array_column( array_filter( $this->object()->getSearchAttributes(), function( $item ) {
201
			return $item->isPublic() || strncmp( $item->getCode(), 'agg:', 4 ) === 0;
202
		} ), null, 'code' );
203
204
		if( $value === null && ( $value = key( $attrMap ) ) === null )
205
		{
206
			$msg = $this->context()->translate( 'mshop', 'No search keys available' );
207
			throw new \Aimeos\MShop\Exception( $msg );
208
		}
209
210
		if( ( $pos = strpos( $valkey = $value, '(' ) ) !== false ) {
211
			$value = substr( $value, 0, $pos ) . '()'; // remove parameters from search function
212
		}
213
214
		if( !isset( $attrMap[$value] ) )
215
		{
216
			$msg = $this->context()->translate( 'mshop', 'Unknown search key "%1$s"' );
217
			throw new \Aimeos\MShop\Exception( sprintf( $msg, $value ) );
218
		}
219
220
		$keys = (array) $keys;
221
		$acols = $cols = $expr = [];
222
		$search = (clone $search)->slice( $search->getOffset(), min( $search->getLimit(), $limit ) );
223
224
		foreach( $keys as $string )
225
		{
226
			if( ( $attrItem = $attrMap[$string] ?? null ) === null )
227
			{
228
				$msg = $this->context()->translate( 'mshop', 'Unknown search key "%1$s"' );
229
				throw new \Aimeos\MShop\Exception( sprintf( $msg, $string ) );
230
			}
231
232
			$cols[] = $attrItem->getInternalCode();
233
			$acols[] = $attrItem->getInternalCode() . ' AS "' . $string . '"';
234
235
			$expr[] = $search->compare( '!=', $string, null ); // required for the joins
236
		}
237
238
		$search->add( $search->and( $expr ) )->add( $valkey, '!=', null );
239
240
		$sql = $this->getSqlConfig( $cfgPath );
241
		$sql = str_replace( ':cols', join( ', ', $cols ), $sql );
242
		$sql = str_replace( ':acols', join( ', ', $acols ), $sql );
243
		$sql = str_replace( ':keys', '"' . join( '", "', $keys ) . '"', $sql );
244
		$sql = str_replace( ':val', $attrMap[$value]->getInternalCode(), $sql );
245
		$sql = str_replace( ':type', in_array( $type, ['avg', 'count', 'max', 'min', 'sum'] ) ? $type : 'count', $sql );
246
247
		return $this->aggregateResult( $search, $sql, $required );
248
	}
249
250
251
	/**
252
	 * Returns the aggregated values for the given SQL string and filter.
253
	 *
254
	 * @param \Aimeos\Base\Criteria\Iface $filter Filter object
255
	 * @param string $sql SQL statement
256
	 * @param string[] $required List of domain/sub-domain names like "catalog.index" that must be additionally joined
257
	 * @return \Aimeos\Map (Nested) list of aggregated values as key and the number of counted products as value
258
	 */
259
	protected function aggregateResult( \Aimeos\Base\Criteria\Iface $filter, string $sql, array $required ) : \Aimeos\Map
260
	{
261
		$map = [];
262
		$total = null;
263
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
264
		$conn = $this->context()->db( $this->getResourceName() );
265
		$results = $this->searchItemsBase( $conn, $filter, $sql, '', $required, $total, $level );
266
267
		while( $row = $results->fetch() )
268
		{
269
			$row = $this->transform( $row );
270
271
			$temp = &$map;
272
			$last = array_pop( $row );
273
274
			foreach( $row as $val ) {
275
				$temp[$val] = $temp[$val] ?? [];
276
				$temp = &$temp[$val];
277
			}
278
			$temp = $last;
279
		}
280
281
		return map( $map );
282
	}
283
284
285
	/**
286
	 * Removes old entries from the storage.
287
	 *
288
	 * @param iterable $siteids List of IDs for sites whose entries should be deleted
289
	 * @param string $cfgpath Configuration key to the cleanup statement
290
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
291
	 */
292
	protected function clearBase( iterable $siteids, string $cfgpath ) : \Aimeos\MShop\Common\Manager\Iface
293
	{
294
		if( empty( $siteids ) ) {
295
			return $this;
296
		}
297
298
		$conn = $this->context()->db( $this->getResourceName() );
299
300
		$sql = $this->getSqlConfig( $cfgpath );
301
		$sql = str_replace( ':cond', '1=1', $sql );
302
303
		$stmt = $conn->create( $sql );
304
305
		foreach( $siteids as $siteid )
306
		{
307
			$stmt->bind( 1, $siteid );
308
			$stmt->execute()->finish();
309
		}
310
311
		return $this;
312
	}
313
314
315
	/**
316
	 * Deletes items.
317
	 *
318
	 * @param \Aimeos\MShop\Common\Item\Iface|\Aimeos\Map|array|string $items List of item objects or IDs of the items
319
	 * @param string $cfgpath Configuration path to the SQL statement
320
	 * @param bool $siteid If siteid should be used in the statement
321
	 * @param string $name Name of the ID column
322
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
323
	 */
324
	protected function deleteItemsBase( $items, string $cfgpath, bool $siteid = true,
325
		string $name = 'id' ) : \Aimeos\MShop\Common\Manager\Iface
326
	{
327
		if( map( $items )->isEmpty() ) {
328
			return $this;
329
		}
330
331
		$search = $this->object()->filter();
332
		$search->setConditions( $search->compare( '==', $name, $items ) );
333
334
		$types = array( $name => \Aimeos\Base\DB\Statement\Base::PARAM_STR );
335
		$translations = array( $name => '"' . $name . '"' );
336
337
		$cond = $search->getConditionSource( $types, $translations );
338
		$sql = str_replace( ':cond', $cond, $this->getSqlConfig( $cfgpath ) );
339
340
		$context = $this->context();
341
		$conn = $context->db( $this->getResourceName() );
342
343
		$stmt = $conn->create( $sql );
344
345
		if( $siteid ) {
346
			$stmt->bind( 1, $context->locale()->getSiteId() . '%' );
347
		}
348
349
		$stmt->execute()->finish();
350
351
		return $this;
352
	}
353
354
355
	/**
356
	 * Returns a sorted list of required criteria keys.
357
	 *
358
	 * @param \Aimeos\Base\Criteria\Iface $criteria Search criteria object
359
	 * @param string[] $required List of prefixes of required search conditions
360
	 * @return string[] Sorted list of criteria keys
361
	 */
362
	protected function getCriteriaKeyList( \Aimeos\Base\Criteria\Iface $criteria, array $required ) : array
363
	{
364
		$keys = array_merge( $required, $this->getCriteriaKeys( $required, $criteria->getConditions() ) );
365
366
		foreach( $criteria->getSortations() as $sortation ) {
367
			$keys = array_merge( $keys, $this->getCriteriaKeys( $required, $sortation ) );
368
		}
369
370
		$keys = array_unique( array_merge( $required, $keys ) );
371
		sort( $keys );
372
373
		return $keys;
374
	}
375
376
377
	/**
378
	 * Returns the name of the resource.
379
	 *
380
	 * @return string Name of the resource, e.g. "db-product"
381
	 */
382
	protected function getResourceName() : string
383
	{
384
		if( $this->resourceName === null ) {
385
			$this->setResourceName( 'db-' . $this->getDomain() );
386
		}
387
388
		return $this->resourceName;
389
	}
390
391
392
	/**
393
	 * Sets the name of the database resource that should be used.
394
	 *
395
	 * @param string $name Name of the resource
396
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
397
	 */
398
	protected function setResourceName( string $name ) : \Aimeos\MShop\Common\Manager\Iface
399
	{
400
		$config = $this->context()->config();
401
402
		if( $config->get( 'resource/' . $name ) === null ) {
403
			$this->resourceName = $config->get( 'resource/default', 'db' );
404
		} else {
405
			$this->resourceName = $name;
406
		}
407
408
		return $this;
409
	}
410
411
412
	/**
413
	 * Returns the search attribute objects used for searching.
414
	 *
415
	 * @param array $list Associative list of search keys and the lists of search definitions
416
	 * @param string $path Configuration path to the sub-domains for fetching the search definitions
417
	 * @param string[] $default List of sub-domains if no others are configured
418
	 * @param bool $withsub True to include search definitions of sub-domains, false if not
419
	 * @return \Aimeos\Base\Criteria\Attribute\Iface[] Associative list of search keys and criteria attribute items as values
420
	 * @since 2014.09
421
	 */
422
	protected function getSearchAttributesBase( array $list, string $path, array $default, bool $withsub ) : array
423
	{
424
		$attr = $this->createAttributes( $list );
425
426
		if( $withsub === true )
427
		{
428
			$domains = $this->context()->config()->get( $path, $default );
429
430
			foreach( $domains as $domain ) {
431
				$attr += $this->object()->getSubManager( $domain )->getSearchAttributes( true );
432
			}
433
		}
434
435
		return $attr;
436
	}
437
438
439
	/**
440
	 * Returns the search results for the given SQL statement.
441
	 *
442
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
443
	 * @param string $sql SQL statement
444
	 * @return \Aimeos\Base\DB\Result\Iface Search result object
445
	 */
446
	protected function getSearchResults( \Aimeos\Base\DB\Connection\Iface $conn, string $sql ) : \Aimeos\Base\DB\Result\Iface
447
	{
448
		$time = microtime( true );
449
450
		$stmt = $conn->create( $sql );
451
		$result = $stmt->execute();
452
453
		$level = \Aimeos\Base\Logger\Iface::DEBUG;
454
		$time = ( microtime( true ) - $time ) * 1000;
455
		$msg = 'Time: ' . $time . "ms\n"
456
			. 'Class: ' . get_class( $this ) . "\n"
457
			. str_replace( ["\t", "\n\n"], ['', "\n"], trim( (string) $stmt ) );
458
459
		if( $time > 1000.0 )
460
		{
461
			$level = \Aimeos\Base\Logger\Iface::NOTICE;
462
			$msg .= "\n" . ( new \Exception() )->getTraceAsString();
463
		}
464
465
		$this->context()->logger()->log( $msg, $level, 'core/sql' );
466
467
		return $result;
468
	}
469
470
471
	/**
472
	 * Returns the site coditions for the search request
473
	 *
474
	 * @param string[] $keys Sorted list of criteria keys
475
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items as values
476
	 * @param int $sitelevel Site level constant from \Aimeos\MShop\Locale\Manager\Base
477
	 * @return \Aimeos\Base\Criteria\Expression\Iface[] List of search conditions
478
	 * @since 2015.01
479
	 */
480
	protected function getSiteConditions( array $keys, array $attributes, int $sitelevel ) : array
481
	{
482
		$list = [];
483
		$entries = array_column( $attributes, null, 'code' );
484
485
		foreach( $keys as $key )
486
		{
487
			$name = $key . '.siteid';
488
489
			if( isset( $entries[$name] ) ) {
490
				$list[] = $this->siteCondition( $name, $sitelevel );
491
			} elseif( isset( $entries['siteid'] ) ) {
492
				$list[] = $this->siteCondition( 'siteid', $sitelevel );
493
			}
494
		}
495
496
		return $list;
497
	}
498
499
500
	/**
501
	 * Returns the SQL statement for the given config path
502
	 *
503
	 * If available, the database specific SQL statement is returned, otherwise
504
	 * the ANSI SQL statement. The database type is determined via the resource
505
	 * adapter.
506
	 *
507
	 * @param string $path Configuration path to the SQL statement
508
	 * @param array $replace Associative list of keys with strings to replace by their values
509
	 * @return array|string ANSI or database specific SQL statement
510
	 */
511
	protected function getSqlConfig( string $path, array $replace = [] )
512
	{
513
		if( preg_match( '#^[a-z0-9\-]+(/[a-z0-9\-]+)*$#', $path ) !== 1 )
514
		{
515
			foreach( $replace as $key => $value ) {
516
				$path = str_replace( $key, $value, $path );
517
			}
518
519
			return $path;
520
		}
521
522
		$config = $this->context()->config();
523
		$adapter = $config->get( 'resource/' . $this->getResourceName() . '/adapter' );
524
525
		if( ( $sql = $config->get( $path . '/' . $adapter, $config->get( $path . '/ansi' ) ) ) === null )
526
		{
527
			$parts = explode( '/', $path );
528
			$cpath = 'mshop/common/manager/' . end( $parts );
529
			$sql = $config->get( $cpath . '/' . $adapter, $config->get( $cpath . '/ansi', $path ) );
530
		}
531
532
		foreach( $replace as $key => $value ) {
533
			$sql = str_replace( $key, $value, $sql );
534
		}
535
536
		return str_replace( [':alias', ':table'], [$this->getAlias(), $this->getTable()], $sql );
537
	}
538
539
540
	/**
541
	 * Returns the available sub-manager names
542
	 *
543
	 * @return array Sub-manager names, e.g. ['lists', 'property', 'type']
544
	 */
545
	protected function getSubManagers() : array
546
	{
547
		return $this->context()->config()->get( $this->getConfigKey( 'submanagers' ), [] );
548
	}
549
550
551
	/**
552
	 * Sets the base criteria "status".
553
	 * (setConditions overwrites the base criteria)
554
	 *
555
	 * @param string $domain Name of the domain/sub-domain like "product" or "product.list"
556
	 * @param bool|null $default TRUE for status=1, NULL for status>0, FALSE for no restriction
557
	 * @return \Aimeos\Base\Criteria\Iface Search critery object
558
	 */
559
	protected function filterBase( string $domain, ?bool $default = false ) : \Aimeos\Base\Criteria\Iface
560
	{
561
		$context = $this->context();
562
		$db = $this->getResourceName();
563
		$conn = $context->db( $db );
564
		$config = $context->config();
565
566
		if( ( $adapter = $config->get( 'resource/' . $db . '/adapter' ) ) === null ) {
567
			$adapter = $config->get( 'resource/db/adapter' );
568
		}
569
570
		switch( $adapter )
571
		{
572
			case 'pgsql':
573
				$filter = new \Aimeos\Base\Criteria\PgSQL( $conn ); break;
574
			default:
575
				$filter = new \Aimeos\Base\Criteria\SQL( $conn ); break;
576
		}
577
578
		if( $default !== false ) {
579
			$filter->add( $domain . '.status', $default ? '==' : '>=', 1 );
580
		}
581
582
		return $filter;
583
	}
584
585
586
	/**
587
	 * Returns the item for the given search key/value pairs.
588
	 *
589
	 * @param array $pairs Search key/value pairs for the item
590
	 * @param string[] $ref List of domains whose items should be fetched too
591
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
592
	 * @return \Aimeos\MShop\Common\Item\Iface Requested item
593
	 * @throws \Aimeos\MShop\Exception if no item with the given ID found
594
	 */
595
	protected function findBase( array $pairs, array $ref, ?bool $default ) : \Aimeos\MShop\Common\Item\Iface
596
	{
597
		$expr = [];
598
		$criteria = $this->object()->filter( $default )->slice( 0, 1 );
599
600
		foreach( $pairs as $key => $value )
601
		{
602
			if( $value === null )
603
			{
604
				$msg = $this->context()->translate( 'mshop', 'Required value for "%1$s" is missing' );
605
				throw new \Aimeos\MShop\Exception( sprintf( $msg, $key ) );
606
			}
607
			$expr[] = $criteria->compare( '==', $key, $value );
608
		}
609
610
		$criteria->setConditions( $criteria->and( $expr ) );
611
612
		if( ( $item = $this->object()->search( $criteria, $ref )->first() ) ) {
613
			return $item;
614
		}
615
616
		$msg = $this->context()->translate( 'mshop', 'No item found for conditions: %1$s' );
617
		throw new \Aimeos\MShop\Exception( sprintf( $msg, print_r( $pairs, true ) ), 404 );
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

617
		throw new \Aimeos\MShop\Exception( sprintf( $msg, /** @scrutinizer ignore-type */ print_r( $pairs, true ) ), 404 );
Loading history...
618
	}
619
620
621
	/**
622
	 * Returns the cached statement for the given key or creates a new prepared statement.
623
	 * If no SQL string is given, the key is used to retrieve the SQL string from the configuration.
624
	 *
625
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
626
	 * @param string $cfgkey Unique key for the SQL
627
	 * @param string|null $sql SQL string if it shouldn't be retrieved from the configuration
628
	 * @return \Aimeos\Base\DB\Statement\Iface Database statement object
629
	 */
630
	protected function getCachedStatement( \Aimeos\Base\DB\Connection\Iface $conn, string $cfgkey,
631
		string $sql = null ) : \Aimeos\Base\DB\Statement\Iface
632
	{
633
		if( !isset( $this->cachedStmts['stmt'][$cfgkey] )
634
			|| !isset( $this->cachedStmts['conn'][$cfgkey] )
635
			|| $conn !== $this->cachedStmts['conn'][$cfgkey]
636
		) {
637
			if( $sql === null ) {
638
				$sql = $this->getSqlConfig( $cfgkey );
639
			}
640
641
			$this->cachedStmts['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

641
			$this->cachedStmts['stmt'][$cfgkey] = $conn->create( /** @scrutinizer ignore-type */ $sql );
Loading history...
642
			$this->cachedStmts['conn'][$cfgkey] = $conn;
643
		}
644
645
		return $this->cachedStmts['stmt'][$cfgkey];
646
	}
647
648
649
	/**
650
	 * Returns the item for the given search key and ID.
651
	 *
652
	 * @param string $key Search key for the requested ID
653
	 * @param string $id Unique ID to search for
654
	 * @param string[] $ref List of domains whose items should be fetched too
655
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
656
	 * @return \Aimeos\MShop\Common\Item\Iface Requested item
657
	 * @throws \Aimeos\MShop\Exception if no item with the given ID found
658
	 */
659
	protected function getItemBase( string $key, string $id, array $ref, ?bool $default ) : \Aimeos\MShop\Common\Item\Iface
660
	{
661
		$criteria = $this->object()->filter( $default )->add( [$key => $id] )->slice( 0, 1 );
662
663
		if( ( $item = $this->object()->search( $criteria, $ref )->first() ) ) {
664
			return $item;
665
		}
666
667
		$msg = $this->context()->translate( 'mshop', 'Item with ID "%2$s" in "%1$s" not found' );
668
		throw new \Aimeos\MShop\Exception( sprintf( $msg, $key, $id ), 404 );
669
	}
670
671
672
	/**
673
	 * Returns the SQL strings for joining dependent tables.
674
	 *
675
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of criteria attribute items
676
	 * @param string $prefix Search key prefix
677
	 * @return array List of JOIN SQL strings
678
	 */
679
	private function getJoins( array $attributes, string $prefix ) : array
680
	{
681
		$iface = \Aimeos\Base\Criteria\Attribute\Iface::class;
682
		$name = $prefix . '.id';
683
684
		if( isset( $attributes[$prefix] ) && $attributes[$prefix] instanceof $iface ) {
685
			return $attributes[$prefix]->getInternalDeps();
686
		} elseif( isset( $attributes[$name] ) && $attributes[$name] instanceof $iface ) {
687
			return $attributes[$name]->getInternalDeps();
688
		} elseif( isset( $attributes['id'] ) && $attributes['id'] instanceof $iface ) {
689
			return $attributes['id']->getInternalDeps();
690
		}
691
692
		return [];
693
	}
694
695
696
	/**
697
	 * Returns the required SQL joins for the critera.
698
	 *
699
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of criteria attribute items
700
	 * @param string $prefix Search key prefix
701
	 * @return array|null List of JOIN SQL strings
702
	 */
703
	private function getRequiredJoins( array $attributes, array $keys, string $basekey = null ) : array
704
	{
705
		$joins = [];
706
707
		foreach( $keys as $key )
708
		{
709
			if( $key !== $basekey ) {
710
				$joins = array_merge( $joins, $this->getJoins( $attributes, $key ) );
711
			}
712
		}
713
714
		return array_unique( $joins );
715
	}
716
717
718
	/**
719
	 * Returns the available manager types
720
	 *
721
	 * @param string $type Main manager type
722
	 * @param string $path Configuration path to the sub-domains
723
	 * @param string[] $default List of sub-domains if no others are configured
724
	 * @param bool $withsub Return also the resource type of sub-managers if true
725
	 * @return string[] Type of the manager and submanagers, subtypes are separated by slashes
726
	 */
727
	protected function getResourceTypeBase( string $type, string $path, array $default, bool $withsub ) : array
728
	{
729
		$list = [$type];
730
731
		if( $withsub )
732
		{
733
			foreach( $this->context()->config()->get( $path, $default ) as $domain ) {
734
				$list = array_merge( $list, $this->object()->getSubManager( $domain )->getResourceType( $withsub ) );
735
			}
736
		}
737
738
		return $list;
739
	}
740
741
742
	/**
743
	 * Returns a search object singleton
744
	 *
745
	 * @return \Aimeos\Base\Criteria\Iface Search object
746
	 */
747
	protected function getSearch() : \Aimeos\Base\Criteria\Iface
748
	{
749
		if( !isset( $this->search ) ) {
750
			$this->search = $this->filter();
0 ignored issues
show
Bug introduced by
The method filter() does not exist on Aimeos\MShop\Common\Manager\DB. Did you maybe mean filterBase()? ( Ignorable by Annotation )

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

750
			/** @scrutinizer ignore-call */ 
751
   $this->search = $this->filter();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
751
		}
752
753
		return $this->search;
754
	}
755
756
757
	/**
758
	 * Returns the string replacements for the SQL statements
759
	 *
760
	 * @param \Aimeos\Base\Criteria\Iface $search Search critera object
761
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items as values
762
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items as values for the base table
763
	 * @param \Aimeos\Base\Criteria\Plugin\Iface[] $plugins Associative list of search keys and criteria plugin items as values
764
	 * @param string[] $joins Associative list of SQL joins
765
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $columns Additional columns to retrieve values from
766
	 * @return array Array of keys, find and replace arrays
767
	 */
768
	protected function getSQLReplacements( \Aimeos\Base\Criteria\Iface $search, array $attributes, array $attronly, array $plugins, array $joins ) : array
769
	{
770
		$types = $this->getSearchTypes( $attributes );
771
		$funcs = $this->getSearchFunctions( $attributes );
772
		$translations = $this->getSearchTranslations( $attributes );
773
774
		if( empty( $search->getSortations() ) && ( $attribute = reset( $attronly ) ) !== false ) {
775
			$search = ( clone $search )->setSortations( [$search->sort( '+', $attribute->getCode() )] );
776
		}
777
		$sorts = $search->translate( $search->getSortations(), $translations, $funcs );
778
779
		$cols = $group = [];
780
		foreach( $attronly as $name => $entry )
781
		{
782
			if( str_contains( $name, ':' ) || empty( $entry->getInternalCode() ) ) {
783
				continue;
784
			}
785
786
			$icode = $entry->getInternalCode();
787
788
			if( !( str_contains( $icode, '"' ) || str_contains( $icode, '.' ) ) ) {
789
				$icode = '"' . $icode . '"';
790
			}
791
792
			$cols[] = $icode . ' AS "' . $entry->getCode() . '"';
793
			$group[] = $icode;
794
		}
795
796
		return [
797
			':columns' => join( ', ', $cols ),
798
			':joins' => join( "\n", array_unique( $joins ) ),
799
			':group' => join( ', ', array_unique( array_merge( $group, $sorts ) ) ),
800
			':cond' => $search->getConditionSource( $types, $translations, $plugins, $funcs ),
801
			':order' => $search->getSortationSource( $types, $translations, $funcs ),
802
			':start' => $search->getOffset(),
803
			':size' => $search->getLimit(),
804
		];
805
	}
806
807
808
	/**
809
	 * Returns the newly created ID for the last record which was inserted.
810
	 *
811
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection used to insert the new record
812
	 * @param string $cfgpath Configuration path to the SQL statement for retrieving the new ID of the last inserted record
813
	 * @return string ID of the last record that was inserted by using the given connection
814
	 * @throws \Aimeos\MShop\Exception if there's no ID of the last record available
815
	 */
816
	protected function newId( \Aimeos\Base\DB\Connection\Iface $conn, string $cfgpath ) : string
817
	{
818
		$sql = $this->getSqlConfig( $cfgpath );
819
820
		$result = $conn->create( $sql )->execute();
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

820
		$result = $conn->create( /** @scrutinizer ignore-type */ $sql )->execute();
Loading history...
821
822
		if( ( $row = $result->fetch( \Aimeos\Base\DB\Result\Base::FETCH_NUM ) ) === false )
823
		{
824
			$msg = $this->context()->translate( 'mshop', 'ID of last inserted database record not available' );
825
			throw new \Aimeos\MShop\Exception( $msg );
826
		}
827
		$result->finish();
828
829
		return $row[0];
830
	}
831
832
833
	/**
834
	 * Returns the search result of the statement combined with the given criteria.
835
	 *
836
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
837
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria object
838
	 * @param string $cfgPathSearch Path to SQL statement in configuration for searching
839
	 * @param string $cfgPathCount Path to SQL statement in configuration for counting
840
	 * @param string[] $required Additional search keys to add conditions for even if no conditions are available
841
	 * @param int|null $total Contains the number of all records matching the criteria if not null
842
	 * @param int $sitelevel Constant from \Aimeos\MShop\Locale\Manager\Base for defining which site IDs should be used for searching
843
	 * @param \Aimeos\Base\Criteria\Plugin\Iface[] $plugins Associative list of search keys and criteria plugin items as values
844
	 * @return \Aimeos\Base\DB\Result\Iface SQL result object for accessing the found records
845
	 * @throws \Aimeos\MShop\Exception if no number of all matching records is available
846
	 */
847
	protected function searchItemsBase( \Aimeos\Base\DB\Connection\Iface $conn, \Aimeos\Base\Criteria\Iface $search,
848
		string $cfgPathSearch, string $cfgPathCount, array $required, int &$total = null,
849
		int $sitelevel = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL, array $plugins = [] ) : \Aimeos\Base\DB\Result\Iface
850
	{
851
		$conditions = $search->getConditions();
852
		$attributes = $this->object()->getSearchAttributes();
853
854
		$keys = $this->getCriteriaKeyList( $search, $required );
855
		$joins = $this->getRequiredJoins( $attributes, $keys, array_shift( $required ) );
856
857
		$attronly = $this->object()->getSearchAttributes( false );
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
		$replace = $this->getSQLReplacements( $search, $attributes, $attronly, $plugins, $joins );
868
869
		if( $total !== null )
870
		{
871
			$sql = $this->getSqlConfig( $cfgPathCount, $replace );
872
			$result = $this->getSearchResults( $conn, $sql );
0 ignored issues
show
Bug introduced by
It seems like $sql can also be of type array; however, parameter $sql of Aimeos\MShop\Common\Manager\DB::getSearchResults() 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

872
			$result = $this->getSearchResults( $conn, /** @scrutinizer ignore-type */ $sql );
Loading history...
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, $this->getSqlConfig( $cfgPathSearch, $replace ) );
886
	}
887
888
889
	/**
890
	 * Saves an attribute item to the storage.
891
	 *
892
	 * @param \Aimeos\MShop\Common\Item\Iface $item Item object
893
	 * @param bool $fetch True if the new ID should be returned in the item
894
	 * @return \Aimeos\MShop\Common\Item\Iface $item Updated item including the generated ID
895
	 */
896
	protected function saveBase( \Aimeos\MShop\Common\Item\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Common\Item\Iface
897
	{
898
		if( !$item->isModified() ) {
899
			return $item;
900
		}
901
902
		$context = $this->context();
903
		$conn = $context->db( $this->getResourceName() );
904
905
		$id = $item->getId();
906
		$columns = $this->object()->getSaveAttributes();
907
908
		if( $id === null )
909
		{
910
			/** mshop/common/manager/insert/mysql
911
			 * Inserts a new record into the database table
912
			 *
913
			 * @see mshop/common/manager/insert/ansi
914
			 */
915
916
			/** mshop/common/manager/insert/ansi
917
			 * Inserts a new record into the database table
918
			 *
919
			 * Items with no ID yet (i.e. the ID is NULL) will be created in
920
			 * the database and the newly created ID retrieved afterwards
921
			 * using the "newid" SQL statement.
922
			 *
923
			 * The SQL statement must be a string suitable for being used as
924
			 * prepared statement. It must include question marks for binding
925
			 * the values from the item to the statement before they are
926
			 * sent to the database server. The number of question marks must
927
			 * be the same as the number of columns listed in the INSERT
928
			 * statement. The order of the columns must correspond to the
929
			 * order in the save() method, so the correct values are
930
			 * bound to the columns.
931
			 *
932
			 * The SQL statement should conform to the ANSI standard to be
933
			 * compatible with most relational database systems. This also
934
			 * includes using double quotes for table and column names.
935
			 *
936
			 * @param string SQL statement for inserting records
937
			 * @since 2023.10
938
			 * @category Developer
939
			 * @see mshop/common/manager/update/ansi
940
			 * @see mshop/common/manager/newid/ansi
941
			 * @see mshop/common/manager/delete/ansi
942
			 * @see mshop/common/manager/search/ansi
943
			 * @see mshop/common/manager/count/ansi
944
			 */
945
			$path = $this->getConfigKey( 'insert' );
946
			$sql = $this->addSqlColumns( array_keys( $columns ), $this->getSqlConfig( $path ) );
0 ignored issues
show
Bug introduced by
It seems like $this->getSqlConfig($path) can also be of type array; however, parameter $sql of Aimeos\MShop\Common\Manager\DB::addSqlColumns() 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

946
			$sql = $this->addSqlColumns( array_keys( $columns ), /** @scrutinizer ignore-type */ $this->getSqlConfig( $path ) );
Loading history...
947
		}
948
		else
949
		{
950
			/** mshop/common/manager/update/mysql
951
			 * Updates an existing record in the database
952
			 *
953
			 * @see mshop/common/manager/update/ansi
954
			 */
955
956
			/** mshop/common/manager/update/ansi
957
			 * Updates an existing record in the database
958
			 *
959
			 * Items which already have an ID (i.e. the ID is not NULL) will
960
			 * be updated in the database.
961
			 *
962
			 * The SQL statement must be a string suitable for being used as
963
			 * prepared statement. It must include question marks for binding
964
			 * the values from the item to the statement before they are
965
			 * sent to the database server. The order of the columns must
966
			 * correspond to the order in the save() method, so the
967
			 * correct values are bound to the columns.
968
			 *
969
			 * The SQL statement should conform to the ANSI standard to be
970
			 * compatible with most relational database systems. This also
971
			 * includes using double quotes for table and column names.
972
			 *
973
			 * @param string SQL statement for updating records
974
			 * @since 2023.10
975
			 * @category Developer
976
			 * @see mshop/common/manager/insert/ansi
977
			 * @see mshop/common/manager/newid/ansi
978
			 * @see mshop/common/manager/delete/ansi
979
			 * @see mshop/common/manager/search/ansi
980
			 * @see mshop/common/manager/count/ansi
981
			 */
982
			$path = $this->getConfigKey( 'update' );
983
			$sql = $this->addSqlColumns( array_keys( $columns ), $this->getSqlConfig( $path ), false );
984
		}
985
986
		$idx = 1;
987
		$values = $item->toArray( true );
988
		$stmt = $this->getCachedStatement( $conn, $path, $sql );
989
990
		foreach( $columns as $entry )
991
		{
992
			$value = $values[$entry->getCode()] ?? null;
993
			$value = $entry->getType() === 'json' ? json_encode( $value, JSON_FORCE_OBJECT ) : $value;
994
			$stmt->bind( $idx++, $value, \Aimeos\Base\Criteria\SQL::type( $entry->getType() ) );
995
		}
996
997
		$stmt->bind( $idx++, $context->datetime() ); // mtime
998
		$stmt->bind( $idx++, $context->editor() );
999
1000
		if( $id !== null ) {
1001
			$stmt->bind( $idx++, $context->locale()->getSiteId() . '%' );
1002
			$stmt->bind( $idx++, $id, \Aimeos\Base\DB\Statement\Base::PARAM_INT );
1003
		} else {
1004
			$stmt->bind( $idx++, $this->siteId( $item->getSiteId(), \Aimeos\MShop\Locale\Manager\Base::SITE_SUBTREE ) );
1005
			$stmt->bind( $idx++, $context->datetime() ); // ctime
1006
		}
1007
1008
		$stmt->execute()->finish();
1009
1010
		if( $id === null )
1011
		{
1012
			/** mshop/common/manager/newid/mysql
1013
			 * Retrieves the ID generated by the database when inserting a new record
1014
			 *
1015
			 * @see mshop/common/manager/newid/ansi
1016
			 */
1017
1018
			/** mshop/common/manager/newid/ansi
1019
			 * Retrieves the ID generated by the database when inserting a new record
1020
			 *
1021
			 * As soon as a new record is inserted into the database table,
1022
			 * the database server generates a new and unique identifier for
1023
			 * that record. This ID can be used for retrieving, updating and
1024
			 * deleting that specific record from the table again.
1025
			 *
1026
			 * For MySQL:
1027
			 *  SELECT LAST_INSERT_ID()
1028
			 * For PostgreSQL:
1029
			 *  SELECT currval('seq_matt_id')
1030
			 * For SQL Server:
1031
			 *  SELECT SCOPE_IDENTITY()
1032
			 * For Oracle:
1033
			 *  SELECT "seq_matt_id".CURRVAL FROM DUAL
1034
			 *
1035
			 * There's no way to retrive the new ID by a SQL statements that
1036
			 * fits for most database servers as they implement their own
1037
			 * specific way.
1038
			 *
1039
			 * @param string SQL statement for retrieving the last inserted record ID
1040
			 * @since 2023.10
1041
			 * @category Developer
1042
			 * @see mshop/common/manager/insert/ansi
1043
			 * @see mshop/common/manager/update/ansi
1044
			 * @see mshop/common/manager/delete/ansi
1045
			 * @see mshop/common/manager/search/ansi
1046
			 * @see mshop/common/manager/count/ansi
1047
			 */
1048
			$id = $this->newId( $conn, 'mshop/common/manager/newid' );
1049
		}
1050
1051
		return $item->setId( $id );
1052
	}
1053
1054
1055
	/**
1056
	 * Replaces the given marker with an expression
1057
	 *
1058
	 * @param string $column Name (including alias) of the column
1059
	 * @param mixed $value Value used in the expression
1060
	 * @param string $op Operator used in the expression
1061
	 * @param int $type Type constant from \Aimeos\Base\DB\Statement\Base class
1062
	 * @return string Created expression
1063
	 */
1064
	protected function toExpression( string $column, $value, string $op = '==',
1065
		int $type = \Aimeos\Base\DB\Statement\Base::PARAM_STR ) : string
1066
	{
1067
		$types = ['marker' => $type];
1068
		$translations = ['marker' => $column];
1069
		$value = ( is_array( $value ) ? array_unique( $value ) : $value );
1070
1071
		return $this->getSearch()->compare( $op, 'marker', $value )->toSource( $types, $translations );
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
1087
	/**
1088
	 * Cuts the last part separated by a dot repeatedly and returns the list of resulting string.
1089
	 *
1090
	 * @param string[] $prefix Required base prefixes of the search keys
1091
	 * @param string $string String containing parts separated by dots
1092
	 * @return array List of resulting strings
1093
	 */
1094
	private function cutNameTail( array $prefix, string $string ) : array
1095
	{
1096
		$result = [];
1097
		$noprefix = true;
1098
		$strlen = strlen( $string );
1099
1100
		foreach( $prefix as $key )
1101
		{
1102
			$len = strlen( $key );
1103
1104
			if( strncmp( $string, $key, $len ) === 0 )
1105
			{
1106
				if( $strlen > $len && ( $pos = strrpos( $string, '.' ) ) !== false )
1107
				{
1108
					$result[] = $string = substr( $string, 0, $pos );
1109
					$result = array_merge( $result, $this->cutNameTail( $prefix, $string ) );
1110
					$noprefix = false;
1111
				}
1112
1113
				break;
1114
			}
1115
		}
1116
1117
		if( $noprefix )
1118
		{
1119
			if( ( $pos = strrpos( $string, ':' ) ) !== false ) {
1120
				$result[] = substr( $string, 0, $pos );
1121
				$result[] = $string;
1122
			} elseif( ( $pos = strrpos( $string, '.' ) ) !== false ) {
1123
				$result[] = substr( $string, 0, $pos );
1124
			} else {
1125
				$result[] = $string;
1126
			}
1127
		}
1128
1129
		return $result;
1130
	}
1131
1132
1133
	/**
1134
	 * Returns a list of unique criteria names shortend by the last element after the ''
1135
	 *
1136
	 * @param string[] $prefix Required base prefixes of the search keys
1137
	 * @param \Aimeos\Base\Criteria\Expression\Iface|null $expr Criteria object
1138
	 * @return array List of shortend criteria names
1139
	 */
1140
	private function getCriteriaKeys( array $prefix, \Aimeos\Base\Criteria\Expression\Iface $expr = null ) : array
1141
	{
1142
		if( $expr === null ) { return []; }
1143
1144
		$result = [];
1145
1146
		foreach( $this->getCriteriaNames( $expr ) as $item )
1147
		{
1148
			if( strncmp( $item, 'sort:', 5 ) === 0 ) {
1149
				$item = substr( $item, 5 );
1150
			}
1151
1152
			if( ( $pos = strpos( $item, '(' ) ) !== false ) {
1153
				$item = substr( $item, 0, $pos );
1154
			}
1155
1156
			$result = array_merge( $result, $this->cutNameTail( $prefix, $item ) );
1157
		}
1158
1159
		return $result;
1160
	}
1161
1162
1163
	/**
1164
	 * Returns a list of criteria names from a expression and its sub-expressions.
1165
	 *
1166
	 * @param \Aimeos\Base\Criteria\Expression\Iface Criteria object
0 ignored issues
show
Bug introduced by
The type Aimeos\MShop\Common\Manager\Criteria was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1167
	 * @return array List of criteria names
1168
	 */
1169
	private function getCriteriaNames( \Aimeos\Base\Criteria\Expression\Iface $expr ) : array
1170
	{
1171
		if( $expr instanceof \Aimeos\Base\Criteria\Expression\Compare\Iface ) {
1172
			return array( $expr->getName() );
1173
		}
1174
1175
		if( $expr instanceof \Aimeos\Base\Criteria\Expression\Combine\Iface )
1176
		{
1177
			$list = [];
1178
			foreach( $expr->getExpressions() as $item ) {
1179
				$list = array_merge( $list, $this->getCriteriaNames( $item ) );
1180
			}
1181
			return $list;
1182
		}
1183
1184
		if( $expr instanceof \Aimeos\Base\Criteria\Expression\Sort\Iface ) {
1185
			return array( $expr->getName() );
1186
		}
1187
1188
		return [];
1189
	}
1190
}
1191