DriverAbstract   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 323
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 94
dl 0
loc 323
rs 8.96
c 2
b 0
f 0
wmc 43

15 Methods

Rating   Name   Duplication   Size   Complexity  
A cacheGet() 0 7 2
A cacheSet() 0 7 2
A raw() 0 12 3
A getDBResource() 0 2 1
A getResult() 0 13 5
A preparedCached() 0 10 2
A rawCached() 0 10 3
A escape() 0 20 6
A checkSQL() 0 4 2
A multi() 0 15 6
A multiCallback() 0 19 4
A cacheKey() 0 2 1
A getDialect() 0 2 1
A prepared() 0 17 4
A __construct() 0 9 1

How to fix   Complexity   

Complex Class

Complex classes like DriverAbstract 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 DriverAbstract, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Class DriverAbstract
4
 *
5
 * @filesource   DriverAbstract.php
6
 * @created      28.06.2017
7
 * @package      chillerlan\Database\Drivers
8
 * @author       Smiley <[email protected]>
9
 * @copyright    2017 Smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\Database\Drivers;
14
15
use chillerlan\Database\{
16
	Dialects\Dialect, Result
17
};
18
use chillerlan\Settings\SettingsContainerInterface;
19
use Psr\Log\{
20
	LoggerAwareInterface, LoggerAwareTrait, LoggerInterface
21
};
22
use Psr\SimpleCache\CacheInterface;
23
24
/**
25
 *
26
 */
