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

DB::getCachedStatement()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 8
c 1
b 0
f 1
dl 0
loc 16
rs 9.6111
cc 5
nc 3
nop 3
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