Passed
Push — master ( c46287...9f1c00 )
by Aimeos
05:19
created

DB::searchItemsBase()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 39
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 21
dl 0
loc 39
rs 9.584
c 1
b 0
f 1
cc 4
nc 6
nop 8

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 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 manager domain
55
	 *
56
	 * @return string Manager domain e.g. "product"
57
	 */
58
	abstract protected function getDomain() : string;
59
60
61
	/**
62
	 * Returns the attribute helper functions for searching defined by the manager.
63
	 *
64
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of search attribute items
65
	 * @return array Associative array of attribute code and helper function
66
	 */
67
	abstract protected function getSearchFunctions( array $attributes ) : array;
68
69
70
	/**
71
	 * Returns the attribute translations for searching defined by the manager.
72
	 *
73
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of search attribute items
74
	 * @return array Associative array of attribute code and internal attribute code
75
	 */
76
	abstract protected function getSearchTranslations( array $attributes ) : array;
77
78
79
	/**
80
	 * Returns the attribute types for searching defined by the manager.
81
	 *
82
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of search attribute items
83
	 * @return array Associative array of attribute code and internal attribute type
84
	 */
85
	abstract protected function getSearchTypes( array $attributes ) : array;
86
87
88
	/**
89
	 * Returns the name of the used table
90
	 *
91
	 * @return string Table name e.g. "mshop_product_lists_type"
92
	 */
93
	abstract protected function getTable() : string;
94
95
96
	/**
97
	 * Returns the outmost decorator of the decorator stack
98
	 *
99
	 * @return \Aimeos\MShop\Common\Manager\Iface Outmost decorator object
100
	 */
101
	abstract protected function object() : \Aimeos\MShop\Common\Manager\Iface;
102
103
104
	/**
105
	 * Returns the site expression for the given name
106
	 *
107
	 * @param string $name Name of the site condition
108
	 * @param int $sitelevel Site level constant from \Aimeos\MShop\Locale\Manager\Base
109
	 * @return \Aimeos\Base\Criteria\Expression\Iface Site search condition
110
	 */
111
	abstract protected function siteCondition( string $name, int $sitelevel ) : \Aimeos\Base\Criteria\Expression\Iface;
112
113
114
	/**
115
	 * Returns the site ID that should be used based on the site level
116
	 *
117
	 * @param string $siteId Site ID to check
118
	 * @param int $sitelevel Site level to check against
119
	 * @return string Site ID that should be use based on the site level
120
	 * @since 2022.04
121
	 */
122
	abstract protected function siteId( string $siteId, int $sitelevel ) : string;
123
124
125
	/**
126
	 * Adds additional column names to SQL statement
127
	 *
128
	 * @param string[] $columns List of column names
129
	 * @param string $sql Insert or update SQL statement
130
	 * @param bool $mode True for insert, false for update statement
131
	 * @return string Modified insert or update SQL statement
132
	 */
133
	protected function addSqlColumns( array $columns, string $sql, bool $mode = true ) : string
134
	{
135
		$names = $values = '';
136
137
		if( $mode )
138
		{
139
			foreach( $columns as $name ) {
140
				$names .= '"' . $name . '", '; $values .= '?, ';
141
			}
142
		}
143
		else
144
		{
145
			foreach( $columns as $name ) {
146
				$names .= '"' . $name . '" = ?, ';
147
			}
148
		}
149
150
		return str_replace( [':names', ':values'], [$names, $values], $sql );
151
	}
152
153
154
	/**
155
	 * Counts the number products that are available for the values of the given key.
156
	 *
157
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria
158
	 * @param array|string $keys Search key or list of keys for aggregation
159
	 * @param string $cfgPath Configuration key for the SQL statement
160
	 * @param string[] $required List of domain/sub-domain names like "catalog.index" that must be additionally joined
161
	 * @param string|null $value Search key for aggregating the value column
162
	 * @param string|null $type Type of aggregation, e.g.  "sum", "min", "max" or NULL for "count"
163
	 * @return \Aimeos\Map List of ID values as key and the number of counted products as value
164
	 * @todo 2018.01 Reorder Parameter list
165
	 */
