Completed
Push — master ( 117114...c02abd )
by smiley
04:21
created

src/Drivers/DriverAbstract.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Traits\ImmutableSettingsInterface;
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 $options;
43
44
	/**
45
	 * @var \Psr\SimpleCache\CacheInterface
46
	 */
47
	protected $cache;
48
49
	/**
50
	 * The dialect to use (FQCN)
51
	 *
52
	 * @var string
53
	 */
54
	protected $dialect;
55
56
	protected $cachekey_hash_algo;
57
	protected $convert_encoding_src;
58
	protected $convert_encoding_dest;
59
60
	/**
61
	 * Constructor.
62
	 *
63
	 * @param \chillerlan\Traits\ImmutableSettingsInterface $options
64
	 * @param \Psr\SimpleCache\CacheInterface|null $cache
65
	 * @param \Psr\Log\LoggerInterface|null        $logger
66
	 */
67
	public function __construct(ImmutableSettingsInterface $options, CacheInterface $cache = null, LoggerInterface $logger = null){
68
		$this->options = $options;
0 ignored issues
show
Documentation Bug introduced by
$options is of type object<chillerlan\Traits...tableSettingsInterface>, but the property $options was declared to be of type object<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;
0 ignored issues
show
Accessing cachekey_hash_algo on the interface chillerlan\Traits\ImmutableSettingsInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
74
		$this->convert_encoding_src  = $this->options->convert_encoding_src;
0 ignored issues
show
Accessing convert_encoding_src on the interface chillerlan\Traits\ImmutableSettingsInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
75
		$this->convert_encoding_dest = $this->options->convert_encoding_dest;
0 ignored issues
show
Accessing convert_encoding_dest on the interface chillerlan\Traits\ImmutableSettingsInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
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       $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($this);
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)){
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
			throw new DriverException('sql error: ['.get_called_class().'::raw()] '.$e->getMessage());
165
		}
166
167
	}
168
169
	/** @inheritdoc */
170
	public function prepared(string $sql, array $values = null, string $index = null, bool $assoc = null){
171
		$this->checkSQL($sql);
172
		$this->logger->debug('DriverAbstract::prepared()', ['method' => __METHOD__, 'sql' => $sql, 'val' => $values, 'index' => $index, 'assoc' => $assoc]);
173
174
		try{
175
			return $this->prepared_query(
176
				$sql,
177
				$values !== null ? $values : [],
178
				$index,
179
				$assoc  !== null ? $assoc  : true
180
			);
181
		}
182
		catch(\Exception $e){
183
			throw new DriverException('sql error: ['.get_called_class().'::prepared()] '.$e->getMessage());
184
		}
185
186
	}
187
188
	/**
189
	 * @inheritdoc
190
	 * @todo: return array of results
191
	 */
192
	public function multi(string $sql, array $values){
193
		$this->checkSQL($sql);
194
195
		if(!is_array($values) || count($values) < 1 || !is_array($values[0]) || count($values[0]) < 1){
196
			throw new DriverException('invalid data');
197
		}
198
199
		try{
200
			return $this->multi_query($sql, $values);
201
		}
202
		catch(\Exception $e){
203
			throw new DriverException('sql error: ['.get_called_class().'::multi()] '.$e->getMessage());
204
		}
205
206
	}
207
208
	/**
209
	 * @inheritdoc
210
	 * @todo: return array of results
211
	 * @see determine callable type? http://php.net/manual/en/language.types.callable.php#118032
212
	 */
213
	public function multiCallback(string $sql, iterable $data, $callback){
214
		$this->checkSQL($sql);
215
216
		if(count($data) < 1){
217
			throw new DriverException('invalid data');
218
		}
219
220
		if(!is_callable($callback)){
221
			throw new DriverException('invalid callback');
222
		}
223
224
		try{
225
			return $this->multi_callback_query($sql, $data, $callback);
226
		}
227
		catch(\Exception $e){
228
			throw new DriverException('sql error: ['.get_called_class().'::multiCallback()] '.$e->getMessage());
229
		}
230
231
	}
232
233
	/** @inheritdoc */
234
	public function rawCached(string $sql, string $index = null, bool $assoc = null, int $ttl = null){
235
		$result = $this->cacheGet($sql, [], $index);
236
237
		if(!$result){
238
			$result = $this->raw($sql, $index, $assoc !== null ? $assoc : true);
239
240
			$this->cacheSet($sql, $result, [], $index, $ttl);
241
		}
242
243
		return $result;
244
	}
245
246
	/** @inheritdoc */
247
	public function preparedCached(string $sql, array $values = null, string $index = null, bool $assoc = null, int $ttl = null){
248
		$result = $this->cacheGet($sql, $values, $index);
249
250
		if(!$result){
251
			$result = $this->prepared($sql, $values, $index, $assoc);
252
253
			$this->cacheSet($sql, $result, $values, $index, $ttl);
254
		}
255
256
		return $result;
257
	}
258
259
	/**
260
	 * @todo return result only, Result::$isBool, Result::$success
261
	 *
262
	 * @param callable    $callable
263
	 * @param array       $args
264
	 * @param string|null $index
265
	 * @param bool        $assoc
266
	 *
267
	 * @return bool|\chillerlan\Database\Result
268
	 */
269
	protected function getResult(callable $callable, array $args, string $index = null, bool $assoc = null){
270
		$out = new Result(null, $this->convert_encoding_src, $this->convert_encoding_dest);
271
		$i   = 0;
272
273
		/** @noinspection PhpAssignmentInConditionInspection */
274
		while($row = call_user_func_array($callable, $args)){
275
			$key = $assoc && !empty($index) ? $row[$index] : $i;
276
277
			$out[$key] = $row;
278
			$i++;
279
		}
280
281
		return $i === 0 ? true : $out;
282
	}
283
284
	/**
285
	 * @param string      $sql
286
	 * @param array|null  $values
287
	 * @param string|null $index
288
	 *
289
	 * @return string
290
	 */
291
	protected function cacheKey(string $sql, array $values = null, string $index = null):string{
292
		return hash($this->cachekey_hash_algo, serialize([$sql, $values, $index]));
293
	}
294
295
	/**
296
	 * @param string      $sql
297
	 * @param array       $values
298
	 * @param string|null $index
299
	 *
300
	 * @return bool|mixed
301
	 */
302
	protected function cacheGet(string $sql, array $values = null, string $index = null){
303
304
		if($this->cache instanceof CacheInterface){
305
			return $this->cache->get($this->cacheKey($sql, $values, $index));
306
		}
307
308
		return false; // @codeCoverageIgnore
309
	}
310
311
	/**
312
	 * @param string      $sql
313
	 * @param             $result
314
	 * @param array|null  $values
315
	 * @param string|null $index
316
	 * @param int|null    $ttl
317
	 *
318
	 * @return bool
319
	 */
320
	protected function cacheSet(string $sql, $result, array $values = null, string $index = null, int $ttl = null):bool{
321
322
		if($this->cache instanceof CacheInterface){
323
			return $this->cache->set($this->cacheKey($sql, $values, $index), $result, $ttl);
324
		}
325
326
		return false; // @codeCoverageIgnore
327
	}
328
329
	/**
330
	 * @param $sql
331
	 *
332
	 * @throws \chillerlan\Database\Drivers\DriverException
333
	 */
334
	protected function checkSQL(string $sql):void{
335
336
		if(empty(trim($sql))){
337
			throw new DriverException('sql error: empty sql');
338
		}
339
340
	}
341
342
}
343