27
abstract class DriverAbstract implements DriverInterface, LoggerAwareInterface{
28
	use LoggerAwareTrait;
29
30
	/**
31
	 * Holds the database resource object
32
	 *
33
	 * @var resource
34
	 */
35
	protected $db;
36
37
	/**
38
	 * Holds the settings
39
	 *
40
	 * @var \chillerlan\Database\DatabaseOptions
41
	 */
42
	protected SettingsContainerInterface $options;
43
44
	/**
45
	 * @var \Psr\SimpleCache\CacheInterface|null
46
	 */
47
	protected ?CacheInterface $cache = null;
48
49
	/**
50
	 * The dialect to use (FQCN)
51
	 *
52
	 * @var string
53
	 */
54
	protected string $dialect;
55
56
	protected string $cachekey_hash_algo;
57
	protected ?string $convert_encoding_src;
58
	protected ?string $convert_encoding_dest;
59
60
	/**
61
	 * Constructor.
62
	 *
63
	 * @param \chillerlan\Settings\SettingsContainerInterface $options
64
	 * @param \Psr\SimpleCache\CacheInterface|null $cache
65
	 * @param \Psr\Log\LoggerInterface|null        $logger
66
	 */
67
	public function __construct(SettingsContainerInterface $options, CacheInterface $cache = null, LoggerInterface $logger = null){
68
		$this->options = $options;
0 ignored issues
show
Documentation Bug introduced by
$options is of type chillerlan\Settings\SettingsContainerInterface, but the property $options was declared to be of type chillerlan\Database\DatabaseOptions. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
69
		$this->cache   = $cache;
70
		$this->logger  = $logger;
71
72
		// avoid unnecessary getter calls in long loops
73
		$this->cachekey_hash_algo    = $this->options->cachekey_hash_algo;
74
		$this->convert_encoding_src  = $this->options->convert_encoding_src;
75
		$this->convert_encoding_dest = $this->options->convert_encoding_dest;
76
	}
77
78
	/**
79
	 * @param string      $sql
80
	 * @param string|null $index
81
	 * @param bool        $assoc
82
	 *
83
	 * @return bool|\chillerlan\Database\Result
84
	 */
85
	abstract protected function raw_query(string $sql, ?string $index, ?bool $assoc);
86
87
	/**
88
	 * @param string      $sql
89
	 * @param array|null  $values
90
	 * @param string|null $index
91
	 * @param bool        $assoc
92
	 *
93
	 * @return bool|\chillerlan\Database\Result
94
	 */
95
	abstract protected function prepared_query(string $sql, ?array $values, ?string $index, ?bool $assoc);
96
97
	/**
98
	 * @param string   $sql
99
	 * @param array    $values
100
	 *
101
	 * @return bool
102
	 */
103
	abstract protected function multi_query(string $sql, array $values);
104
105
	/**
106
	 * @param string   $sql
107
	 * @param iterable $data
108
	 * @param          $callback
109
	 *
110
	 * @return bool
111
	 */
112
	abstract protected function multi_callback_query(string $sql, iterable $data, $callback);
113
114
	/**
115
	 * @param string $data
116
	 *
117
	 * @return string
118
	 */
119
	abstract protected function __escape(string $data):string;
120
121
	/**
122
	 * @inheritdoc
123
	 * @codeCoverageIgnore
124
	 */
125
	public function getDBResource(){
126
		return $this->db;
127
	}
128
129
	public function getDialect():Dialect{
130
		return new $this->dialect();
131
	}
132
133
	public function escape($data = null){
134
135
		if($data === null){
136
			return 'null';
137
		}
138
		elseif(is_bool($data)){
139
			return (int)$data;
140
		}
141
		elseif(is_numeric($data)){
142
			$data = $data + 0;
143
144
			if(is_int($data)){
0 ignored issues
show
introduced by
The condition is_int($data) is always true.
Loading history...
145
				return intval($data);
146
			}
147
			elseif(is_float($data)){
148
				return floatval($data);
149
			}
150
		}
151
152
		return $this->__escape($data);
153
	}
154
155
	/** @inheritdoc */
156
	public function raw(string $sql, string $index = null, bool $assoc = null){
157
		$this->checkSQL($sql);
158
		$this->logger->debug('DriverAbstract::raw()', ['method' => __METHOD__, 'sql' => $sql, 'index' => $index, 'assoc' => $assoc]);
159
160
		try{
161
			return $this->raw_query($sql, $index, $assoc !== null ? $assoc : true);
162
		}
163
		catch(\Exception $e){
164
			$msg = 'sql error: ['.get_called_class().'::raw()] '.$e->getMessage();
165
			$this->logger->error($msg);
166
167
			throw new DriverException($msg);
168
		}
169
170
	}
171
172
	/** @inheritdoc */
173
	public function prepared(string $sql, array $values = null, string $index = null, bool $assoc = null){
174
		$this->checkSQL($sql);
175
		$this->logger->debug('DriverAbstract::prepared()', ['method' => __METHOD__, 'sql' => $sql, 'val' => $values, 'index' => $index, 'assoc' => $assoc]);
176
177
		try{
178
			return $this->prepared_query(
179
				$sql,
180
				$values !== null ? $values : [],
181
				$index,
182
				$assoc  !== null ? $assoc  : true
183
			);
184
		}
185
		catch(\Exception $e){
186
			$msg = 'sql error: ['.get_called_class().'::prepared()] '.$e->getMessage();
187
			$this->logger->error($msg);
188
189
			throw new DriverException($msg);
190
		}
191
192
	}
193
194
	/**
195
	 * @inheritdoc
196
	 * @todo: return array of results
197
	 */
198
	public function multi(string $sql, array $values){
199
		$this->checkSQL($sql);
200
201
		if(!is_array($values) || count($values) < 1 || !is_array($values[0]) || count($values[0]) < 1){
0 ignored issues
show
introduced by
The condition is_array($values) is always true.
Loading history...
202
			throw new DriverException('invalid data');
203
		}
204
205
		try{
206
			return $this->multi_query($sql, $values);
207
		}
208
		catch(\Exception $e){
209
			$msg = 'sql error: ['.get_called_class().'::multi()] '.$e->getMessage();
210
			$this->logger->error($msg);
211
212
			throw new DriverException($msg);
213
		}
214
215
	}
216
217
	/**
218
	 * @inheritdoc
219
	 * @todo: return array of results
220
	 * @see determine callable type? http://php.net/manual/en/language.types.callable.php#118032
221
	 */
222
	public function multiCallback(string $sql, iterable $data, $callback){
223
		$this->checkSQL($sql);
224
225
		if(count($data) < 1){
226
			throw new DriverException('invalid data');
227
		}
228
229
		if(!is_callable($callback)){
230
			throw new DriverException('invalid callback');
231
		}
232
233
		try{
234
			return $this->multi_callback_query($sql, $data, $callback);
235
		}
236
		catch(\Exception $e){
237
			$msg = 'sql error: ['.get_called_class().'::multiCallback()] '.$e->getMessage();
238
			$this->logger->error($msg);
239
240
			throw new DriverException($msg);
241
		}
242
243
	}
244
245
	/** @inheritdoc */
246
	public function rawCached(string $sql, string $index = null, bool $assoc = null, int $ttl = null){
247
		$result = $this->cacheGet($sql, [], $index);
248
249
		if(!$result){
250
			$result = $this->raw($sql, $index, $assoc !== null ? $assoc : true);
251
252
			$this->cacheSet($sql, $result, [], $index, $ttl);
253
		}
254
255
		return $result;
256
	}
257
258
	/** @inheritdoc */
259
	public function preparedCached(string $sql, array $values = null, string $index = null, bool $assoc = null, int $ttl = null){
260
		$result = $this->cacheGet($sql, $values, $index);
261
262
		if(!$result){
263
			$result = $this->prepared($sql, $values, $index, $assoc);
264
265
			$this->cacheSet($sql, $result, $values, $index, $ttl);
266
		}
267
268
		return $result;
269
	}
270
271
	/**
272
	 * @todo return result only, Result::$isBool, Result::$success
273
	 *
274
	 * @param callable    $callable
275
	 * @param array       $args
276
	 * @param string|null $index
277
	 * @param bool        $assoc
278
	 *
279
	 * @return bool|\chillerlan\Database\Result
280
	 */
281
	protected function getResult(callable $callable, array $args, string $index = null, bool $assoc = null){
282
		$out = new Result(null, $this->convert_encoding_src, $this->convert_encoding_dest);
283
		$i   = 0;
284
285
		/** @noinspection PhpAssignmentInConditionInspection */
286
		while($row = call_user_func_array($callable, $args)){
287
			$key = $assoc && !empty($index) ? $row[$index] : $i;
288
289
			$out[$key] = $row;
290
			$i++;
291
		}
292
293
		return $i === 0 ? true : $out;
294
	}
295
296
	/**
297
	 * @param string      $sql
298
	 * @param array|null  $values
299
	 * @param string|null $index
300
	 *
301
	 * @return string
302
	 */
303
	protected function cacheKey(string $sql, array $values = null, string $index = null):string{
304
		return hash($this->cachekey_hash_algo, serialize([$sql, $values, $index]));
305
	}
306
307
	/**
308
	 * @param string      $sql
309
	 * @param array       $values
310
	 * @param string|null $index
311
	 *
312
	 * @return bool|mixed
313
	 */
314
	protected function cacheGet(string $sql, array $values = null, string $index = null){
315
316
		if($this->cache instanceof CacheInterface){
317
			return $this->cache->get($this->cacheKey($sql, $values, $index));
318
		}
319
320
		return false; // @codeCoverageIgnore
321
	}
322
323
	/**
324
	 * @param string      $sql
325
	 * @param             $result
326
	 * @param array|null  $values
327
	 * @param string|null $index
328
	 * @param int|null    $ttl
329
	 *
330
	 * @return bool
331
	 */
332
	protected function cacheSet(string $sql, $result, array $values = null, string $index = null, int $ttl = null):bool{
333
334
		if($this->cache instanceof CacheInterface){
335
			return $this->cache->set($this->cacheKey($sql, $values, $index), $result, $ttl);
336
		}
337
338
		return false; // @codeCoverageIgnore
339
	}
340
341
	/**
342
	 * @param $sql
343
	 *
344
	 * @throws \chillerlan\Database\Drivers\DriverException
345
	 */
346
	protected function checkSQL(string $sql):void{
347
348
		if(empty(trim($sql))){
349
			throw new DriverException('sql error: empty sql');
350
		}
351
352
	}
353
354
}
355