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

690
		throw new \Aimeos\MShop\Exception( sprintf( $msg, /** @scrutinizer ignore-type */ print_r( $pairs, true ) ), 404 );
Loading history...
691
	}
692
693
694
	/**
695
	 * Returns the cached statement for the given key or creates a new prepared statement.
696
	 * If no SQL string is given, the key is used to retrieve the SQL string from the configuration.
697
	 *
698
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
699
	 * @param string $cfgkey Unique key for the SQL
700
	 * @param string|null $sql SQL string if it shouldn't be retrieved from the configuration
701
	 * @return \Aimeos\Base\DB\Statement\Iface Database statement object
702
	 */
703
	protected function getCachedStatement( \Aimeos\Base\DB\Connection\Iface $conn, string $cfgkey,
704
		string $sql = null ) : \Aimeos\Base\DB\Statement\Iface
705
	{
706
		if( !isset( $this->cachedStmts['stmt'][$cfgkey] )
707
			|| !isset( $this->cachedStmts['conn'][$cfgkey] )
708
			|| $conn !== $this->cachedStmts['conn'][$cfgkey]
709
		) {
710
			if( $sql === null ) {
711
				$sql = $this->getSqlConfig( $cfgkey );
712
			}
713
714
			$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

714
			$this->cachedStmts['stmt'][$cfgkey] = $conn->create( /** @scrutinizer ignore-type */ $sql );
Loading history...
715
			$this->cachedStmts['conn'][$cfgkey] = $conn;
716
		}
717
718
		return $this->cachedStmts['stmt'][$cfgkey];
719
	}
720
721
722
	/**
723
	 * Returns the item for the given search key and ID.
724
	 *
725
	 * @param string $key Search key for the requested ID
726
	 * @param string $id Unique ID to search for
727
	 * @param string[] $ref List of domains whose items should be fetched too
728
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
729
	 * @return \Aimeos\MShop\Common\Item\Iface Requested item
730
	 * @throws \Aimeos\MShop\Exception if no item with the given ID found
731
	 */
732
	protected function getItemBase( string $key, string $id, array $ref, ?bool $default ) : \Aimeos\MShop\Common\Item\Iface
733
	{
734
		$criteria = $this->object()->filter( $default )->add( [$key => $id] )->slice( 0, 1 );
735
736
		if( ( $item = $this->object()->search( $criteria, $ref )->first() ) ) {
737
			return $item;
738
		}
739
740
		$msg = $this->context()->translate( 'mshop', 'Item with ID "%2$s" in "%1$s" not found' );
741
		throw new \Aimeos\MShop\Exception( sprintf( $msg, $key, $id ), 404 );
742
	}
743
744
745
	/**
746
	 * Returns the SQL strings for joining dependent tables.
747
	 *
748
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of criteria attribute items
749
	 * @param string $prefix Search key prefix
750
	 * @return array List of JOIN SQL strings
751
	 */
752
	private function getJoins( array $attributes, string $prefix ) : array
753
	{
754
		$iface = \Aimeos\Base\Criteria\Attribute\Iface::class;
755
		$name = $prefix . '.id';
756
757
		if( isset( $attributes[$prefix] ) && $attributes[$prefix] instanceof $iface ) {
758
			return $attributes[$prefix]->getInternalDeps();
759
		} elseif( isset( $attributes[$name] ) && $attributes[$name] instanceof $iface ) {
760
			return $attributes[$name]->getInternalDeps();
761
		} elseif( isset( $attributes['id'] ) && $attributes['id'] instanceof $iface ) {
762
			return $attributes['id']->getInternalDeps();
763
		}
764
765
		return [];
766
	}
767
768
769
	/**
770
	 * Returns the required SQL joins for the critera.
771
	 *
772
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of criteria attribute items
773
	 * @param string $prefix Search key prefix
774
	 * @return array|null List of JOIN SQL strings
775
	 */
776
	private function getRequiredJoins( array $attributes, array $keys, string $basekey = null ) : array
777
	{
778
		$joins = [];
779
780
		foreach( $keys as $key )
781
		{
782
			if( $key !== $basekey ) {
783
				$joins = array_merge( $joins, $this->getJoins( $attributes, $key ) );
784
			}
785
		}
786
787
		return array_unique( $joins );
788
	}