166
	protected function aggregateBase( \Aimeos\Base\Criteria\Iface $search, $keys, string $cfgPath,
167
		array $required = [], string $value = null, string $type = null ) : \Aimeos\Map
168
	{
169
		/** mshop/common/manager/aggregate/limit
170
		 * Limits the number of records that are used when aggregating items
171
		 *
172
		 * As counting huge amount of records (several 10 000 records) takes a long time,
173
		 * the limit can cut down response times so the counts are available more quickly
174
		 * in the front-end and the server load is reduced.
175
		 *
176
		 * Using a low limit can lead to incorrect numbers if the amount of found items
177
		 * is very high. Approximate item counts are normally not a problem but it can
178
		 * lead to the situation that visitors see that no items are available despite
179
		 * the fact that there would be at least one.
180
		 *
181
		 * @param integer Number of records
182
		 * @since 2021.04
183
		 */
184
		$limit = $this->context()->config()->get( 'mshop/common/manager/aggregate/limit', 10000 );
185
186
		if( empty( $keys ) )
187
		{
188
			$msg = $this->context()->translate( 'mshop', 'At least one key is required for aggregation' );
189
			throw new \Aimeos\MShop\Exception( $msg );
190
		}
191
192
		$attrMap = array_column( array_filter( $this->object()->getSearchAttributes(), function( $item ) {
193
			return $item->isPublic() || strncmp( $item->getCode(), 'agg:', 4 ) === 0;
194
		} ), null, 'code' );
195
196
		if( $value === null && ( $value = key( $attrMap ) ) === null )
197
		{
198
			$msg = $this->context()->translate( 'mshop', 'No search keys available' );
199
			throw new \Aimeos\MShop\Exception( $msg );
200
		}
201
202
		if( ( $pos = strpos( $valkey = $value, '(' ) ) !== false ) {
203
			$value = substr( $value, 0, $pos ) . '()'; // remove parameters from search function
204
		}
205
206
		if( !isset( $attrMap[$value] ) )
207
		{
208
			$msg = $this->context()->translate( 'mshop', 'Unknown search key "%1$s"' );
209
			throw new \Aimeos\MShop\Exception( sprintf( $msg, $value ) );
210
		}
211
212
		$keys = (array) $keys;
213
		$acols = $cols = $expr = [];
214
		$search = (clone $search)->slice( $search->getOffset(), min( $search->getLimit(), $limit ) );
215
216
		foreach( $keys as $string )
217
		{
218
			if( ( $attrItem = $attrMap[$string] ?? null ) === null )
219
			{
220
				$msg = $this->context()->translate( 'mshop', 'Unknown search key "%1$s"' );
221
				throw new \Aimeos\MShop\Exception( sprintf( $msg, $string ) );
222
			}
223
224
			$cols[] = $attrItem->getInternalCode();
225
			$acols[] = $attrItem->getInternalCode() . ' AS "' . $string . '"';
226
227
			$expr[] = $search->compare( '!=', $string, null ); // required for the joins
228
		}
229
230
		$search->add( $search->and( $expr ) )->add( $valkey, '!=', null );
231
232
		$sql = $this->getSqlConfig( $cfgPath );
233
		$sql = str_replace( ':cols', join( ', ', $cols ), $sql );
234
		$sql = str_replace( ':acols', join( ', ', $acols ), $sql );
235
		$sql = str_replace( ':keys', '"' . join( '", "', $keys ) . '"', $sql );
236
		$sql = str_replace( ':val', $attrMap[$value]->getInternalCode(), $sql );
237
		$sql = str_replace( ':type', in_array( $type, ['avg', 'count', 'max', 'min', 'sum'] ) ? $type : 'count', $sql );
238
239
		return $this->aggregateResult( $search, $sql, $required );
240
	}
241
242
243
	/**
244
	 * Returns the aggregated values for the given SQL string and filter.
245
	 *
246
	 * @param \Aimeos\Base\Criteria\Iface $filter Filter object
247
	 * @param string $sql SQL statement
248
	 * @param string[] $required List of domain/sub-domain names like "catalog.index" that must be additionally joined
249
	 * @return \Aimeos\Map (Nested) list of aggregated values as key and the number of counted products as value
250
	 */
