Passed
Push — master ( ac2e3e...212e15 )
by Aimeos
05:15
created

Base::searchItemsBase()   B

Complexity

Conditions 6
Paths 18

Size

Total Lines 48
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 26
nc 18
nop 8
dl 0
loc 48
rs 8.8817
c 0
b 0
f 0

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), 2015-2023
6
 * @package MShop
7
 * @subpackage Common
8
 */
9
10
11
namespace Aimeos\MShop\Common\Manager;
12
13
14
/**
15
 * Provides common methods required by most of the manager classes.
16
 *
17
 * @package MShop
18
 * @subpackage Common
19
 */
20
abstract class Base implements \Aimeos\Macro\Iface
21
{
22
	use \Aimeos\Macro\Macroable;
23
	use Sub\Traits;
24
	use Methods;
25
	use Site;
26
27
28
	private \Aimeos\MShop\ContextIface $context;
29
	private ?\Aimeos\Base\Criteria\Iface $search;
30
	private array $stmts = [];
31
32
33
	/**
34
	 * Initialization of class.
35
	 *
36
	 * @param \Aimeos\MShop\ContextIface $context Context object
37
	 */
38
	public function __construct( \Aimeos\MShop\ContextIface $context )
39
	{
40
		$this->context = $context;
41
	}
42
43
44
	/**
45
	 * Adds additional column names to SQL statement
46
	 *
47
	 * @param string[] $columns List of column names
48
	 * @param string $sql Insert or update SQL statement
49
	 * @param bool $mode True for insert, false for update statement
50
	 * @return string Modified insert or update SQL statement
51
	 */
52
	protected function addSqlColumns( array $columns, string $sql, bool $mode = true ) : string
53
	{
54
		$names = $values = '';
55
56
		if( $mode )
57
		{
58
			foreach( $columns as $name ) {
59
				$names .= '"' . $name . '", '; $values .= '?, ';
60
			}
61
		}
62
		else
63
		{
64
			foreach( $columns as $name ) {
65
				$names .= '"' . $name . '" = ?, ';
66
			}
67
		}
68
69
		return str_replace( [':names', ':values'], [$names, $values], $sql );
70
	}
71
72
73
	/**
74
	 * Counts the number products that are available for the values of the given key.
75
	 *
76
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria
77
	 * @param array|string $keys Search key or list of keys for aggregation
78
	 * @param string $cfgPath Configuration key for the SQL statement
79
	 * @param string[] $required List of domain/sub-domain names like "catalog.index" that must be additionally joined
80
	 * @param string|null $value Search key for aggregating the value column
81
	 * @param string|null $type Type of aggregation, e.g.  "sum", "min", "max" or NULL for "count"
82
	 * @return \Aimeos\Map List of ID values as key and the number of counted products as value
83
	 * @todo 2018.01 Reorder Parameter list
84
	 */
85
	protected function aggregateBase( \Aimeos\Base\Criteria\Iface $search, $keys, string $cfgPath,
86
		array $required = [], string $value = null, string $type = null ) : \Aimeos\Map
87
	{
88
		/** mshop/common/manager/aggregate/limit
89
		 * Limits the number of records that are used when aggregating items
90
		 *
91
		 * As counting huge amount of records (several 10 000 records) takes a long time,
92
		 * the limit can cut down response times so the counts are available more quickly
93
		 * in the front-end and the server load is reduced.
94
		 *
95
		 * Using a low limit can lead to incorrect numbers if the amount of found items
96
		 * is very high. Approximate item counts are normally not a problem but it can
97
		 * lead to the situation that visitors see that no items are available despite
98
		 * the fact that there would be at least one.
99
		 *
100
		 * @param integer Number of records
101
		 * @since 2021.04
102
		 */
103
		$limit = $this->context->config()->get( 'mshop/common/manager/aggregate/limit', 10000 );
104
		$keys = (array) $keys;
105
106
		if( !count( $keys ) )
107
		{
108
			$msg = $this->context()->translate( 'mshop', 'At least one key is required for aggregation' );
109
			throw new \Aimeos\MShop\Exception( $msg );
110
		}
111
112
		$conn = $this->context->db( $this->getResourceName() );
113
114
		$total = null;
115
		$cols = $map = [];
116
		$search = clone $search;
117
		$search->slice( $search->getOffset(), min( $search->getLimit(), $limit ) );
118
119
		$level = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL;
120
		$attrList = array_filter( $this->object()->getSearchAttributes(), function( $item ) {
121
			return $item->isPublic() || strncmp( $item->getCode(), 'agg:', 4 ) === 0;
122
		} );
123
124
		if( $value === null && ( $value = key( $attrList ) ) === null )
125
		{
126
			$msg = $this->context()->translate( 'mshop', 'No search keys available' );
127
			throw new \Aimeos\MShop\Exception( $msg );
128
		}
129
130
		if( ( $pos = strpos( $valkey = $value, '(' ) ) !== false ) {
131
			$value = substr( $value, 0, $pos );
132
		}
133
134
		if( !isset( $attrList[$value] ) )
135
		{
136
			$msg = $this->context()->translate( 'mshop', 'Unknown search key "%1$s"' );
137
			throw new \Aimeos\MShop\Exception( sprintf( $msg, $value ) );
138
		}
139
140
		foreach( $keys as $string )
141
		{
142
			if( !isset( $attrList[$string] ) )
143
			{
144
				$msg = $this->context()->translate( 'mshop', 'Unknown search key "%1$s"' );
145
				throw new \Aimeos\MShop\Exception( sprintf( $msg, $string ) );
146
			}
147
148
			$cols[] = $attrList[$string]->getInternalCode();
149
			$acols[] = $attrList[$string]->getInternalCode() . ' AS "' . $string . '"';
150
151
			/** @todo Required to get the joins, but there should be a better way */
152
			$search->add( [$string => null], '!=' );
153
		}
154
		$search->add( [$valkey => null], '!=' );
155
156
		$sql = $this->getSqlConfig( $cfgPath );
157
		$sql = str_replace( ':cols', join( ', ', $cols ), $sql );
158
		$sql = str_replace( ':acols', join( ', ', $acols ), $sql );
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $acols seems to be defined by a foreach iteration on line 140. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
159
		$sql = str_replace( ':keys', '"' . join( '", "', $keys ) . '"', $sql );
160
		$sql = str_replace( ':val', $attrList[$value]->getInternalCode(), $sql );
161
		$sql = str_replace( ':type', in_array( $type, ['avg', 'count', 'max', 'min', 'sum'] ) ? $type : 'count', $sql );
162
163
		$results = $this->searchItemsBase( $conn, $search, $sql, '', $required, $total, $level );
164
165
		while( ( $row = $results->fetch() ) !== null )
166
		{
167
			$row = $this->transform( $row );
168
169
			$temp = &$map;
170
			$last = array_pop( $row );
171
172
			foreach( $row as $val ) {
173
				$temp[$val] = $temp[$val] ?? [];
174
				$temp = &$temp[$val];
175
			}
176
			$temp = $last;
177
		}
178
179
		return map( $map );
180
	}
181
182
183
	/**
184
	 * Removes old entries from the storage.
185
	 *
186
	 * @param iterable $siteids List of IDs for sites whose entries should be deleted
187
	 * @param string $cfgpath Configuration key to the cleanup statement
188
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
189
	 */
190
	protected function clearBase( iterable $siteids, string $cfgpath ) : \Aimeos\MShop\Common\Manager\Iface
191
	{
192
		if( empty( $siteids ) ) {
193
			return $this;
194
		}
195
196
		$conn = $this->context->db( $this->getResourceName() );
197
198
		$sql = $this->getSqlConfig( $cfgpath );
199
		$sql = str_replace( ':cond', '1=1', $sql );
200
201
		$stmt = $conn->create( $sql );
202
203
		foreach( $siteids as $siteid )
204
		{
205
			$stmt->bind( 1, $siteid );
206
			$stmt->execute()->finish();
207
		}
208
209
		return $this;
210
	}
211
212
213
	/**
214
	 * Returns the context object.
215
	 *
216
	 * @return \Aimeos\MShop\ContextIface Context object
217
	 */
218
	protected function context() : \Aimeos\MShop\ContextIface
219
	{
220
		return $this->context;
221
	}
222
223
224
	/**
225
	 * Creates the criteria attribute items from the list of entries
226
	 *
227
	 * @param array $list Associative array of code as key and array with properties as values
228
	 * @return \Aimeos\Base\Criteria\Attribute\Standard[] List of criteria attribute items
229
	 */
230
	protected function createAttributes( array $list ) : array
231
	{
232
		$attr = [];
233
234
		foreach( $list as $key => $fields ) {
235
			$attr[$key] = new \Aimeos\Base\Criteria\Attribute\Standard( $fields );
236
		}
237
238
		return $attr;
239
	}
240
241
242
	/**
243
	 * Deletes items.
244
	 *
245
	 * @param \Aimeos\MShop\Common\Item\Iface|\Aimeos\Map|array|string $items List of item objects or IDs of the items
246
	 * @param string $cfgpath Configuration path to the SQL statement
247
	 * @param bool $siteid If siteid should be used in the statement
248
	 * @param string $name Name of the ID column
249
	 * @return \Aimeos\MShop\Common\Manager\Iface Manager object for chaining method calls
250
	 */
251
	protected function deleteItemsBase( $items, string $cfgpath, bool $siteid = true,
252
		string $name = 'id' ) : \Aimeos\MShop\Common\Manager\Iface
253
	{
254
		if( map( $items )->isEmpty() ) {
255
			return $this;
256
		}
257
258
		$search = $this->object()->filter();
259
		$search->setConditions( $search->compare( '==', $name, $items ) );
260
261
		$types = array( $name => \Aimeos\Base\DB\Statement\Base::PARAM_STR );
262
		$translations = array( $name => '"' . $name . '"' );
263
264
		$cond = $search->getConditionSource( $types, $translations );
265
		$sql = str_replace( ':cond', $cond, $this->getSqlConfig( $cfgpath ) );
266
267
		$context = $this->context();
268
		$conn = $context->db( $this->getResourceName() );
269
270
		$stmt = $conn->create( $sql );
271
272
		if( $siteid ) {
273
			$stmt->bind( 1, $context->locale()->getSiteId() . '%' );
274
		}
275
276
		$stmt->execute()->finish();
277
278
		return $this;
279
	}
280
281
282
	/**
283
	 * Returns a sorted list of required criteria keys.
284
	 *
285
	 * @param \Aimeos\Base\Criteria\Iface $criteria Search criteria object
286
	 * @param string[] $required List of prefixes of required search conditions
287
	 * @return string[] Sorted list of criteria keys
288
	 */
289
	protected function getCriteriaKeyList( \Aimeos\Base\Criteria\Iface $criteria, array $required ) : array
290
	{
291
		$keys = array_merge( $required, $this->getCriteriaKeys( $required, $criteria->getConditions() ) );
292
293
		foreach( $criteria->getSortations() as $sortation ) {
294
			$keys = array_merge( $keys, $this->getCriteriaKeys( $required, $sortation ) );
295
		}
296
297
		$keys = array_unique( array_merge( $required, $keys ) );
298
		sort( $keys );
299
300
		return $keys;
301
	}
302
303
304
	/**
305
	 * Returns the search attribute objects used for searching.
306
	 *
307
	 * @param array $list Associative list of search keys and the lists of search definitions
308
	 * @param string $path Configuration path to the sub-domains for fetching the search definitions
309
	 * @param string[] $default List of sub-domains if no others are configured
310
	 * @param bool $withsub True to include search definitions of sub-domains, false if not
311
	 * @return \Aimeos\Base\Criteria\Attribute\Iface[] Associative list of search keys and criteria attribute items as values
312
	 * @since 2014.09
313
	 */
314
	protected function getSearchAttributesBase( array $list, string $path, array $default, bool $withsub ) : array
315
	{
316
		$attr = $this->createAttributes( $list );
317
318
		if( $withsub === true )
319
		{
320
			$domains = $this->context->config()->get( $path, $default );
321
322
			foreach( $domains as $domain ) {
323
				$attr += $this->object()->getSubManager( $domain )->getSearchAttributes( true );
324
			}
325
		}
326
327
		return $attr;
328
	}
329
330
331
	/**
332
	 * Returns the attribute helper functions for searching defined by the manager.
333
	 *
334
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of search attribute items
335
	 * @return array Associative array of attribute code and helper function
336
	 */
337
	protected function getSearchFunctions( array $attributes ) : array
338
	{
339
		$list = [];
340
		$iface = \Aimeos\Base\Criteria\Attribute\Iface::class;
341
342
		foreach( $attributes as $key => $item )
343
		{
344
			if( $item instanceof $iface ) {
345
				$list[$item->getCode()] = $item->getFunction();
346
			} else if( isset( $item['code'] ) ) {
347
				$list[$item['code']] = $item['function'] ?? null;
348
			} else {
349
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid attribute at position "%1$d"', $key ) );
350
			}
351
		}
352
353
		return $list;
354
	}
355
356
357
	/**
358
	 * Returns the search results for the given SQL statement.
359
	 *
360
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
361
	 * @param string $sql SQL statement
362
	 * @return \Aimeos\Base\DB\Result\Iface Search result object
363
	 */
364
	protected function getSearchResults( \Aimeos\Base\DB\Connection\Iface $conn, string $sql ) : \Aimeos\Base\DB\Result\Iface
365
	{
366
		$time = microtime( true );
367
368
		$stmt = $conn->create( $sql );
369
		$result = $stmt->execute();
370
371
		$level = \Aimeos\Base\Logger\Iface::DEBUG;
372
		$time = ( microtime( true ) - $time ) * 1000;
373
		$msg = 'Time: ' . $time . "ms\n"
374
			. 'Class: ' . get_class( $this ) . "\n"
375
			. str_replace( ["\t", "\n\n"], ['', "\n"], trim( (string) $stmt ) );
376
377
		if( $time > 1000.0 )
378
		{
379
			$level = \Aimeos\Base\Logger\Iface::NOTICE;
380
			$msg .= "\n" . ( new \Exception() )->getTraceAsString();
381
		}
382
383
		$this->context->logger()->log( $msg, $level, 'core/sql' );
384
385
		return $result;
386
	}
387
388
389
	/**
390
	 * Returns the attribute translations for searching defined by the manager.
391
	 *
392
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of search attribute items
393
	 * @return array Associative array of attribute code and internal attribute code
394
	 */
395
	protected function getSearchTranslations( array $attributes ) : array
396
	{
397
		$translations = [];
398
		$iface = \Aimeos\Base\Criteria\Attribute\Iface::class;
399
400
		foreach( $attributes as $key => $item )
401
		{
402
			if( $item instanceof $iface ) {
403
				$translations[$item->getCode()] = $item->getInternalCode();
404
			} else if( isset( $item['code'] ) ) {
405
				$translations[$item['code']] = $item['internalcode'];
406
			} else {
407
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid attribute at position "%1$d"', $key ) );
408
			}
409
		}
410
411
		return $translations;
412
	}
413
414
415
	/**
416
	 * Returns the attribute types for searching defined by the manager.
417
	 *
418
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of search attribute items
419
	 * @return array Associative array of attribute code and internal attribute type
420
	 */
421
	protected function getSearchTypes( array $attributes ) : array
422
	{
423
		$types = [];
424
		$iface = \Aimeos\Base\Criteria\Attribute\Iface::class;
425
426
		foreach( $attributes as $key => $item )
427
		{
428
			if( $item instanceof $iface ) {
429
				$types[$item->getCode()] = $item->getInternalType();
430
			} else if( isset( $item['code'] ) ) {
431
				$types[$item['code']] = $item['internaltype'];
432
			} else {
433
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid attribute at position "%1$d"', $key ) );
434
			}
435
		}
436
437
		return $types;
438
	}
439
440
441
	/**
442
	 * Returns the SQL statement for the given config path
443
	 *
444
	 * If available, the database specific SQL statement is returned, otherwise
445
	 * the ANSI SQL statement. The database type is determined via the resource
446
	 * adapter.
447
	 *
448
	 * @param string $path Configuration path to the SQL statement
449
	 * @return array|string ANSI or database specific SQL statement
450
	 */
451
	protected function getSqlConfig( string $path )
452
	{
453
		$config = $this->context()->config();
454
		$adapter = $config->get( 'resource/' . $this->getResourceName() . '/adapter' );
455
456
		return $config->get( $path . '/' . $adapter, $config->get( $path . '/ansi', $path ) );
457
	}
458
459
460
	/**
461
	 * Sets the base criteria "status".
462
	 * (setConditions overwrites the base criteria)
463
	 *
464
	 * @param string $domain Name of the domain/sub-domain like "product" or "product.list"
465
	 * @param bool|null $default TRUE for status=1, NULL for status>0, FALSE for no restriction
466
	 * @return \Aimeos\Base\Criteria\Iface Search critery object
467
	 */
468
	protected function filterBase( string $domain, ?bool $default = false ) : \Aimeos\Base\Criteria\Iface
469
	{
470
		$filter = self::filter();
0 ignored issues
show
Bug Best Practice introduced by
The method Aimeos\MShop\Common\Manager\Base::filter() is not static, but was called statically. ( Ignorable by Annotation )

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

470
		/** @scrutinizer ignore-call */ 
471
  $filter = self::filter();
Loading history...
471
472
		if( $default !== false ) {
473
			$filter->add( $domain . '.status', $default ? '==' : '>=', 1 );
474
		}
475
476
		return $filter;
477
	}
478
479
480
	/**
481
	 * Returns the item for the given search key/value pairs.
482
	 *
483
	 * @param array $pairs Search key/value pairs for the item
484
	 * @param string[] $ref List of domains whose items should be fetched too
485
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
486
	 * @return \Aimeos\MShop\Common\Item\Iface Requested item
487
	 * @throws \Aimeos\MShop\Exception if no item with the given ID found
488
	 */
489
	protected function findBase( array $pairs, array $ref, ?bool $default ) : \Aimeos\MShop\Common\Item\Iface
490
	{
491
		$expr = [];
492
		$criteria = $this->object()->filter( $default )->slice( 0, 1 );
493
494
		foreach( $pairs as $key => $value )
495
		{
496
			if( $value === null )
497
			{
498
				$msg = $this->context()->translate( 'mshop', 'Required value for "%1$s" is missing' );
499
				throw new \Aimeos\MShop\Exception( sprintf( $msg, $key ) );
500
			}
501
			$expr[] = $criteria->compare( '==', $key, $value );
502
		}
503
504
		$criteria->setConditions( $criteria->and( $expr ) );
505
506
		if( ( $item = $this->object()->search( $criteria, $ref )->first() ) ) {
507
			return $item;
508
		}
509
510
		$msg = $this->context()->translate( 'mshop', 'No item found for conditions: %1$s' );
511
		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

511
		throw new \Aimeos\MShop\Exception( sprintf( $msg, /** @scrutinizer ignore-type */ print_r( $pairs, true ) ), 404 );
Loading history...
512
	}
513
514
515
	/**
516
	 * Returns the cached statement for the given key or creates a new prepared statement.
517
	 * If no SQL string is given, the key is used to retrieve the SQL string from the configuration.
518
	 *
519
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
520
	 * @param string $cfgkey Unique key for the SQL
521
	 * @param string|null $sql SQL string if it shouldn't be retrieved from the configuration
522
	 * @return \Aimeos\Base\DB\Statement\Iface Database statement object
523
	 */
524
	protected function getCachedStatement( \Aimeos\Base\DB\Connection\Iface $conn, string $cfgkey,
525
		string $sql = null ) : \Aimeos\Base\DB\Statement\Iface
526
	{
527
		if( !isset( $this->stmts['stmt'][$cfgkey] )
528
			|| !isset( $this->stmts['conn'][$cfgkey] )
529
			|| $conn !== $this->stmts['conn'][$cfgkey]
530
		) {
531
			if( $sql === null ) {
532
				$sql = $this->getSqlConfig( $cfgkey );
533
			}
534
535
			$this->stmts['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

535
			$this->stmts['stmt'][$cfgkey] = $conn->create( /** @scrutinizer ignore-type */ $sql );
Loading history...
536
			$this->stmts['conn'][$cfgkey] = $conn;
537
		}
538
539
		return $this->stmts['stmt'][$cfgkey];
540
	}
541
542
543
	/**
544
	 * Returns the item for the given search key and ID.
545
	 *
546
	 * @param string $key Search key for the requested ID
547
	 * @param string $id Unique ID to search for
548
	 * @param string[] $ref List of domains whose items should be fetched too
549
	 * @param bool|null $default Add default criteria or NULL for relaxed default criteria
550
	 * @return \Aimeos\MShop\Common\Item\Iface Requested item
551
	 * @throws \Aimeos\MShop\Exception if no item with the given ID found
552
	 */
553
	protected function getItemBase( string $key, string $id, array $ref, ?bool $default ) : \Aimeos\MShop\Common\Item\Iface
554
	{
555
		$criteria = $this->object()->filter( $default )->add( [$key => $id] )->slice( 0, 1 );
556
557
		if( ( $item = $this->object()->search( $criteria, $ref )->first() ) ) {
558
			return $item;
559
		}
560
561
		$msg = $this->context()->translate( 'mshop', 'Item with ID "%2$s" in "%1$s" not found' );
562
		throw new \Aimeos\MShop\Exception( sprintf( $msg, $key, $id ), 404 );
563
	}
564
565
566
	/**
567
	 * Returns the SQL strings for joining dependent tables.
568
	 *
569
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes List of criteria attribute items
570
	 * @param string $prefix Search key prefix
571
	 * @return array List of JOIN SQL strings
572
	 */
573
	private function getJoins( array $attributes, string $prefix ) : array
574
	{
575
		$iface = \Aimeos\Base\Criteria\Attribute\Iface::class;
576
		$name = $prefix . '.id';
577
578
		if( isset( $attributes[$prefix] ) && $attributes[$prefix] instanceof $iface ) {
579
			return $attributes[$prefix]->getInternalDeps();
580
		}
581
		elseif( isset( $attributes[$name] ) && $attributes[$name] instanceof $iface ) {
582
			return $attributes[$name]->getInternalDeps();
583
		}
584
		else if( isset( $attributes['id'] ) && $attributes['id'] instanceof $iface ) {
585
			return $attributes['id']->getInternalDeps();
586
		}
587
588
		return [];
589
	}
590
591
592
	/**
593
	 * Returns the available manager types
594
	 *
595
	 * @param string $type Main manager type
596
	 * @param string $path Configuration path to the sub-domains
597
	 * @param string[] $default List of sub-domains if no others are configured
598
	 * @param bool $withsub Return also the resource type of sub-managers if true
599
	 * @return string[] Type of the manager and submanagers, subtypes are separated by slashes
600
	 */
601
	protected function getResourceTypeBase( string $type, string $path, array $default, bool $withsub ) : array
602
	{
603
		$list = [$type];
604
605
		if( $withsub )
606
		{
607
			foreach( $this->context->config()->get( $path, $default ) as $domain ) {
608
				$list = array_merge( $list, $this->object()->getSubManager( $domain )->getResourceType( $withsub ) );
609
			}
610
		}
611
612
		return $list;
613
	}
614
615
616
	/**
617
	 * Returns a search object singleton
618
	 *
619
	 * @return \Aimeos\Base\Criteria\Iface Search object
620
	 */
621
	protected function getSearch() : \Aimeos\Base\Criteria\Iface
622
	{
623
		if( !isset( $this->search ) ) {
624
			$this->search = $this->filter();
625
		}
626
627
		return $this->search;
628
	}
629
630
631
	/**
632
	 * Returns the site coditions for the search request
633
	 *
634
	 * @param string[] $keys Sorted list of criteria keys
635
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items as values
636
	 * @param int $sitelevel Site level constant from \Aimeos\MShop\Locale\Manager\Base
637
	 * @return \Aimeos\Base\Criteria\Expression\Iface[] List of search conditions
638
	 * @since 2015.01
639
	 */
640
	protected function getSiteConditions( array $keys, array $attributes, int $sitelevel ) : array
641
	{
642
		$list = [];
643
644
		foreach( $keys as $key )
645
		{
646
			$name = $key . '.siteid';
647
648
			if( isset( $attributes[$name] ) ) {
649
				$list[] = $this->siteCondition( $name, $sitelevel );
650
			}
651
		}
652
653
		return $list;
654
	}
655
656
657
	/**
658
	 * Returns the string replacements for the SQL statements
659
	 *
660
	 * @param \Aimeos\Base\Criteria\Iface $search Search critera object
661
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $attributes Associative list of search keys and criteria attribute items as values
662
	 * @param \Aimeos\Base\Criteria\Plugin\Iface[] $plugins Associative list of search keys and criteria plugin items as values
663
	 * @param string[] $joins Associative list of SQL joins
664
	 * @param \Aimeos\Base\Criteria\Attribute\Iface[] $columns Additional columns to retrieve values from
665
	 * @return array Array of keys, find and replace arrays
666
	 */
667
	protected function getSQLReplacements( \Aimeos\Base\Criteria\Iface $search, array $attributes, array $plugins,
668
		array $joins, array $columns = [] ) : array
669
	{
670
		$types = $this->getSearchTypes( $attributes );
671
		$funcs = $this->getSearchFunctions( $attributes );
672
		$translations = $this->getSearchTranslations( $attributes );
673
674
		$colstring = '';
675
		foreach( $columns as $name => $entry ) {
676
			$colstring .= $entry->getInternalCode() . ', ';
677
		}
678
679
		$find = array( ':columns', ':joins', ':cond', ':start', ':size' );
680
		$replace = array(
681
			$colstring,
682
			implode( "\n", array_unique( $joins ) ),
683
			$search->getConditionSource( $types, $translations, $plugins, $funcs ),
684
			$search->getOffset(),
685
			$search->getLimit(),
686
		);
687
688
		if( empty( $search->getSortations() ) && ( $attribute = reset( $attributes ) ) !== false ) {
689
			$search = ( clone $search )->setSortations( [$search->sort( '+', $attribute->getCode() )] );
690
		}
691
692
		$find[] = ':order';
693
		$replace[] = $search->getSortationSource( $types, $translations, $funcs );
694
695
		$find[] = ':group';
696
		$replace[] = implode( ', ', $search->translate( $search->getSortations(), $translations, $funcs ) ) . ', ';
697
698
		return [$find, $replace];
699
	}
700
701
702
	/**
703
	 * Returns the newly created ID for the last record which was inserted.
704
	 *
705
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection used to insert the new record
706
	 * @param string $cfgpath Configuration path to the SQL statement for retrieving the new ID of the last inserted record
707
	 * @return string ID of the last record that was inserted by using the given connection
708
	 * @throws \Aimeos\MShop\Exception if there's no ID of the last record available
709
	 */
710
	protected function newId( \Aimeos\Base\DB\Connection\Iface $conn, string $cfgpath ) : string
711
	{
712
		$result = $conn->create( $this->getSqlConfig( $cfgpath ) )->execute();
0 ignored issues
show
Bug introduced by
It seems like $this->getSqlConfig($cfgpath) 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

712
		$result = $conn->create( /** @scrutinizer ignore-type */ $this->getSqlConfig( $cfgpath ) )->execute();
Loading history...
713
714
		if( ( $row = $result->fetch( \Aimeos\Base\DB\Result\Base::FETCH_NUM ) ) === false )
715
		{
716
			$msg = $this->context()->translate( 'mshop', 'ID of last inserted database record not available' );
717
			throw new \Aimeos\MShop\Exception( $msg );
718
		}
719
		$result->finish();
720
721
		return $row[0];
722
	}
723
724
725
	/**
726
	 * Returns the search result of the statement combined with the given criteria.
727
	 *
728
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection
729
	 * @param \Aimeos\Base\Criteria\Iface $search Search criteria object
730
	 * @param string $cfgPathSearch Path to SQL statement in configuration for searching
731
	 * @param string $cfgPathCount Path to SQL statement in configuration for counting
732
	 * @param string[] $required Additional search keys to add conditions for even if no conditions are available
733
	 * @param int|null $total Contains the number of all records matching the criteria if not null
734
	 * @param int $sitelevel Constant from \Aimeos\MShop\Locale\Manager\Base for defining which site IDs should be used for searching
735
	 * @param \Aimeos\Base\Criteria\Plugin\Iface[] $plugins Associative list of search keys and criteria plugin items as values
736
	 * @return \Aimeos\Base\DB\Result\Iface SQL result object for accessing the found records
737
	 * @throws \Aimeos\MShop\Exception if no number of all matching records is available
738
	 */
739
	protected function searchItemsBase( \Aimeos\Base\DB\Connection\Iface $conn, \Aimeos\Base\Criteria\Iface $search,
740
		string $cfgPathSearch, string $cfgPathCount, array $required, int &$total = null,
741
		int $sitelevel = \Aimeos\MShop\Locale\Manager\Base::SITE_ALL, array $plugins = [] ) : \Aimeos\Base\DB\Result\Iface
742
	{
743
		$joins = [];
744
		$conditions = $search->getConditions();
745
		$columns = $this->object()->getSaveAttributes();
746
		$attributes = $this->object()->getSearchAttributes();
747
		$keys = $this->getCriteriaKeyList( $search, $required );
748
749
		$basekey = array_shift( $required );
750
751
		foreach( $keys as $key )
752
		{
753
			if( $key !== $basekey ) {
754
				$joins = array_merge( $joins, $this->getJoins( $attributes, $key ) );
755
			}
756
		}
757
758
		$joins = array_unique( $joins );
759
		$cond = $this->getSiteConditions( $keys, $attributes, $sitelevel );
760
761
		if( $conditions !== null ) {
762
			$cond[] = $conditions;
763
		}
764
765
		$search = clone $search;
766
		$search->setConditions( $search->and( $cond ) );
767
768
		list( $find, $replace ) = $this->getSQLReplacements( $search, $attributes, $plugins, $joins, $columns );
769
770
		if( $total !== null )
771
		{
772
			$sql = str_replace( $find, $replace, $this->getSqlConfig( $cfgPathCount ) );
773
			$result = $this->getSearchResults( $conn, $sql );
774
			$row = $result->fetch();
775
			$result->finish();
776
777
			if( $row === null )
778
			{
779
				$msg = $this->context()->translate( 'mshop', 'Total results value not found' );
780
				throw new \Aimeos\MShop\Exception( $msg );
781
			}
782
783
			$total = (int) $row['count'];
784
		}
785
786
		return $this->getSearchResults( $conn, str_replace( $find, $replace, $this->getSqlConfig( $cfgPathSearch ) ) );
787
	}
788
789
790
	/**
791
	 * Replaces the given marker with an expression
792
	 *
793
	 * @param string $column Name (including alias) of the column
794
	 * @param mixed $value Value used in the expression
795
	 * @param string $op Operator used in the expression
796
	 * @param int $type Type constant from \Aimeos\Base\DB\Statement\Base class
797
	 * @return string Created expression
798
	 */
799
	protected function toExpression( string $column, $value, string $op = '==',
800
		int $type = \Aimeos\Base\DB\Statement\Base::PARAM_STR ) : string
801
	{
802
		$types = ['marker' => $type];
803
		$translations = ['marker' => $column];
804
		$value = ( is_array( $value ) ? array_unique( $value ) : $value );
805
806
		return $this->getSearch()->compare( $op, 'marker', $value )->toSource( $types, $translations );
807
	}
808
809
810
	/**
811
	 * Transforms the application specific values to Aimeos standard values.
812
	 *
813
	 * @param array $values Associative list of key/value pairs from the storage
814
	 * @return array Associative list of key/value pairs with standard Aimeos values
815
	 */
816
	protected function transform( array $values ) : array
817
	{
818
		return $values;
819
	}
820
821
822
	/**
823
	 * Cuts the last part separated by a dot repeatedly and returns the list of resulting string.
824
	 *
825
	 * @param string[] $prefix Required base prefixes of the search keys
826
	 * @param string $string String containing parts separated by dots
827
	 * @return array List of resulting strings
828
	 */
829
	private function cutNameTail( array $prefix, string $string ) : array
830
	{
831
		$result = [];
832
		$noprefix = true;
833
		$strlen = strlen( $string );
834
835
		foreach( $prefix as $key )
836
		{
837
			$len = strlen( $key );
838
839
			if( strncmp( $string, $key, $len ) === 0 )
840
			{
841
				if( $strlen > $len && ( $pos = strrpos( $string, '.' ) ) !== false )
842
				{
843
					$result[] = $string = substr( $string, 0, $pos );
844
					$result = array_merge( $result, $this->cutNameTail( $prefix, $string ) );
845
					$noprefix = false;
846
				}
847
848
				break;
849
			}
850
		}
851
852
		if( $noprefix )
853
		{
854
			if( ( $pos = strrpos( $string, ':' ) ) !== false ) {
855
				$result[] = substr( $string, 0, $pos );
856
				$result[] = $string;
857
			} elseif( ( $pos = strrpos( $string, '.' ) ) !== false ) {
858
				$result[] = substr( $string, 0, $pos );
859
			} else {
860
				$result[] = $string;
861
			}
862
		}
863
864
		return $result;
865
	}
866
867
868
	/**
869
	 * Returns a list of unique criteria names shortend by the last element after the ''
870
	 *
871
	 * @param string[] $prefix Required base prefixes of the search keys
872
	 * @param \Aimeos\Base\Criteria\Expression\Iface|null $expr Criteria object
873
	 * @return array List of shortend criteria names
874
	 */
875
	private function getCriteriaKeys( array $prefix, \Aimeos\Base\Criteria\Expression\Iface $expr = null ) : array
876
	{
877
		if( $expr === null ) { return []; }
878
879
		$result = [];
880
881
		foreach( $this->getCriteriaNames( $expr ) as $item )
882
		{
883
			if( strncmp( $item, 'sort:', 5 ) === 0 ) {
884
				$item = substr( $item, 5 );
885
			}
886
887
			if( ( $pos = strpos( $item, '(' ) ) !== false ) {
888
				$item = substr( $item, 0, $pos );
889
			}
890
891
			$result = array_merge( $result, $this->cutNameTail( $prefix, $item ) );
892
		}
893
894
		return $result;
895
	}
896
897
898
	/**
899
	 * Returns a list of criteria names from a expression and its sub-expressions.
900
	 *
901
	 * @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...
902
	 * @return array List of criteria names
903
	 */
904
	private function getCriteriaNames( \Aimeos\Base\Criteria\Expression\Iface $expr ) : array
905
	{
906
		if( $expr instanceof \Aimeos\Base\Criteria\Expression\Compare\Iface ) {
907
			return array( $expr->getName() );
908
		}
909
910
		if( $expr instanceof \Aimeos\Base\Criteria\Expression\Combine\Iface )
911
		{
912
			$list = [];
913
			foreach( $expr->getExpressions() as $item ) {
914
				$list = array_merge( $list, $this->getCriteriaNames( $item ) );
915
			}
916
			return $list;
917
		}
918
919
		if( $expr instanceof \Aimeos\Base\Criteria\Expression\Sort\Iface ) {
920
			return array( $expr->getName() );
921
		}
922
923
		return [];
924
	}
925
}
926