Passed
Push — master ( 3a0301...45c7a2 )
by Aimeos
02:41
created

SQL::translateValue()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 8
rs 10
cc 4
nc 5
nop 3
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-2023
7
 * @package Base
8
 * @subpackage Common
9
 */
10
11
12
namespace Aimeos\Base\Criteria\Expression\Compare;
13
14
15
/**
16
 * SQL implementation for comparing objects.
17
 *
18
 * @package Base
19
 * @subpackage Common
20
 */
21
class SQL extends Base
22
{
23
	private static $operators = ['=~' => 'LIKE', '~=' => 'LIKE', '==' => '=', '!=' => '<>', '>' => '>', '>=' => '>=', '<' => '<', '<=' => '<=', '-' => '-'];
24
	private \Aimeos\Base\DB\Connection\Iface $conn;
25
26
27
	/**
28
	 * Initializes the object.
29
	 *
30
	 * @param \Aimeos\Base\DB\Connection\Iface $conn Database connection object
31
	 * @param string $operator Operator used for the expression
32
	 * @param string $name Name of variable or column that should be compared.
33
	 * @param mixed $value Value that the variable or column should be compared to
34
	 */
35
	public function __construct( \Aimeos\Base\DB\Connection\Iface $conn, string $operator, string $name, $value )
36
	{
37
		if( !isset( self::$operators[$operator] ) ) {
38
			throw new \Aimeos\Base\Exception( sprintf( 'Invalid operator "%1$s"', $operator ) );
39
		}
40
41
		parent::__construct( $operator, $name, $value );
42
		$this->conn = $conn;
43
	}
44
45
46
	/**
47
	 * Returns the available operators for the expression.
48
	 *
49
	 * @return array List of available operators
50
	 */
51
	public static function getOperators() : array
52
	{
53
		return array_keys( self::$operators );
54
	}
55
56
57
	/**
58
	 * Creates a term string from the given parameters.
59
	 *
60
	 * @param string|array $name Translated name(s) of the variable or column
61
	 * @param string $type Type constant
62
	 * @param mixed $value Value that the variable or column should be compared to
63
	 * @return string Created term string (name operator value)
64
	 */
65
	protected function createTerm( $name, string $type, $value ) : string
66
	{
67
		$op = $this->getOperator();
68
69
		if( $op === '-' )
70
		{
71
			$p = explode( ' - ', $value );
72
73
			return $name . ' >= ' . $this->escape( '>=', $type, $p[0] )
0 ignored issues
show
Bug introduced by
Are you sure $name of type array|string 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

73
			return /** @scrutinizer ignore-type */ $name . ' >= ' . $this->escape( '>=', $type, $p[0] )
Loading history...
74
				. ' AND ' . $name . ' <= ' . $this->escape( '<=', $type, $p[1] );
75
		}
76
77
		$term = $name . ' ' . self::$operators[$op] . ' ' . $this->escape( $op, $type, $value );
78
79
		if( in_array( $op, array( '=~', '~=' ), true ) ) {
80
			$term .= ' ESCAPE \'#\'';
81
		}
82
83
		return $term;
84
	}
85
86
87
	/**
88
	 * Creates a term which contains a null value.
89
	 *
90
	 * @param string|array $name Translated name(s) of the variable or column
91
	 * @param string $type Code of the internal value type
92
	 * @return string String that can be inserted into a SQL statement
93
	 */
94
	protected function createNullTerm( $name, string $type ) : string
95
	{
96
		switch( $this->getOperator() )
97
		{
98
			case '==': return $name . ' IS NULL';
0 ignored issues
show
Bug introduced by
Are you sure $name of type array|string 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

98
			case '==': return /** @scrutinizer ignore-type */ $name . ' IS NULL';
Loading history...
99
			case '!=': return $name . ' IS NOT NULL';
100
		}
101
102
		throw new \Aimeos\Base\Exception( sprintf( 'NULL value not allowed for operator "%1$s"', $this->getOperator() ) );
103
	}
104
105
106
	/**
107
	 * Creates a term from a list of values.
108
	 *
109
	 * @param string|array $name Translated name(s) of the variable or column
110
	 * @param string $type Type constant
111
	 * @return string String that can be inserted into a SQL statement
112
	 */
113
	protected function createListTerm( $name, string $type ) : string
114
	{
115
		switch( $this->getOperator() )
116
		{
117
			case '==':
118
				return $name . ' IN ' . $this->createValueList( $type, (array) $this->getValue() );
0 ignored issues
show
Bug introduced by
Are you sure $name of type array|string 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

118
				return /** @scrutinizer ignore-type */ $name . ' IN ' . $this->createValueList( $type, (array) $this->getValue() );
Loading history...
119
			case '!=':
120
				return $name . ' NOT IN ' . $this->createValueList( $type, (array) $this->getValue() );
121
			default:
122
				$terms = [];
123
124
				foreach( (array) $this->getValue() as $val ) {
125
					$terms[] = $this->createTerm( $name, $type, $val );
126
				}
127
128
				return '(' . implode( ' OR ', $terms ) . ')';
129
		}
130
	}
131
132
133
	/**
134
	 * Creates a list of search values.
135
	 *
136
	 * @param string $type Type constant
137
	 * @param string[] $values Value list for the variable or column name
138
	 * @return string String of comma separated values in parenthesis
139
	 */
140
	protected function createValueList( string $type, array $values ) : string
141
	{
142
		if( empty( $values ) ) {
143
			return '(NULL)';
144
		}
145
146
		$operator = $this->getOperator();
147
148
		foreach( $values as $key => $value ) {
149
			$values[$key] = $this->escape( $operator, $type, $value );
150
		}
151
152
		return '(' . implode( ',', $values ) . ')';
153
	}
154
155
156
	/**
157
	 * Escapes the value so it can be inserted into a SQL statement
158
	 *
159
	 * @param string $operator Operator used for the expression
160
	 * @param string $type Type constant
161
	 * @param mixed $value Value that the variable or column should be compared to
162
	 * @return double|string|int Escaped value
163
	 */
164
	protected function escape( string $operator, string $type, $value )
165
	{
166
		$value = $this->translateValue( $this->getName(), $value, $type );
167
168
		switch( $type )
169
		{
170
			case \Aimeos\Base\DB\Statement\Base::PARAM_NULL:
171
				$value = 'null'; break;
172
			case \Aimeos\Base\DB\Statement\Base::PARAM_BOOL:
173
				$value = (int) (bool) $value; break;
174
			case \Aimeos\Base\DB\Statement\Base::PARAM_INT:
175
				$value = $value !== '' ? (int) $value : 'null'; break;
176
			case \Aimeos\Base\DB\Statement\Base::PARAM_FLOAT:
177
				$value = $value !== '' ? (double) $value : 'null'; break;
178
			case \Aimeos\Base\DB\Statement\Base::PARAM_STR:
179
				if( $operator === '~=' ) {
180
					$value = '\'%' . str_replace( ['#', '%', '_', '['], ['##', '#%', '#_', '#['], $this->conn->escape( (string) $value ) ) . '%\''; break;
181
				}
182
				if( $operator === '=~' ) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
183
					$value = '\'' . str_replace( ['#', '%', '_', '['], ['##', '#%', '#_', '#['], $this->conn->escape( (string) $value ) ) . '%\''; break;
184
				}
185
			default: // all other operators: escape in default case
186
				$value = '\'' . $this->conn->escape( (string) $value ) . '\'';
187
		}
188
189
		return $value;
190
	}