251
	protected function aggregateResult( \Aimeos\Base\Criteria\Iface $filter, string $sql, array $required ) : \Aimeos\Map
252
	{
253
		$map = [];
254
		$total = null;
255
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
256
		$conn = $this->context()->db( $this->getResourceName() );
257
		$results = $this->searchItemsBase( $conn, $filter, $sql, '', $required, $total, $level );
258
259
		while( $row = $results->fetch() )
260
		{
261
			$row = $this->transform( $row );
262
263
			$temp = &$map;
264
			$last = array_pop( $row );
265
266
			foreach( $row as $val ) {
267
				$temp[$val] = $temp[$val] ?? [];
268
				$temp = &$temp[$val];
269
			}
270
			$temp = $last;
271
		}
272
273
		return map( $map );
274
	}
275
276
277
	/**
278
	 * Removes old entries from the storage.
279
	 *
280
	 * @param iterable $siteids List of IDs for sites whose entries should be deleted
281
	 * @param string $cfgpath Configuration key to the cleanup statement
282
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
283
	 */
284
	protected function clearBase( iterable $siteids, string $cfgpath ) : \Aimeos\MShop\Common\Manager\Iface
285
	{
286
		if( empty( $siteids ) ) {
287
			return $this;
288
		}
289
290
		$conn = $this->context()->db( $this->getResourceName() );
291
292
		$sql = $this->getSqlConfig( $cfgpath );
293
		$sql = str_replace( ':cond', '1=1', $sql );
294
295
		$stmt = $conn->create( $sql );
296
297
		foreach( $siteids as $siteid )
298
		{
299
			$stmt->bind( 1, $siteid );
300
			$stmt->execute()->finish();
301
		}
302
303
		return $this;
304
	}
305
306
307
	/**
308
	 * Deletes items.
309
	 *
310
	 * @param \Aimeos\MShop\Common\Item\Iface|\Aimeos\Map|array|string $items List of item objects or IDs of the items
311
	 * @param string $cfgpath Configuration path to the SQL statement
312
	 * @param bool $siteid If siteid should be used in the statement
313
	 * @param string $name Name of the ID column
314
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
315
	 */
316
	protected function deleteItemsBase( $items, string $cfgpath, bool $siteid = true,
317
		string $name = 'id' ) : \Aimeos\MShop\Common\Manager\Iface
318
	{
319
		if( map( $items )->isEmpty() ) {
320
			return $this;
321
		}
322
323
		$search = $this->object()->filter();
324
		$search->setConditions( $search->compare( '==', $name, $items ) );
325
326
		$types = array( $name => \Aimeos\Base\DB\Statement\Base::PARAM_STR );
327
		$translations = array( $name => '"' . $name . '"' );
328
329
		$cond = $search->getConditionSource( $types, $translations );
330
		$sql = str_replace( ':cond', $cond, $this->getSqlConfig( $cfgpath ) );
331
332
		$context = $this->context();
333
		$conn = $context->db( $this->getResourceName() );
334
335
		$stmt = $conn->create( $sql );
336
337
		if( $siteid ) {
338
			$stmt->bind( 1, $context->locale()->getSiteId() . '%' );
339
		}
340
341
		$stmt->execute()->finish();
342
343
		return $this;
344
	}
345
346
347
	/**
348
	 * Returns the table alias name.
349
	 *
350
	 * @return string Table alias name
351
	 */
352
	protected function alias() : string
353
	{
354
		$parts = explode( '/', $this->getSubPath() );
0 ignored issues
show
Bug introduced by
It seems like getSubPath() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

354
		$parts = explode( '/', $this->/** @scrutinizer ignore-call */ getSubPath() );
Loading history...
355
		$str = 'm' . substr( $this->getDomain(), 0, 3 );
356
357
		foreach( $parts as $part ) {
358
			$str .= substr( $part, 0, 2 );
359
		}
360
361
		return $str;
362
	}
