Traits   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 235
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 41
eloc 76
c 4
b 0
f 0
dl 0
loc 235
rs 9.1199

8 Methods

Rating   Name   Duplication   Size   Complexity  
B translateName() 0 48 9
A isFunction() 0 23 6
A translate() 0 4 1
A replaceParameter() 0 12 3
A translateValue() 0 4 2
A getPlugin() 0 3 1
A setPlugins() 0 3 1
D escape() 0 34 18

How to fix   Complexity   

Complex Class

Complex classes like Traits often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Traits, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2020-2025
6
 * @package Base
7
 * @subpackage Common
8
 */
9
10
11
namespace Aimeos\Base\Criteria\Expression;
12
13
14
/**
15
 * Expression trait with basic methods
16
 *
17
 * @package Base
18
 * @subpackage Common
19
 */
20
trait Traits
21
{
22
	private array $exprPlugins = [];
23
24
25
	/**
26
	 * Returns the left side of the compare expression.
27
	 *
28
	 * @return string Name of variable or column that should be compared
29
	 */
30
	abstract public function getName() : string;
31
32
33
	/**
34
	 * Translates the sort key into the name required by the storage
35
	 *
36
	 * @param array $translations Associative list of variable or column names that should be translated
37
	 * @param array $funcs Associative list of item names and functions modifying the conditions
38
	 * @return string Translated name (with replaced parameters if the name is an expression function)
39
	 */
40
	public function translate( array $translations, array $funcs = [] ) : ?string
41
	{
42
		$name = $this->getName();
43
		return $this->translateName( $name, $translations, $funcs );
44
	}
45
46
47
	/**
48
	 * Checks if the given string is an expression function and returns the parameters.
49
	 * The parameters will be cut off the function name and will be added to
50
	 * the given parameter array
51
	 *
52
	 * @param string $name Function string to check, will be cut to <function>() (without parameter)
53
	 * @param array $params Array that will contain the list of parameters afterwards
54
	 * @return bool True if string is an expression function, false if not
55
	 */
56
	protected function isFunction( string &$name, array &$params ) : bool
57
	{
58
		$len = strlen( $name );
59
		if( $len === 0 || $name[$len - 1] !== ')' ) {
60
			return false;
61
		}
62
63
		if( ( $pos = strpos( $name, '(' ) ) === false ) {
64
			throw new \Aimeos\Base\Exception( 'Missing opening bracket for function syntax' );
65
		}
66
67
		if( ( $paramstr = substr( $name, $pos, $len - $pos ) ) === false ) {
68
			throw new \Aimeos\Base\Exception( 'Unable to extract function parameter' );
69
		}
70
71
		if( ( $namestr = substr( $name, 0, $pos ) ) === false ) {
72
			throw new \Aimeos\Base\Exception( 'Unable to extract function name' );
73
		}
74
75
		$params = json_decode( str_replace( ['(', ')'], ['[', ']'], $paramstr ), true );
76
		$name = $namestr . '()';
77
78
		return true;
79
	}
80
81
82
	/**
83
	 * Replaces the parameters in nested arrays
84
	 *
85
	 * @param array $list Multi-dimensional associative array of values including positional parameter, e.g. "$1"
86
	 * @param string[] $find List of strings to search for, e.g. ['$1', '$2']
87
	 * @param string[] $replace List of strings to replace by, e.g. ['val1', 'val2']
88
	 * @return array Multi-dimensional associative array with parameters replaced
89
	 */
90
	protected function replaceParameter( array $list, array $find, array $replace ) : array
91
	{
92
		foreach( $list as $key => $value )
93
		{
94
			if( is_array( $value ) ) {
95
				$list[$key] = $this->replaceParameter( $value, $find, $replace );
96
			} else {
97
				$list[$key] = str_replace( $find, $replace, $value );
98
			}
99
		}
100
101
		return $list;
102
	}
103
104
105
	/**
106
	 * Translates an expression string and replaces the parameter if it's an expression function.
107
	 *
108
	 * @param string $name Expresion string or function
109
	 * @param array $translations Associative list of names and their translations (may include parameter if a name is an expression function)
110
	 * @param array $funcs Associative list of item names and functions modifying the conditions
111
	 * @return mixed Translated name (with replaced parameters if the name is an expression function)
112
	 */
113
	protected function translateName( string &$name, array $translations = [], array $funcs = [] )
114
	{
115
		$params = [];
116
117
		if( $this->isFunction( $name, $params ) === true )
118
		{
119
			$source = $name;
120
			if( isset( $translations[$name] ) ) {
121
				$source = $translations[$name];
122
			}
123
124
			if( isset( $funcs[$name] ) ) {
125
				$params = $funcs[$name]( $source, $params );
126
			}
127
128
			$find = [];
129
			$count = count( $params );
130
131
			for( $i = 0; $i < $count; $i++ )
132
			{
133
				$find[$i] = '$' . ( $i + 1 );
134
135
				if( is_array( $params[$i] ) )
136
				{
137
					$list = [];
138
					foreach( $params[$i] as $key => $item ) {
139
						$list[] = $this->escape( '==', $this->getParamType( $item ), $item );
140
					}
141
					$params[$i] = join( ',', $list );
142
				}
143
				else
144
				{
145
					$params[$i] = $this->escape( '==', $this->getParamType( $params[$i] ), $params[$i] );
146
				}
147
			}
148
149
			if( is_array( $source ) ) {
150
				return $this->replaceParameter( $source, $find, $params );
151
			}
152
153
			return str_replace( $find, $params, $source );
154
		}
155
156
		if( array_key_exists( $name, $translations ) ) {
157
			return $translations[$name];
158
		}
159
160
		return $name;
161
	}
162
163
164
	/**
165
	 * Translates a value to another one by a plugin if available.
166
	 *
167
	 * @param string $name Name of variable or column that should be translated
168
	 * @param mixed $value Original value
169
	 * @param mixed $type Value type
170
	 * @return mixed Translated value
171
	 */
172
	protected function translateValue( string $name, $value, $type )
173
	{
174
		$plugin = $this->getPlugin( $name );
175
		return $plugin ? $plugin->translate( $value, $type ) : $value;
176
	}
177
178
179
	/**
180
	 * Sets the new plugins for translating values.
181
	 *
182
	 * @param \Aimeos\Base\Criteria\Plugin\Iface[] $plugins Associative list of names as keys and plugin items as values
183
	 */
184
	protected function setPlugins( array $plugins )
185
	{
186
		$this->exprPlugins = \Aimeos\Base\Criteria\Base::implements( \Aimeos\Base\Criteria\Plugin\Iface::class, $plugins );
187
	}
188
189
190
	/**
191
	 * Returns the plugin for translating values.
192
	 *
193
	 * @param string $name Column name
194
	 * @return \Aimeos\Base\Criteria\Plugin\Iface|null Plugin item or NULL if not available
195
	 */
196
	protected function getPlugin( string $name ) : ?\Aimeos\Base\Criteria\Plugin\Iface
197
	{
198
		return $this->exprPlugins[$name] ?? null;
199
	}
200
201
202
	/**
203
	 * Escapes the value so it can be inserted into a SQL statement
204
	 *
205
	 * @param string $operator Operator used for the expression
206
	 * @param string $type Type constant
207
	 * @param mixed $value Value that the variable or column should be compared to
208
	 * @return double|string|int Escaped value
209
	 */
210
	protected function escape( string $operator, string $type, $value )
211
	{
212
		$value = $this->translateValue( $this->getName(), $value, $type );
213
214
		switch( $type )
215
		{
216
			case 'null':
217
			case \Aimeos\Base\DB\Statement\Base::PARAM_NULL:
218
				$value = 'null'; break;
219
			case 'bool':
220
			case 'boolean':
221
			case \Aimeos\Base\DB\Statement\Base::PARAM_BOOL:
222
				$value = (int) (bool) $value; break;
223
			case 'int':
224
			case 'integer':
225
			case \Aimeos\Base\DB\Statement\Base::PARAM_INT:
226
				$value = $value !== '' ? (int) (string) $value : 'null'; break; // objects must be casted to strings first
227
			case 'float':
228
			case \Aimeos\Base\DB\Statement\Base::PARAM_FLOAT:
229
				$value = $value !== '' ? (double) $value : 'null'; break;
230
			case 'json':
231
			case 'string':
232
			case \Aimeos\Base\DB\Statement\Base::PARAM_STR:
233
				if( $operator === '~=' ) {
234
					$value = '\'%' . str_replace( ['#', '%', '_', '['], ['##', '#%', '#_', '#['], $this->getConnection()->escape( (string) $value ) ) . '%\''; break;
0 ignored issues
show
Bug introduced by
It seems like getConnection() 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

234
					$value = '\'%' . str_replace( ['#', '%', '_', '['], ['##', '#%', '#_', '#['], $this->/** @scrutinizer ignore-call */ getConnection()->escape( (string) $value ) ) . '%\''; break;
Loading history...
235
				}
236
				if( $operator === '=~' ) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
237
					$value = '\'' . str_replace( ['#', '%', '_', '['], ['##', '#%', '#_', '#['], $this->getConnection()->escape( (string) $value ) ) . '%\''; break;
238
				}
239
			default: // all other operators: escape in default case
240
				$value = '\'' . $this->getConnection()->escape( (string) $value ) . '\'';
241
		}
242
243
		return $value;
244
	}
245
246
247
	/**
248
	 * @param string &$item Reference to parameter value (will be updated if necessary)
249
	 *
250
	 * @param mixed $item Parameter value
251
	 * @return string Internal parameter type
252
	 * @throws \Aimeos\Base\Exception If an error occurs
253
	 */
254
	abstract protected function getParamType( &$item ) : string;
255
}
256