191
192
193
	/**
194
	 * Returns the connection object.
195
	 *
196
	 * return \Aimeos\Base\DB\Connection\Iface Connection object
197
	 */
198
	public function getConnection() : \Aimeos\Base\DB\Connection\Iface
199
	{
200
		return $this->conn;
201
	}
202
203
204
	/**
205
	 * Returns the internal type of the function parameter.
206
	 *
207
	 * @param mixed &$item Reference to parameter value (will be updated if necessary)
208
	 * @return string Internal parameter type
209
	 * @throws \Aimeos\Base\Exception If an error occurs
210
	 */
211
	protected function getParamType( &$item ) : string
212
	{
213
		if( is_null( $item ) ) {
214
			return \Aimeos\Base\DB\Statement\Base::PARAM_NULL;
215
		} elseif( is_float( $item ) ) {
216
			return \Aimeos\Base\DB\Statement\Base::PARAM_FLOAT;
217
		} elseif( is_int( $item ) ) {
218
			return \Aimeos\Base\DB\Statement\Base::PARAM_INT;
219
		}
220
221
		return \Aimeos\Base\DB\Statement\Base::PARAM_STR;
222
	}
223
224
225
	/**
226
	 * Translates a value to another one by a plugin if available.
227
	 *
228
	 * @param string $name Name of variable or column that should be translated
229
	 * @param mixed $value Original value
230
	 * @param mixed $type Value type
231
	 * @return mixed Translated value
232
	 */
233
	protected function translateValue( string $name, $value, $type )
234
	{
235
		if( isset( $this->exprPlugins[$name] ) ) {
0 ignored issues
show
Bug introduced by
The property exprPlugins is declared private in Aimeos\Base\Criteria\Expression\Compare\Base and cannot be accessed from this context.
Loading history...
236
			return $this->exprPlugins[$name]->translate( $value, $type );
237
		}
238
239
		$types = [\Aimeos\Base\DB\Statement\Base::PARAM_INT, \Aimeos\Base\DB\Statement\Base::PARAM_FLOAT];
240
		return in_array( $type, $types ) && $value === '' ? null : $value;
241
	}
242
}
243