363
364
365
	/**
366
	 * Returns a sorted list of required criteria keys.
367
	 *
368
	 * @param \Aimeos\Base\Criteria\Iface $criteria Search criteria object
369
	 * @param string[] $required List of prefixes of required search conditions
370
	 * @return string[] Sorted list of criteria keys
371
	 */
372
	protected function getCriteriaKeyList( \Aimeos\Base\Criteria\Iface $criteria, array $required ) : array
373
	{
374
		$keys = array_merge( $required, $this->getCriteriaKeys( $required, $criteria->getConditions() ) );
375
376
		foreach( $criteria->getSortations() as $sortation ) {
377
			$keys = array_merge( $keys, $this->getCriteriaKeys( $required, $sortation ) );
378
		}
379
380
		$keys = array_unique( array_merge( $required, $keys ) );
381
		sort( $keys );
382
383
		return $keys;
384
	}
385
386
387
	/**
388
	 * Returns the name of the resource.
389
	 *
390
	 * @return string Name of the resource, e.g. "db-product"
391
	 */
392
	protected function getResourceName() : string
393
	{
394
		if( $this->resourceName === null ) {
395
			$this->setResourceName( 'db-' . $this->getDomain() );
396
		}
397
398
		return $this->resourceName;
399
	}
400
401
402
	/**
403
	 * Sets the name of the database resource that should be used.
404
	 *
405
	 * @param string $name Name of the resource
406
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
407
	 */
408
	protected function setResourceName( string $name ) : \Aimeos\MShop\Common\Manager\Iface
409
	{
410
		$config = $this->context()->config();
411
412
		if( $config->get( 'resource/' . $name ) === null ) {
413
			$this->resourceName = $config->get( 'resource/default', 'db' );
414
		} else {
415
			$this->resourceName = $name;
416
		}
417
418
		return $this;
419
	}
420
421
422
	/**
423
	 * Returns the search attribute objects used for searching.
424
	 *
425
	 * @param array $list Associative list of search keys and the lists of search definitions
426
	 * @param string $path Configuration path to the sub-domains for fetching the search definitions
427
	 * @param string[] $default List of sub-domains if no others are configured
428
	 * @param bool $withsub True to include search definitions of sub-domains, false if not
429
	 * @return \Aimeos\Base\Criteria\Attribute\Iface[] Associative list of search keys and criteria attribute items as values
430
	 * @since 2014.09
431
	 */
432
	protected function getSearchAttributesBase( array $list, string $path, array $default, bool $withsub ) : array
433
	{
434
		$attr = $this->createAttributes( $list );
435
436
		if( $withsub === true )
437
		{
438
			$domains = $this->context()->config()->get( $path, $default );
439
440
			foreach( $domains as $domain ) {
441
				$attr += $this->object()->getSubManager( $domain )->getSearchAttributes( true );
442
			}
443
		}
444
445
		return $attr;
446
	}
447
448
449
	/**
450
	 * Returns the search results for the given SQL statement.
451
	 *
452
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
453
	 * @param string $sql SQL statement
454
	 * @return \Aimeos\Base\DB\Result\Iface Search result object
455
	 */
456
	protected function getSearchResults( \Aimeos\Base\DB\Connection\Iface $conn, string $sql ) : \Aimeos\Base\DB\Result\Iface
457
	{
458
		$time = microtime( true );
459
460
		$stmt = $conn->create( $sql );
461
		$result = $stmt->execute();
462
463
		$level = \Aimeos\Base\Logger\Iface::DEBUG;
464
		$time = ( microtime( true ) - $time ) * 1000;
465
		$msg = 'Time: ' . $time . "ms\n"
466
			. 'Class: ' . get_class( $this ) . "\n"
467
			. str_replace( ["\t", "\n\n"], ['', "\n"], trim( (string) $stmt ) );
468
469
		if( $time > 1000.0 )
470
		{
471
			$level = \Aimeos\Base\Logger\Iface::NOTICE;
472
			$msg .= "\n" . ( new \Exception() )->getTraceAsString();
473
		}
474
475
		$this->context()->logger()->log( $msg, $level, 'core/sql' );
476
477
		return $result;
478
	}
