Passed
Push — master ( f502a6...018a81 )
by Aimeos
03:11
created

Base::implements()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 10
rs 10
cc 3
nc 3
nop 2
1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2011
6
 * @copyright Aimeos (aimeos.org), 2015-2022
7
 * @package Base
8
 * @subpackage Common
9
 */
10
11
12
namespace Aimeos\Base\Criteria;
13
14
15
/**
16
 * Abstract search class
17
 *
18
 * @package Base
19
 * @subpackage Common
20
 */
21
abstract class Base implements \Aimeos\Base\Criteria\Iface
22
{
23
	/**
24
	 * Tests if all list entries implement the passed interface name
25
	 *
26
	 * @param string $interface Interface name
27
	 * @param iterable $list List of items
28
	 * @return List of tested items
0 ignored issues
show
Bug introduced by
The type Aimeos\Base\Criteria\List 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...
29
	 * @throws \Aimeos\Base\Exception If at least one items doesn't implement the interface
30
	 */
31
	public static function implements( string $interface, iterable $list ) : iterable
32
	{
33
		foreach( $list as $entry )
34
		{
35
			if( !( $entry instanceof $interface ) ) {
36
				throw new \Aimeos\Base\Exception( "Does not implement $interface: " . print_r( $entry, true ) );
0 ignored issues
show
Bug introduced by
Are you sure print_r($entry, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

36
				throw new \Aimeos\Base\Exception( "Does not implement $interface: " . /** @scrutinizer ignore-type */ print_r( $entry, true ) );
Loading history...
37
			}
38
		}
39
40
		return $list;
41
	}
42
43
44
	/**
45
	 * Returns an array representation of the expression that can be parsed again
46
	 *
47
	 * @return array Multi-dimensional expression structure
48
	 */
49
	public function __toArray() : array
50
	{
51
		$cond = $this->getConditions();
52
		return $cond ? $cond->__toArray() : [];
53
	}
54
55
56
	/**
57
	 * Adds a new expression to the existing list combined by the AND operator.
58
	 *
59
	 * You can add expression is three ways:
60
	 *
61
	 * - Name, operator and value:
62
	 *   $f->add( 'product.code', '==', 'abc' );
63
	 *
64
	 * - Name/value pairs and optional operator ("==" by default):
65
	 *   $f->add( ['product.type' => 'voucher', 'product.status' => 1], '!=' );
66
	 *   $f->add( ['product.type' => 'default', 'product.status' => 1] );
67
	 *
68
	 * - Single expression:
69
	 *   $f->add( $f->is( 'product.code', '==', 'abc' ) );
70
	 *   $f->add( $f->and( [$f->is( 'product.code', '==', 'abc' ), $f->is( 'product.status', '>', 0 )] );
71
	 *   $f->add( $f->or( [$f->is( 'product.code', '==', 'abc' ), $f->is( 'product.label', '=~', 'abc' )] );
72
	 *   $f->add( $f->not( $f->is( 'product.code', '=~', 'abc' ) );
73
	 *
74
	 * @param \Aimeos\Base\Criteria\Expression\Combine\Iface|\Aimeos\Base\Criteria\Expression\Compare\Iface|array|string|null Expression, list of name/value pairs or name
75
	 * @param string $operator Operator to compare name and value with
76
	 * @param mixed $value Value to compare the name with
77
	 * @return \Aimeos\Base\Criteria\Iface Same object for fluent interface
78
	 */
79
	public function add( $expr, string $operator = '==', $value = null ) : \Aimeos\Base\Criteria\Iface
80
	{
81
		$cond = [];
82
83
		if( is_null( $expr ) ) {
84
			return $this;
85
		}
86
87
		if( is_string( $expr ) ) {
88
			$cond[] = $this->compare( $operator, $expr, $value );
89
		}
90
91
		if( is_array( $expr ) )
92
		{
93
			$list = [];
94
95
			foreach( $expr as $name => $value ) {
96
				$list[] = $this->compare( $operator, $name, $value );
97
			}
98
99
			$cond[] = $this->and( $list );
100
		}
101
102
		if( $expr instanceof \Aimeos\Base\Criteria\Expression\Combine\Iface
103
			|| $expr instanceof \Aimeos\Base\Criteria\Expression\Compare\Iface
104
		) {
105
			$cond[] = $expr;
106
		}
107
108
		if( !empty( $cond ) )
109
		{
110
			$cond[] = $this->getConditions();
111
			return $this->setConditions( $this->and( $cond ) );
112
		}
113
114
		$msg = 'Use a column name, an array of name/value pairs or the result from and(), or(), not() or is() as first argument for add()';
115
		throw new \Aimeos\Base\Exception( $msg );
116
	}
117
118
119
	/**
120
	 * Combines the expression with an AND operator
121
	 *
122
	 * @param \Aimeos\Base\Criteria\Expression\Compare\Iface[] $list List of expression objects
123
	 * @return \Aimeos\Base\Criteria\Expression\Combine\Iface Combine expression object
124
	 */
125
	public function and( array $list ) : \Aimeos\Base\Criteria\Expression\Combine\Iface
126
	{
127
		return $this->combine( '&&', $list );
128
	}
129
130
131
	/**
132
	 * Creates a new compare expression.
133
	 *
134
	 * Available comparision operators are:
135
	 * "==": item EQUAL value
136
	 * "!=": item NOT EQUAL value
137
	 * "~=": item LIKE value
138
	 * ">=": item GREATER OR EQUAL value
139
	 * "<=": item SMALLER OR EQUAL value
140
	 * ">": item GREATER value
141
	 * "<": item SMALLER value
142
	 *
143
	 * @param string $name Name of the column or property that should be used for comparison
144
	 * @param string $operator One of the known operators
145
	 * @param mixed $value Value the column or property should be compared to
146
	 * @return \Aimeos\Base\Criteria\Expression\Compare\Iface Compare expression object
147
	 */
148
	public function is( string $name, string $operator, $value ) : \Aimeos\Base\Criteria\Expression\Compare\Iface
149
	{
150
		return $this->compare( $operator, $name, $value );
151
	}
152
153
154
	/**
155
	 * Creates a function signature for expressions used in is() and add().
156
	 *
157
	 * @param string $name Function name without parentheses
158
	 * @param array $params Single- or multi-dimensional list of parameters of type boolean, integer, float and string
159
	 * @return string Function signature
160
	 */
161
	public function make( string $name, array $params ) : string
162
	{
163
		return $name . '(' . substr( json_encode( $params ), 1, -1 ) . ')';
164
	}
165
166
167
	/**
168
	 * Negates the whole expression.
169
	 *
170
	 * @param \Aimeos\Base\Criteria\Expression\Iface $expr Expression object
171
	 * @return \Aimeos\Base\Criteria\Expression\Combine\Iface Combine expression object
172
	 */
173
	public function not( \Aimeos\Base\Criteria\Expression\Iface $expr ) : \Aimeos\Base\Criteria\Expression\Combine\Iface
174
	{
175
		return $this->combine( '!', [$expr] );
176
	}
177
178
179
	/**
180
	 * Combines the expression with an OR operator
181
	 *
182
	 * @param \Aimeos\Base\Criteria\Expression\Compare\Iface[] $list List of expression objects
183
	 * @return \Aimeos\Base\Criteria\Expression\Combine\Iface Combine expression object
184
	 */
185
	public function or( array $list ) : \Aimeos\Base\Criteria\Expression\Combine\Iface
186
	{
187
		return $this->combine( '||', $list );
188
	}
189
190
191
	/**
192
	 * Sets the keys the data should be ordered by.
193
	 *
194
	 *
195
	 * Available sorting operators are:
196
	 * "product.label": sort ascending
197
	 * "-product.label": sort descending
198
	 *
199
	 * @param array|string $keys Name of the column or property that should be used for sorting
200
	 * @return \Aimeos\Base\Criteria\Iface Object instance for fluent interface
201
	 */
202
	public function order( $names ) : \Aimeos\Base\Criteria\Iface
203
	{
204
		$sort = [];
205
206
		foreach( (array) $names as $name )
207
		{
208
			$op = '+';
209
			$name = (string) $name;
210
211
			if( strlen( $name ) && $name[0] === '-' ) {
212
				$op = '-'; $name = substr( $name, 1 );
213
			}
214
215
			$sort[] = $this->sort( $op, $name );
216
		}
217
218
		return $this->setSortations( $sort );
219
	}
220
221
222
	/**
223
	 * Creates condition expressions from a multi-dimensional associative array.
224
	 *
225
	 * The simplest form of a valid associative array is a single comparison:
226
	 * 	$array = [
227
	 * 		'==' => ['name' => 'value'],
228
	 * 	];
229
	 *
230
	 * Combining several conditions can look like:
231
	 * 	$array = [
232
	 * 		'&&' => [
233
	 * 			['==' => ['name' => 'value']],
234
	 * 			['==' => ['name2' => 'value2']],
235
	 * 		],
236
	 * 	];
237
	 *
238
	 * Nested combine operators are also possible.
239
	 *
240
	 * @param array $array Multi-dimensional associative array containing the expression arrays
241
	 * @return \Aimeos\Base\Criteria\Expression\Iface|null Condition expressions (maybe nested) or null for none
242
	 * @throws \Aimeos\Base\Exception If given array is invalid
243
	 */
244
	public function parse( array $array ) : ?\Aimeos\Base\Criteria\Expression\Iface
245
	{
246
		if( ( $value = reset( $array ) ) === false ) {
247
			return null;
248
		}
249
250
		$op = key( $array );
251
		$operators = $this->getOperators();
252
253
		if( in_array( $op, $operators['combine'], true ) ) {
254
			return $this->createCombineExpression( $op, (array) $value );
255
		}
256
		else if( in_array( $op, $operators['compare'], true ) ) {
257
			return $this->createCompareExpression( $op, (array) $value );
258
		}
259
260
		throw new \Aimeos\Base\Exception( sprintf( 'Invalid operator "%1$s"', $op ) );
261
	}
262
263
264
	/**
265
	 * Returns the list of translated colums
266
	 *
267
	 * @param array $columns List of objects implementing getName() method
268
	 * @param array $translations Associative list of item names that should be translated
269
	 * @param array $funcs Associative list of item names and functions modifying the conditions
270
	 * @return array List of translated columns
271
	 */
272
	public function translate( array $columns, array $translations = [], array $funcs = [] ) : array
273
	{
274
		$list = [];
275
276
		foreach( $columns as $item )
277
		{
278
			if( ( $value = $item->translate( $translations, $funcs ) ) !== null ) {
279
				$list[] = $value;
280
			}
281
		}
282
283
		return $list;
284
	}
285
286
287
	/**
288
	 * Creates a "combine" expression.
289
	 *
290
	 * @param string $operator One of the "combine" operators
291
	 * @param array $list List of arrays with "combine" or "compare" representations
292
	 * @return \Aimeos\Base\Criteria\Expression\Combine\Iface Combine expression object
293
	 * @throws \Aimeos\Base\Exception If operator is invalid
294
	 */
295
	protected function createCombineExpression( string $operator, array $list )
296
	{
297
		$results = [];
298
		$operators = $this->getOperators();
299
300
		foreach( $list as $entry )
301
		{
302
			$entry = (array) $entry;
303
304
			if( ( $op = key( $entry ) ) === null ) {
305
				throw new \Aimeos\Base\Exception( sprintf( 'Invalid combine condition array "%1$s"', json_encode( $entry ) ) );
306
			}
307
308
			if( in_array( $op, $operators['combine'], true ) ) {
309
				$results[] = $this->createCombineExpression( $op, (array) $entry[$op] );
310
			}
311
			else if( in_array( $op, $operators['compare'], true ) ) {
312
				$results[] = $this->createCompareExpression( $op, (array) $entry[$op] );
313
			}
314
			else {
315
				throw new \Aimeos\Base\Exception( sprintf( 'Invalid operator "%1$s"', $op ) );
316
			}
317
		}
318
319
		return $this->combine( $operator, $results );
320
	}
321
322
323
	/**
324
	 * Creates a "compare" expression.
325
	 *
326
	 * @param string $op One of the "compare" operators
327
	 * @param array $pair Associative list containing one name/value pair
328
	 * @return \Aimeos\Base\Criteria\Expression\Compare\Iface Compare expression object
329
	 * @throws \Aimeos\Base\Exception If no name/value pair is available
330
	 */
331
	protected function createCompareExpression( $op, array $pair )
332
	{
333
		if( ( $value = reset( $pair ) ) === false ) {
334
			throw new \Aimeos\Base\Exception( sprintf( 'Invalid compare condition array "%1$s"', json_encode( $pair ) ) );
335
		}
336
337
		return $this->compare( $op, key( $pair ), $value );
338
	}
339
}
340