Passed
Push — master ( fceccc...b4a5c9 )
by Aimeos
07:57
created

Base::createFunction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
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-2020
7
 * @package MW
8
 * @subpackage Common
9
 */
10
11
12
namespace Aimeos\MW\Criteria;
13
14
15
/**
16
 * Abstract search class
17
 *
18
 * @package MW
19
 * @subpackage Common
20
 */
21
abstract class Base implements \Aimeos\MW\Criteria\Iface
22
{
23
	/**
24
	 * Returns an array representation of the expression that can be parsed again
25
	 *
26
	 * @return array Multi-dimensional expression structure
27
	 */
28
	public function __toArray() : array
0 ignored issues
show
Coding Style introduced by
Method name "Base::__toArray" is invalid; only PHP magic methods should be prefixed with a double underscore
Loading history...
29
	{
30
		$cond = $this->getConditions();
31
		return $cond ? $cond->__toArray() : [];
32
	}
33
34
35
	/**
36
	 * Adds a new expression to the existing list combined by the AND operator.
37
	 *
38
	 * You can add expression is three ways:
39
	 *
40
	 * - Name, operator and value:
41
	 *   $f->add( 'product.code', '==', 'abc' );
42
	 *
43
	 * - Name/value pairs and optional operator ("==" by default):
44
	 *   $f->add( ['product.type' => 'voucher', 'product.status' => 1], '!=' );
45
	 *   $f->add( ['product.type' => 'default', 'product.status' => 1] );
46
	 *
47
	 * - Single expression:
48
	 *   $f->add( $f->is( 'product.code', '==', 'abc' ) );
49
	 *   $f->add( $f->and( [$f->is( 'product.code', '==', 'abc' ), $f->is( 'product.status', '>', 0 )] );
50
	 *   $f->add( $f->or( [$f->is( 'product.code', '==', 'abc' ), $f->is( 'product.label', '=~', 'abc' )] );
51
	 *   $f->add( $f->not( $f->is( 'product.code', '=~', 'abc' ) );
52
	 *
53
	 * @param \Aimeos\MW\Criteria\Expression\Combine\Iface|\Aimeos\MW\Criteria\Expression\Compare\Iface|array|string|null Expression, list of name/value pairs or name
54
	 * @param string $operator Operator to compare name and value with
55
	 * @param mixed $value Value to compare the name with
56
	 * @return \Aimeos\MW\Criteria\Iface Same object for fluent interface
57
	 */
58
	public function add( $expr, string $operator = '==', $value = null ) : \Aimeos\MW\Criteria\Iface
59
	{
60
		$cond = [];
61
62
		if( is_null( $expr ) ) {
63
			return $this;
64
		}
65
66
		if( is_string( $expr ) ) {
67
			$cond[] = $this->compare( $operator, $expr, $value );
0 ignored issues
show
Bug introduced by
The method compare() does not exist on Aimeos\MW\Criteria\Base. Since it exists in all sub-types, consider adding an abstract or default implementation to Aimeos\MW\Criteria\Base. ( Ignorable by Annotation )

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

67
			/** @scrutinizer ignore-call */ 
68
   $cond[] = $this->compare( $operator, $expr, $value );
Loading history...
68
		}
69
70
		if( is_array( $expr ) )
71
		{
72
			$list = [];
73
74
			foreach( $expr as $name => $value ) {
75
				$list[] = $this->compare( $operator, $name, $value );
76
			}
77
78
			$cond[] = $this->combine( '&&', $list );
0 ignored issues
show
Bug introduced by
The method combine() does not exist on Aimeos\MW\Criteria\Base. Since it exists in all sub-types, consider adding an abstract or default implementation to Aimeos\MW\Criteria\Base. ( Ignorable by Annotation )

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

78
			/** @scrutinizer ignore-call */ 
79
   $cond[] = $this->combine( '&&', $list );
Loading history...
79
		}
80
81
		if( $expr instanceof \Aimeos\MW\Criteria\Expression\Combine\Iface
82
			|| $expr instanceof \Aimeos\MW\Criteria\Expression\Compare\Iface
83
		) {
84
			$cond[] = $expr;
85
		}
86
87
		if( !empty( $cond ) )
88
		{
89
			$cond[] = $this->getConditions();
90
			return $this->setConditions( $this->combine( '&&', $cond ) );
0 ignored issues
show
Bug introduced by
The method setConditions() does not exist on Aimeos\MW\Criteria\Base. Since it exists in all sub-types, consider adding an abstract or default implementation to Aimeos\MW\Criteria\Base. ( Ignorable by Annotation )

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

90
			return $this->/** @scrutinizer ignore-call */ setConditions( $this->combine( '&&', $cond ) );
Loading history...
91
		}
92
93
		$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()';
94
		throw new \Aimeos\MW\Exception( $msg );
95
	}
96
97
98
	/**
99
	 * Combines the expression with an AND operator
100
	 *
101
	 * @param \Aimeos\MW\Criteria\Expression\Compare\Iface[] $list List of expression objects
102
	 * @return \Aimeos\MW\Criteria\Expression\Combine\Iface Combine expression object
103
	 */
104
	public function and( array $list ) : \Aimeos\MW\Criteria\Expression\Combine\Iface
105
	{
106
		return $this->combine( '&&', $list );
107
	}
108
109
110
	/**
111
	 * Creates a new compare expression.
112
	 *
113
	 * Available comparision operators are:
114
	 * "==": item EQUAL value
115
	 * "!=": item NOT EQUAL value
116
	 * "~=": item LIKE value
117
	 * ">=": item GREATER OR EQUAL value
118
	 * "<=": item SMALLER OR EQUAL value
119
	 * ">": item GREATER value
120
	 * "<": item SMALLER value
121
	 *
122
	 * @param string $name Name of the column or property that should be used for comparison
123
	 * @param string $operator One of the known operators
124
	 * @param mixed $value Value the column or property should be compared to
125
	 * @return \Aimeos\MW\Criteria\Expression\Compare\Iface Compare expression object
126
	 */
127
	public function is( string $name, string $operator, $value ) : \Aimeos\MW\Criteria\Expression\Compare\Iface
128
	{
129
		return $this->compare( $operator, $name, $value );
130
	}
131
132
133
	/**
134
	 * Creates a function signature for expressions used in is() and add().
135
	 *
136
	 * @param string $name Function name without parentheses
137
	 * @param array $params Single- or multi-dimensional list of parameters of type boolean, integer, float and string
138
	 * @return string Function signature
139
	 */
140
	public function make( string $name, array $params ) : string
141
	{
142
		return $name . '(' . substr( json_encode( $params ), 1, -1 ) . ')';
143
	}
144
145
146
	/**
147
	 * Negates the whole expression.
148
	 *
149
	 * @param \Aimeos\MW\Criteria\Expression\Iface $expr Expression object
150
	 * @return \Aimeos\MW\Criteria\Expression\Combine\Iface Combine expression object
151
	 */
152
	public function not( \Aimeos\MW\Criteria\Expression\Iface $expr ) : \Aimeos\MW\Criteria\Expression\Combine\Iface
153
	{
154
		return $this->combine( '!', [$expr] );
155
	}
156
157
158
	/**
159
	 * Combines the expression with an OR operator
160
	 *
161
	 * @param \Aimeos\MW\Criteria\Expression\Compare\Iface[] $list List of expression objects
162
	 * @return \Aimeos\MW\Criteria\Expression\Combine\Iface Combine expression object
163
	 */
164
	public function or( array $list ) : \Aimeos\MW\Criteria\Expression\Combine\Iface
165
	{
166
		return $this->combine( '||', $list );
167
	}
168
169
170
	/**
171
	 * Sets the keys the data should be ordered by.
172
	 *
173
	 *
174
	 * Available sorting operators are:
175
	 * "product.label": sort ascending
176
	 * "-product.label": sort descending
177
	 *
178
	 * @param array|string $keys Name of the column or property that should be used for sorting
179
	 * @return \Aimeos\MW\Criteria\Iface Object instance for fluent interface
180
	 */
181
	public function order( $names ) : \Aimeos\MW\Criteria\Iface
182
	{
183
		$sort = [];
184
185
		foreach( (array) $names as $name )
186
		{
187
			$op = '+';
188
			$name = (string) $name;
189
190
			if( strlen( $name ) && $name[0] === '-' ) {
191
				$op = '-'; $name = substr( $name, 1 );
192
			}
193
194
			$sort[] = $this->sort( $op, $name );
0 ignored issues
show
Bug introduced by
The method sort() does not exist on Aimeos\MW\Criteria\Base. Since it exists in all sub-types, consider adding an abstract or default implementation to Aimeos\MW\Criteria\Base. ( Ignorable by Annotation )

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

194
			/** @scrutinizer ignore-call */ 
195
   $sort[] = $this->sort( $op, $name );
Loading history...
195
		}
196
197
		return $this->setSortations( $sort );
0 ignored issues
show
Bug introduced by
The method setSortations() does not exist on Aimeos\MW\Criteria\Base. Since it exists in all sub-types, consider adding an abstract or default implementation to Aimeos\MW\Criteria\Base. ( Ignorable by Annotation )

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

197
		return $this->/** @scrutinizer ignore-call */ setSortations( $sort );
Loading history...
198
	}
199
200
201
	/**
202
	 * Creates condition expressions from a multi-dimensional associative array.
203
	 *
204
	 * The simplest form of a valid associative array is a single comparison:
205
	 * 	$array = [
206
	 * 		'==' => ['name' => 'value'],
207
	 * 	];
208
	 *
209
	 * Combining several conditions can look like:
210
	 * 	$array = [
211
	 * 		'&&' => [
212
	 * 			['==' => ['name' => 'value']],
213
	 * 			['==' => ['name2' => 'value2']],
214
	 * 		],
215
	 * 	];
216
	 *
217
	 * Nested combine operators are also possible.
218
	 *
219
	 * @param array $array Multi-dimensional associative array containing the expression arrays
220
	 * @return \Aimeos\MW\Criteria\Expression\Iface|null Condition expressions (maybe nested) or null for none
221
	 * @throws \Aimeos\MW\Exception If given array is invalid
222
	 */
223
	public function parse( array $array ) : ?\Aimeos\MW\Criteria\Expression\Iface
224
	{
225
		if( ( $value = reset( $array ) ) === false ) {
226
			return null;
227
		}
228
229
		$op = key( $array );
230
		$operators = $this->getOperators();
231
232
		if( in_array( $op, $operators['combine'], true ) ) {
233
			return $this->createCombineExpression( $op, (array) $value );
234
		}
235
		else if( in_array( $op, $operators['compare'], true ) ) {
236
			return $this->createCompareExpression( $op, (array) $value );
237
		}
238
239
		throw new \Aimeos\MW\Common\Exception( sprintf( 'Invalid operator "%1$s"', $op ) );
240
	}
241
242
243
	/**
244
	 * Returns the list of translated colums
245
	 *
246
	 * @param array $columns List of objects implementing getName() method
247
	 * @param array $translations Associative list of item names that should be translated
248
	 * @param array $funcs Associative list of item names and functions modifying the conditions
249
	 * @return array List of translated columns
250
	 */
251
	public function translate( array $columns, array $translations = [], array $funcs = [] ) : array
252
	{
253
		$list = [];
254
255
		foreach( $columns as $item )
256
		{
257
			if( ( $value = $item->translate( $translations, $funcs ) ) !== null ) {
258
				$list[] = $value;
259
			}
260
		}
261
262
		return $list;
263
	}
264
265
266
	/**
267
	 * Creates a "combine" expression.
268
	 *
269
	 * @param string $operator One of the "combine" operators
270
	 * @param array $list List of arrays with "combine" or "compare" representations
271
	 * @return \Aimeos\MW\Criteria\Expression\Combine\Iface Combine expression object
272
	 * @throws \Aimeos\MW\Common\Exception If operator is invalid
273
	 */
274
	protected function createCombineExpression( string $operator, array $list )
275
	{
276
		$results = [];
277
		$operators = $this->getOperators();
278
279
		foreach( $list as $entry )
280
		{
281
			$entry = (array) $entry;
282
283
			if( ( $op = key( $entry ) ) === null ) {
284
				throw new \Aimeos\MW\Common\Exception( sprintf( 'Invalid combine condition array "%1$s"', json_encode( $entry ) ) );
285
			}
286
287
			if( in_array( $op, $operators['combine'], true ) ) {
288
				$results[] = $this->createCombineExpression( $op, (array) $entry[$op] );
289
			}
290
			else if( in_array( $op, $operators['compare'], true ) ) {
291
				$results[] = $this->createCompareExpression( $op, (array) $entry[$op] );
292
			}
293
			else {
294
				throw new \Aimeos\MW\Common\Exception( sprintf( 'Invalid operator "%1$s"', $op ) );
295
			}
296
		}
297
298
		return $this->combine( $operator, $results );
299
	}
300
301
302
	/**
303
	 * Creates a "compare" expression.
304
	 *
305
	 * @param string $op One of the "compare" operators
306
	 * @param array $pair Associative list containing one name/value pair
307
	 * @return \Aimeos\MW\Criteria\Expression\Compare\Iface Compare expression object
308
	 * @throws \Aimeos\MW\Common\Exception If no name/value pair is available
309
	 */
310
	protected function createCompareExpression( $op, array $pair )
311
	{
312
		if( ( $value = reset( $pair ) ) === false ) {
313
			throw new \Aimeos\MW\Common\Exception( sprintf( 'Invalid compare condition array "%1$s"', json_encode( $pair ) ) );
314
		}
315
316
		return $this->compare( $op, key( $pair ), $value );
317
	}
318
}
319