479
480
481
	/**
482
	 * Returns the site coditions for the search request
483
	 *
484
	 * @param string[] $keys Sorted list of criteria keys
485
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items as values
486
	 * @param int $sitelevel Site level constant from \Aimeos\MShop\Locale\Manager\Base
487
	 * @return \Aimeos\Base\Criteria\Expression\Iface[] List of search conditions
488
	 * @since 2015.01
489
	 */
490
	protected function getSiteConditions( array $keys, array $attributes, int $sitelevel ) : array
491
	{
492
		$list = [];
493
		$entries = array_column( $attributes, null, 'code' );
494
495
		foreach( $keys as $key )
496
		{
497
			$name = $key . '.siteid';
498
499
			if( isset( $entries[$name] ) ) {
500
				$list[] = $this->siteCondition( $name, $sitelevel );
501
			} elseif( isset( $entries['siteid'] ) ) {
502
				$list[] = $this->siteCondition( 'siteid', $sitelevel );
503
			}
504
		}
505
506
		return $list;
507
	}
508
509
510
	/**
511
	 * Returns the SQL statement for the given config path
512
	 *
513
	 * If available, the database specific SQL statement is returned, otherwise
514
	 * the ANSI SQL statement. The database type is determined via the resource
515
	 * adapter.
516
	 *
517
	 * @param string $path Configuration path to the SQL statement
518
	 * @param array $replace Associative list of keys with strings to replace by their values
519
	 * @return array|string ANSI or database specific SQL statement
520
	 */
521
	protected function getSqlConfig( string $path, array $replace = [] )
522
	{
523
		if( preg_match( '#^[a-z0-9\-]+(/[a-z0-9\-]+)*$#', $path ) !== 1 )
524
		{
525
			foreach( $replace as $key => $value ) {
526
				$path = str_replace( $key, $value, $path );
527
			}
528
529
			return $path;
530
		}
531
532
		$config = $this->context()->config();
533
		$adapter = $config->get( 'resource/' . $this->getResourceName() . '/adapter' );
534
535
		if( ( $sql = $config->get( $path . '/' . $adapter, $config->get( $path . '/ansi' ) ) ) === null )
536
		{
537
			$parts = explode( '/', $path );
538
			$cpath = 'mshop/common/manager/' . end( $parts );
539
			$sql = $config->get( $cpath . '/' . $adapter, $config->get( $cpath . '/ansi', $path ) );
540
		}
541
542
		foreach( $replace as $key => $value ) {
543
			$sql = str_replace( $key, $value, $sql );
544
		}
545
546
		return str_replace( [':alias', ':table'], [$this->alias(), $this->getTable()], $sql );
547
	}
548
549
550
	/**
551
	 * Returns the available sub-manager names
552
	 *
553
	 * @return array Sub-manager names, e.g. ['lists', 'property', 'type']
554
	 */
555
	protected function getSubManagers() : array
556
	{
557
		return $this->context()->config()->get( $this->getConfigKey( 'submanagers' ), [] );
558
	}
559
560
561
	/**
562
	 * Sets the base criteria "status".
563
	 * (setConditions overwrites the base criteria)
564
	 *
565
	 * @param string $domain Name of the domain/sub-domain like "product" or "product.list"
566
	 * @param bool|null $default TRUE for status=1, NULL for status>0, FALSE for no restriction
567
	 * @return \Aimeos\Base\Criteria\Iface Search critery object
568
	 */
569
	protected function filterBase( string $domain, ?bool $default = false ) : \Aimeos\Base\Criteria\Iface
570
	{
571
		$context = $this->context();
572
		$db = $this->getResourceName();
573
		$conn = $context->db( $db );
574
		$config = $context->config();
575
576
		if( ( $adapter = $config->get( 'resource/' . $db . '/adapter' ) ) === null ) {
577
			$adapter = $config->get( 'resource/db/adapter' );
578
		}
579
580
		switch( $adapter )
581
		{
582
			case 'pgsql':
583
				$filter = new \Aimeos\Base\Criteria\PgSQL( $conn ); break;
584
			default:
585
				$filter = new \Aimeos\Base\Criteria\SQL( $conn ); break;
586
		}
587
588
		if( $default !== false ) {
589
			$filter->add( $domain . '.status', $default ? '==' : '>=', 1 );
590
		}
591
592
		return $filter;
593
	}