789
790
791
	/**
792
	 * Returns the available manager types
793
	 *
794
	 * @param string $type Main manager type
795
	 * @param string $path Configuration path to the sub-domains
796
	 * @param string[] $default List of sub-domains if no others are configured
797
	 * @param bool $withsub Return also the resource type of sub-managers if true
798
	 * @return string[] Type of the manager and submanagers, subtypes are separated by slashes
799
	 */
800
	protected function getResourceTypeBase( string $type, string $path, array $default, bool $withsub ) : array
801
	{
802
		$list = [$type];
803
804
		if( $withsub )
805
		{
806
			foreach( $this->context()->config()->get( $path, $default ) as $domain ) {
807
				$list = array_merge( $list, $this->object()->getSubManager( $domain )->getResourceType( $withsub ) );
808
			}
809
		}
810
811
		return $list;
812
	}
813
814
815
	/**
816
	 * Returns a search object singleton
817
	 *
818
	 * @return \Aimeos\Base\Criteria\Iface Search object
819
	 */
820
	protected function getSearch() : \Aimeos\Base\Criteria\Iface
821
	{
822
		if( !isset( $this->search ) ) {
823
			$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

823
			/** @scrutinizer ignore-call */ 
824
   $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...
824
		}
825
826
		return $this->search;
827
	}
828
829
830
	/**
831
	 * Returns the string replacements for the SQL statements
832
	 *
833
	 * @param \Aimeos\Base\Criteria\Iface $search Search critera object
834
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items as values
835
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items as values for the base table
836
	 * @param \Aimeos\Base\Criteria\Plugin\Iface[] $plugins Associative list of search keys and criteria plugin items as values
837
	 * @param string[] $joins Associative list of SQL joins
838
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $columns Additional columns to retrieve values from
839
	 * @return array Array of keys, find and replace arrays
840
	 */
841
	protected function getSQLReplacements( \Aimeos\Base\Criteria\Iface $search, array $attributes, array $attronly, array $plugins, array $joins ) : array
842
	{
843
		$types = $this->getSearchTypes( $attributes );
844
		$funcs = $this->getSearchFunctions( $attributes );
845
		$trans = $this->getSearchTranslations( $attributes );
846
		$trans = $this->aliasTranslations( $trans );
847
848
		if( empty( $search->getSortations() ) && ( $attribute = reset( $attronly ) ) !== false ) {
849
			$search = ( clone $search )->setSortations( [$search->sort( '+', $attribute->getCode() )] );
850
		}
851
		$sorts = $search->translate( $search->getSortations(), $trans, $funcs );
852
853
		$cols = $group = [];
854
		foreach( $attronly as $name => $entry )
855
		{
856
			if( str_contains( $name, ':' ) || empty( $entry->getInternalCode() ) ) {
857
				continue;
858
			}
859
860
			$icode = $entry->getInternalCode();
861
862
			if( !str_contains( $icode, '"' ) )
863
			{
864
				$alias = $this->alias( $entry->getCode() );
865
				$icode = $alias . '."' . $icode . '"';
866
			}
867
868
			$cols[] = $icode . ' AS "' . $entry->getCode() . '"';
869
			$group[] = $icode;
870
		}
871
872
		return [
873
			':columns' => join( ', ', $cols ),
874
			':joins' => join( "\n", array_unique( $joins ) ),
875
			':group' => join( ', ', array_unique( array_merge( $group, $sorts ) ) ),
876
			':cond' => $search->getConditionSource( $types, $trans, $plugins, $funcs ),
877
			':order' => $search->getSortationSource( $types, $trans, $funcs ),
878
			':start' => $search->getOffset(),
879
			':size' => $search->getLimit(),
880
		];
881
	}
882
883
884
	/**
885
	 * Returns the name of the used table
886
	 *
887
	 * @return string Table name e.g. "mshop_product_lists_type"
888
	 */
889
	protected function getTable() : string
890
	{
891
		$subPath = $this->getSubPath();
892
		return 'mshop_' . $this->getDomain() . ( $subPath ? '_' . str_replace( '/', '_', $subPath ) : '' );
893
	}
894
895
896
	/**
897
	 * Initializes the trait
898
	 */
899
	protected function initDb()
