Passed
Push — master ( baa5ed...e67b42 )
by Aimeos
05:46
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 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