594
595
596
	/**
597
	 * Returns the item for the given search key/value pairs.
598
	 *
599
	 * @param array $pairs Search key/value pairs for the item
600
	 * @param string[] $ref List of domains whose items should be fetched too
601
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
602
	 * @return \Aimeos\MShop\Common\Item\Iface Requested item
603
	 * @throws \Aimeos\MShop\Exception if no item with the given ID found
604
	 */
605
	protected function findBase( array $pairs, array $ref, ?bool $default ) : \Aimeos\MShop\Common\Item\Iface
606
	{
607
		$expr = [];
608
		$criteria = $this->object()->filter( $default )->slice( 0, 1 );
609
610
		foreach( $pairs as $key => $value )
611
		{
612
			if( $value === null )
613
			{
614
				$msg = $this->context()->translate( 'mshop', 'Required value for "%1$s" is missing' );
615
				throw new \Aimeos\MShop\Exception( sprintf( $msg, $key ) );
616
			}
617
			$expr[] = $criteria->compare( '==', $key, $value );
618
		}
619
620
		$criteria->setConditions( $criteria->and( $expr ) );
621
622
		if( ( $item = $this->object()->search( $criteria, $ref )->first() ) ) {
623
			return $item;
624
		}
625
626
		$msg = $this->context()->translate( 'mshop', 'No item found for conditions: %1$s' );
627
		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

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

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

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

831
		$result = $conn->create( /** @scrutinizer ignore-type */ $sql )->execute();
Loading history...
832
833
		if( ( $row = $result->fetch( \Aimeos\Base\DB\Result\Base::FETCH_NUM ) ) === false )
834
		{
835
			$msg = $this->context()->translate( 'mshop', 'ID of last inserted database record not available' );
836
			throw new \Aimeos\MShop\Exception( $msg );
837
		}
838
		$result->finish();
839
840
		return $row[0];
841
	}
842
843
844
	/**
845
	 * Returns the search result of the statement combined with the given criteria.
846
	 *
847
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
848
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria object
849
	 * @param string $cfgPathSearch Path to SQL statement in configuration for searching
850
	 * @param string $cfgPathCount Path to SQL statement in configuration for counting
851
	 * @param string[] $required Additional search keys to add conditions for even if no conditions are available
852
	 * @param int|null $total Contains the number of all records matching the criteria if not null
853
	 * @param int $sitelevel Constant from \Aimeos\MShop\Locale\Manager\Base for defining which site IDs should be used for searching
854
	 * @param \Aimeos\Base\Criteria\Plugin\Iface[] $plugins Associative list of search keys and criteria plugin items as values
855
	 * @return \Aimeos\Base\DB\Result\Iface SQL result object for accessing the found records
856
	 * @throws \Aimeos\MShop\Exception if no number of all matching records is available
857
	 */
858
	protected function searchItemsBase( \Aimeos\Base\DB\Connection\Iface $conn, \Aimeos\Base\Criteria\Iface $search,
859
		string $cfgPathSearch, string $cfgPathCount, array $required, int &$total = null,
860
		int $sitelevel = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL, array $plugins = [] ) : \Aimeos\Base\DB\Result\Iface
861
	{
862
		$conditions = $search->getConditions();
863
		$attributes = $this->object()->getSearchAttributes();
864
865
		$keys = $this->getCriteriaKeyList( $search, $required );
866
		$joins = $this->getRequiredJoins( $attributes, $keys, array_shift( $required ) );
867
868
		$attronly = $this->object()->getSearchAttributes( false );
869
		$cond = $this->getSiteConditions( $keys, $attributes, $sitelevel );
870
871
		if( $conditions !== null ) {
872
			$cond[] = $conditions;
873
		}
874
875
		$search = clone $search;
876
		$search->setConditions( $search->and( $cond ) );
877
878
		$replace = $this->getSQLReplacements( $search, $attributes, $attronly, $plugins, $joins );
879
880
		if( $total !== null )
881
		{
882
			$sql = $this->getSqlConfig( $cfgPathCount, $replace );
883
			$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

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

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