900
	{
901
		$parts = array_slice( explode( '\\', strtolower( current( $this->classes() ) ) ), 2, -1 );
0 ignored issues
show
Bug introduced by
It seems like classes() 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

901
		$parts = array_slice( explode( '\\', strtolower( current( $this->/** @scrutinizer ignore-call */ classes() ) ) ), 2, -1 );
Loading history...
902
903
		$this->domain = array_shift( $parts ) ?: '';
0 ignored issues
show
Bug Best Practice introduced by
The property domain does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
904
		array_shift( $parts ); // remove "manager"
905
		$this->subpath = join( '/', $parts );
0 ignored issues
show
Bug Best Practice introduced by
The property subpath does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
906
	}
907
908
909
	/**
910
	 * Returns the newly created ID for the last record which was inserted.
911
	 *
912
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection used to insert the new record
913
	 * @param string $cfgpath Configuration path to the SQL statement for retrieving the new ID of the last inserted record
914
	 * @return string ID of the last record that was inserted by using the given connection
915
	 * @throws \Aimeos\MShop\Exception if there's no ID of the last record available
916
	 */
917
	protected function newId( \Aimeos\Base\DB\Connection\Iface $conn, string $cfgpath ) : string
918
	{
919
		$sql = $this->getSqlConfig( $cfgpath );
920
921
		$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

921
		$result = $conn->create( /** @scrutinizer ignore-type */ $sql )->execute();
Loading history...
922
923
		if( ( $row = $result->fetch( \Aimeos\Base\DB\Result\Base::FETCH_NUM ) ) === false )
924
		{
925
			$msg = $this->context()->translate( 'mshop', 'ID of last inserted database record not available' );
926
			throw new \Aimeos\MShop\Exception( $msg );
927
		}
928
		$result->finish();
929
930
		return $row[0];
931
	}
932
933
934
	/**
935
	 * Returns the search result of the statement combined with the given criteria.
936
	 *
937
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
938
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria object
939
	 * @param string $cfgPathSearch Path to SQL statement in configuration for searching
940
	 * @param string $cfgPathCount Path to SQL statement in configuration for counting
941
	 * @param string[] $required Additional search keys to add conditions for even if no conditions are available
942
	 * @param int|null $total Contains the number of all records matching the criteria if not null
943
	 * @param int $sitelevel Constant from \Aimeos\MShop\Locale\Manager\Base for defining which site IDs should be used for searching
944
	 * @param \Aimeos\Base\Criteria\Plugin\Iface[] $plugins Associative list of search keys and criteria plugin items as values
945
	 * @return \Aimeos\Base\DB\Result\Iface SQL result object for accessing the found records
946
	 * @throws \Aimeos\MShop\Exception if no number of all matching records is available
947
	 */
948
	protected function searchItemsBase( \Aimeos\Base\DB\Connection\Iface $conn, \Aimeos\Base\Criteria\Iface $search,
949
		string $cfgPathSearch, string $cfgPathCount, array $required, int &$total = null,
950
		int $sitelevel = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL, array $plugins = [] ) : \Aimeos\Base\DB\Result\Iface
951
	{
952
		$conditions = $search->getConditions();
953
		$attributes = $this->object()->getSearchAttributes();
954
955
		$keys = $this->getCriteriaKeyList( $search, $required );
956
		$joins = $this->getRequiredJoins( $attributes, $keys, array_shift( $required ) );
957
958
		$attronly = $this->object()->getSearchAttributes( false );
959
		$cond = $this->getSiteConditions( $keys, $attributes, $sitelevel );
960
961
		if( $conditions !== null ) {
962
			$cond[] = $conditions;
963
		}
964
965
		$search = clone $search;
966
		$search->setConditions( $search->and( $cond ) );
967
968
		$replace = $this->getSQLReplacements( $search, $attributes, $attronly, $plugins, $joins );
969
970
		if( $total !== null )
971
		{
972
			$sql = $this->getSqlConfig( $cfgPathCount, $replace );
973
			$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

973
			$result = $this->getSearchResults( $conn, /** @scrutinizer ignore-type */ $sql );
Loading history...
974
			$row = $result->fetch();
975
			$result->finish();
976
977
			if( $row === null )
978
			{
979
				$msg = $this->context()->translate( 'mshop', 'Total results value not found' );
980
				throw new \Aimeos\MShop\Exception( $msg );
981
			}
982
983
			$total = (int) $row['count'];
984
		}
985
986
		return $this->getSearchResults( $conn, $this->getSqlConfig( $cfgPathSearch, $replace ) );
987
	}
988
989
990
	/**
991
	 * Saves an attribute item to the storage.
992
	 *
993
	 * @param \Aimeos\MShop\Common\Item\Iface $item Item object
994
	 * @param bool $fetch True if the new ID should be returned in the item
995
	 * @return \Aimeos\MShop\Common\Item\Iface $item Updated item including the generated ID
996
	 */
997
	protected function saveBase( \Aimeos\MShop\Common\Item\Iface $item, bool $fetch = true ) : \Aimeos\MShop\Common\Item\Iface
998
	{
999
		if( !$item->isModified() ) {
1000
			return $item;
1001
		}
1002
1003
		$context = $this->context();
1004
		$conn = $context->db( $this->getResourceName() );
1005
1006
		$id = $item->getId();
1007
		$columns = array_column( $this->object()->getSaveAttributes(), null, 'internalcode' );
1008
1009
		if( $id === null )
1010
		{
1011
			/** mshop/common/manager/insert/mysql
1012
			 * Inserts a new record into the database table
1013
			 *
1014
			 * @see mshop/common/manager/insert/ansi
1015
			 */
1016
1017
			/** mshop/common/manager/insert/ansi
1018
			 * Inserts a new record into the database table
1019
			 *
1020
			 * Items with no ID yet (i.e. the ID is NULL) will be created in
1021
			 * the database and the newly created ID retrieved afterwards
1022
			 * using the "newid" SQL statement.
1023
			 *
1024
			 * The SQL statement must be a string suitable for being used as
1025
			 * prepared statement. It must include question marks for binding
1026
			 * the values from the item to the statement before they are
1027
			 * sent to the database server. The number of question marks must
1028
			 * be the same as the number of columns listed in the INSERT
1029
			 * statement. The order of the columns must correspond to the
1030
			 * order in the save() method, so the correct values are
1031
			 * bound to the columns.
1032
			 *
1033
			 * The SQL statement should conform to the ANSI standard to be
1034
			 * compatible with most relational database systems. This also
1035
			 * includes using double quotes for table and column names.
1036
			 *
1037
			 * @param string SQL statement for inserting records
1038
			 * @since 2023.10
1039
			 * @see mshop/common/manager/update/ansi
1040
			 * @see mshop/common/manager/newid/ansi
1041
			 * @see mshop/common/manager/delete/ansi
1042
			 * @see mshop/common/manager/search/ansi
1043
			 * @see mshop/common/manager/count/ansi
1044
			 */
1045
			$path = $this->getConfigKey( 'insert' );
1046
			$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

1046
			$sql = $this->addSqlColumns( array_keys( $columns ), /** @scrutinizer ignore-type */ $this->getSqlConfig( $path ) );
Loading history...
1047
		}
1048
		else
1049
		{
1050
			/** mshop/common/manager/update/mysql
1051
			 * Updates an existing record in the database
1052
			 *
1053
			 * @see mshop/common/manager/update/ansi
1054
			 */
1055
1056
			/** mshop/common/manager/update/ansi
1057
			 * Updates an existing record in the database
1058
			 *
1059
			 * Items which already have an ID (i.e. the ID is not NULL) will
1060
			 * be updated in the database.
1061
			 *
1062
			 * The SQL statement must be a string suitable for being used as
1063
			 * prepared statement. It must include question marks for binding
1064
			 * the values from the item to the statement before they are
1065
			 * sent to the database server. The order of the columns must
1066
			 * correspond to the order in the save() method, so the
1067
			 * correct values are bound to the columns.
1068
			 *
1069
			 * The SQL statement should conform to the ANSI standard to be
1070
			 * compatible with most relational database systems. This also
1071
			 * includes using double quotes for table and column names.
1072
			 *
1073
			 * @param string SQL statement for updating records
1074
			 * @since 2023.10
1075
			 * @see mshop/common/manager/insert/ansi
1076
			 * @see mshop/common/manager/newid/ansi
1077
			 * @see mshop/common/manager/delete/ansi
1078
			 * @see mshop/common/manager/search/ansi
1079
			 * @see mshop/common/manager/count/ansi
1080
			 */
1081
			$path = $this->getConfigKey( 'update' );
1082
			$sql = $this->addSqlColumns( array_keys( $columns ), $this->getSqlConfig( $path ), false );
1083
		}
1084
1085
		$idx = 1;
1086
		$values = $item->toArray( true );
1087
		$stmt = $this->getCachedStatement( $conn, $path, $sql );
1088
1089
		foreach( $columns as $entry )
1090
		{
1091
			$value = $values[$entry->getCode()] ?? null;
1092
			$value = $entry->getType() === 'json' ? json_encode( $value, JSON_FORCE_OBJECT ) : $value;
1093
			$stmt->bind( $idx++, $value, \Aimeos\Base\Criteria\SQL::type( $entry->getType() ) );
1094
		}
1095
1096
		$stmt->bind( $idx++, $context->datetime() ); // mtime
1097
		$stmt->bind( $idx++, $context->editor() );
1098
1099
		if( $id !== null ) {
1100
			$stmt->bind( $idx++, $context->locale()->getSiteId() . '%' );
1101
			$stmt->bind( $idx++, $id, \Aimeos\Base\DB\Statement\Base::PARAM_INT );
1102
		} else {
1103
			$stmt->bind( $idx++, $this->siteId( $item->getSiteId(), \Aimeos\MShop\Locale\Manager\Base::SITE_SUBTREE ) );
1104
			$stmt->bind( $idx++, $context->datetime() ); // ctime
1105
		}
1106
1107
		$stmt->execute()->finish();
1108
1109
		if( $id === null )
1110
		{
1111
			/** mshop/common/manager/newid/mysql
1112
			 * Retrieves the ID generated by the database when inserting a new record
1113
			 *
1114
			 * @see mshop/common/manager/newid/ansi
1115
			 */
1116
1117
			/** mshop/common/manager/newid/ansi
1118
			 * Retrieves the ID generated by the database when inserting a new record
1119
			 *
1120
			 * As soon as a new record is inserted into the database table,
1121
			 * the database server generates a new and unique identifier for
1122
			 * that record. This ID can be used for retrieving, updating and
1123
			 * deleting that specific record from the table again.
1124
			 *
1125
			 * For MySQL:
1126
			 *  SELECT LAST_INSERT_ID()
1127
			 * For PostgreSQL:
1128
			 *  SELECT currval('seq_matt_id')
1129
			 * For SQL Server:
1130
			 *  SELECT SCOPE_IDENTITY()
1131
			 * For Oracle:
1132
			 *  SELECT "seq_matt_id".CURRVAL FROM DUAL
1133
			 *
1134
			 * There's no way to retrive the new ID by a SQL statements that
1135
			 * fits for most database servers as they implement their own
1136
			 * specific way.
1137
			 *
1138
			 * @param string SQL statement for retrieving the last inserted record ID
1139
			 * @since 2023.10
1140
			 * @see mshop/common/manager/insert/ansi
1141
			 * @see mshop/common/manager/update/ansi
1142
			 * @see mshop/common/manager/delete/ansi
1143
			 * @see mshop/common/manager/search/ansi
1144
			 * @see mshop/common/manager/count/ansi
1145
			 */
1146
			$id = $this->newId( $conn, 'mshop/common/manager/newid' );
1147
		}
1148
1149
		return $item->setId( $id );
1150
	}
1151
1152
1153
	/**
1154
	 * Replaces the given marker with an expression
1155
	 *
1156
	 * @param string $column Name (including alias) of the column
1157
	 * @param mixed $value Value used in the expression
1158
	 * @param string $op Operator used in the expression
1159
	 * @param int $type Type constant from \Aimeos\Base\DB\Statement\Base class
1160
	 * @return string Created expression
1161
	 */
1162
	protected function toExpression( string $column, $value, string $op = '==',
1163
		int $type = \Aimeos\Base\DB\Statement\Base::PARAM_STR ) : string
1164
	{
1165
		$types = ['marker' => $type];
1166
		$translations = ['marker' => $column];
1167
		$value = ( is_array( $value ) ? array_unique( $value ) : $value );
1168
1169
		return $this->getSearch()->compare( $op, 'marker', $value )->toSource( $types, $translations );
1170
	}
1171
1172
1173
	/**
1174
	 * Transforms the application specific values to Aimeos standard values.
1175
	 *
1176
	 * @param array $values Associative list of key/value pairs from the storage
1177
	 * @return array Associative list of key/value pairs with standard Aimeos values
1178
	 */
1179
	protected function transform( array $values ) : array
1180
	{
1181
		return $values;
1182
	}
1183
1184
1185
	/**
1186
	 * Cuts the last part separated by a dot repeatedly and returns the list of resulting string.
1187
	 *
1188
	 * @param string[] $prefix Required base prefixes of the search keys
1189
	 * @param string $string String containing parts separated by dots
1190
	 * @return array List of resulting strings
1191
	 */
1192
	private function cutNameTail( array $prefix, string $string ) : array
1193
	{
1194
		$result = [];
1195
		$noprefix = true;
1196
		$strlen = strlen( $string );
1197
1198
		foreach( $prefix as $key )
1199
		{
1200
			$len = strlen( $key );
1201
1202
			if( strncmp( $string, $key, $len ) === 0 )
1203
			{
1204
				if( $strlen > $len && ( $pos = strrpos( $string, '.' ) ) !== false )
1205
				{
1206
					$result[] = $string = substr( $string, 0, $pos );
1207
					$result = array_merge( $result, $this->cutNameTail( $prefix, $string ) );
1208
					$noprefix = false;
1209
				}
1210
1211
				break;
1212
			}
1213
		}
1214
1215
		if( $noprefix )
1216
		{
1217
			if( ( $pos = strrpos( $string, ':' ) ) !== false ) {
1218
				$result[] = substr( $string, 0, $pos );
1219
				$result[] = $string;
1220
			} elseif( ( $pos = strrpos( $string, '.' ) ) !== false ) {
1221
				$result[] = substr( $string, 0, $pos );
1222
			} else {
1223
				$result[] = $string;
1224
			}
1225
		}
1226
1227
		return $result;
1228
	}
1229
1230
1231
	/**
1232
	 * Returns a list of unique criteria names shortend by the last element after the ''
1233
	 *
1234
	 * @param string[] $prefix Required base prefixes of the search keys
1235
	 * @param \Aimeos\Base\Criteria\Expression\Iface|null $expr Criteria object
1236
	 * @return array List of shortend criteria names
1237
	 */
1238
	private function getCriteriaKeys( array $prefix, \Aimeos\Base\Criteria\Expression\Iface $expr = null ) : array
1239
	{
1240
		if( $expr === null ) { return []; }
1241
1242
		$result = [];
1243
1244
		foreach( $this->getCriteriaNames( $expr ) as $item )
1245
		{
1246
			if( strncmp( $item, 'sort:', 5 ) === 0 ) {
1247
				$item = substr( $item, 5 );
1248
			}
1249
1250
			if( ( $pos = strpos( $item, '(' ) ) !== false ) {
1251
				$item = substr( $item, 0, $pos );
1252
			}
1253
1254
			$result = array_merge( $result, $this->cutNameTail( $prefix, $item ) );
1255
		}
1256
1257
		return $result;
1258
	}
1259
1260
1261
	/**
1262
	 * Returns a list of criteria names from a expression and its sub-expressions.
1263
	 *
1264
	 * @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...
1265
	 * @return array List of criteria names
1266
	 */
1267
	private function getCriteriaNames( \Aimeos\Base\Criteria\Expression\Iface $expr ) : array
1268
	{
1269
		if( $expr instanceof \Aimeos\Base\Criteria\Expression\Compare\Iface ) {
1270
			return array( $expr->getName() );
1271
		}
1272
1273
		if( $expr instanceof \Aimeos\Base\Criteria\Expression\Combine\Iface )
1274
		{
1275
			$list = [];
1276
			foreach( $expr->getExpressions() as $item ) {
1277
				$list = array_merge( $list, $this->getCriteriaNames( $item ) );
1278
			}
1279
			return $list;
1280
		}
1281
1282
		if( $expr instanceof \Aimeos\Base\Criteria\Expression\Sort\Iface ) {
1283
			return array( $expr->getName() );
1284
		}
1285
1286
		return [];
1287
	}
1288
}
1289