Completed
Push — develop ( 111dce...b8d476 )
by Timothy
02:35
created

Driver::insertBatch()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 35
Code Lines 14

Duplication

Lines 8
Ratio 22.86 %

Importance

Changes 0
Metric Value
cc 3
eloc 14
nc 3
nop 2
dl 8
loc 35
rs 8.8571
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
/**
3
 * Query
4
 *
5
 * SQL Query Builder / Database Abstraction Layer
6
 *
7
 * PHP version 7
8
 *
9
 * @package     Query
10
 * @author      Timothy J. Warren <[email protected]>
11
 * @copyright   2012 - 2016 Timothy J. Warren
12
 * @license     http://www.opensource.org/licenses/mit-license.html  MIT License
13
 * @link        https://git.timshomepage.net/aviat4ion/Query
14
 */
15
namespace Query\Drivers\Firebird;
16
17
use PDO;
18
use PDOException;
19
use Query\Drivers\AbstractDriver;
20
use Query\Drivers\DriverInterface;
21
22
/**
23
 * Firebird Database class
24
 *
25
 * PDO-firebird isn't stable, so this is a wrapper of the fbird_ public functions.
26
 *
27
 */
28
class Driver extends AbstractDriver implements DriverInterface {
29
30
	/**
31
	 * Reference to the resource returned by
32
	 * the last query executed
33
	 *
34
	 * @var resource
35
	 */
36
	protected $statementLink = NULL;
37
38
	/**
39
	 * Reference to the current transaction
40
	 *
41
	 * @var resource
42
	 */
43
	protected $trans = NULL;
44
45
	/**
46
	 * Reference to the connection resource
47
	 *
48
	 * @var resource
49
	 */
50
	protected $conn = NULL;
51
52
	/**
53
	 * Reference to the service resource
54
	 *
55
	 * @var resource
56
	 */
57
	protected $service = NULL;
58
59
	/**
60
	 * Firebird doesn't have the truncate keyword
61
	 *
62
	 * @var boolean
63
	 */
64
	protected $hasTruncate = FALSE;
65
66
	/**
67
	 * Open the link to the database
68
	 *
69
	 * @param string $dbpath
70
	 * @param string $user
71
	 * @param string $pass
72
	 * @param array $options
73
	 * @throws PDOException
74
	 */
75
	public function __construct($dbpath, $user='SYSDBA', $pass='masterkey', array $options = [])
76
	{
77
		$connectFunction = (isset($options[PDO::ATTR_PERSISTENT]) && $options[PDO::ATTR_PERSISTENT])
78
			? '\\fbird_pconnect'
79
			: '\\fbird_connect';
80
81
		$this->conn = $connectFunction($dbpath, $user, $pass, 'utf-8', 0);
82
		$this->service = \fbird_service_attach('localhost', $user, $pass);
83
84
		// Throw an exception to make this match other pdo classes
85
		if ( ! \is_resource($this->conn))
86
		{
87
			throw new PDOException(\fbird_errmsg(), \fbird_errcode(), NULL);
88
		}
89
90
		// Load these classes here because this
91
		// driver does not call the constructor
92
		// of AbstractDriver, which defines these
93
		// class variables for the other drivers
94
		$this->_loadSubClasses();
95
	}
96
97
	/**
98
	 * Cleanup some loose ends
99
	 * @codeCoverageIgnore
100
	 */
101
	public function __destruct()
102
	{
103
		\fbird_service_detach($this->service);
104
	}
105
106
	/**
107
	 * Return service handle
108
	 *
109
	 * @return resource
110
	 */
111
	public function getService()
112
	{
113
		return $this->service;
114
	}
115
116
	/**
117
	 * Execute an sql statement and return number of affected rows
118
	 *
119
	 * @param string $sql
120
	 * @return int
121
	 */
122
	public function exec($sql)
123
	{
124
		return NULL;
125
	}
126
127
	/**
128
	 * Implement for compatibility with PDO
129
	 *
130
	 * @param int $attribute
131
	 * @return mixed
132
	 */
133
	public function getAttribute($attribute)
134
	{
135
		return NULL;
136
	}
137
138
	/**
139
	 * Return whether the current statement is in a transaction
140
	 *
141
	 * @return bool
142
	 */
143
	public function inTransaction()
144
	{
145
		return ! is_null($this->trans);
146
	}
147
148
	/**
149
	 * Returns the last value of the specified generator
150
	 *
151
	 * @param string $name
152
	 * @return mixed
153
	 */
154
	public function lastInsertId($name = NULL)
155
	{
156
		return \fbird_gen_id($name, 0, $this->conn);
157
	}
158
159
	/**
160
	 * Wrapper public function to better match PDO
161
	 *
162
	 * @param string $sql
163
	 * @return Result
164
	 * @throws PDOException
165
	 */
166
	public function query($sql = '')
167
	{
168
		if (empty($sql))
169
		{
170
			throw new PDOException("Query method requires an sql query!", 0, NULL);
171
		}
172
173
		$this->statementLink = (isset($this->trans))
174
			? \fbird_query($this->trans, $sql)
175
			: \fbird_query($this->conn, $sql);
176
177
		// Throw the error as a exception
178
		$errString = \fbird_errmsg() . "Last query:" . $this->getLastQuery();
179
		if ($this->statementLink === FALSE)
180
		{
181
			throw new PDOException($errString, \fbird_errcode(), NULL);
182
		}
183
184
		$this->statement = new Result($this->statementLink, $this);
185
186
		return $this->statement;
187
	}
188
189
	/**
190
	 * Emulate PDO prepare
191
	 *
192
	 * @param string $query
193
	 * @param array $options
194
	 * @return Result
195
	 * @throws PDOException
196
	 */
197
	public function prepare($query, $options=[])
198
	{
199
		$this->statementLink = \fbird_prepare($this->conn, $query);
200
201
		// Throw the error as an exception
202
		if ($this->statementLink === FALSE)
203
		{
204
			throw new PDOException(\fbird_errmsg(), \fbird_errcode(), NULL);
205
		}
206
207
		$this->statement = new Result($this->statementLink, $this);
208
209
		return $this->statement;
210
	}
211
212
	/**
213
	 * Start a database transaction
214
	 *
215
	 * @return boolean|null
216
	 */
217
	public function beginTransaction()
218
	{
219
		return (($this->trans = \fbird_trans($this->conn)) !== NULL) ? TRUE : NULL;
220
	}
221
222
	/**
223
	 * Commit a database transaction
224
	 *
225
	 * @return bool
226
	 */
227
	public function commit()
228
	{
229
		$res = \fbird_commit($this->trans);
230
		$this->trans = NULL;
231
		return $res;
232
	}
233
234
	/**
235
	 * Rollback a transaction
236
	 *
237
	 * @return bool
238
	 */
239
	public function rollBack()
240
	{
241
		$res = \fbird_rollback($this->trans);
242
		$this->trans = NULL;
243
		return $res;
244
	}
245
246
	/**
247
	 * Set a connection attribute
248
	 * @param int $attribute
249
	 * @param mixed $value
250
	 * @return bool
251
	 */
252
	public function setAttribute($attribute, $value)
253
	{
254
		return FALSE;
255
	}
256
257
	/**
258
	 * Prepare and execute a query
259
	 *
260
	 * @param string $sql
261
	 * @param array $args
262
	 * @return Result
263
	 */
264
	public function prepareExecute($sql, $args)
265
	{
266
		$query = $this->prepare($sql);
267
268
		// Set the statement in the class variable for easy later access
269
		$this->statementLink =& $query;
270
271
		return $query->execute($args);
272
	}
273
274
	/**
275
	 * Method to emulate PDO->quote
276
	 *
277
	 * @param string $str
278
	 * @param int $paramType
279
	 * @return string
280
	 */
281
	public function quote($str, $paramType = PDO::PARAM_STR)
282
	{
283
		if(is_numeric($str))
284
		{
285
			return $str;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $str; (integer|double|string) is incompatible with the return type declared by the interface Query\Drivers\PDOInterface::quote of type string|false.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
286
		}
287
288
		return "'".str_replace("'", "''", $str)."'";
289
	}
290
291
	/**
292
	 * Method to emulate PDO->errorInfo / PDOStatement->errorInfo
293
	 *
294
	 * @return array
295
	 */
296
	public function errorInfo()
297
	{
298
		$code = \fbird_errcode();
299
		$msg = \fbird_errmsg();
300
301
		return [0, $code, $msg];
302
	}
303
304
	/**
305
	 * Method to emulate PDO->errorCode
306
	 *
307
	 * @return array
308
	 */
309
	public function errorCode()
310
	{
311
		return \fbird_errcode();
312
	}
313
314
	/**
315
	 * Bind a prepared query with arguments for executing
316
	 *
317
	 * @param string $sql
318
	 * @param array $params
319
	 * @return NULL
320
	 */
321
	public function prepareQuery($sql, $params)
322
	{
323
		// You can't bind query statements before execution with
324
		// the firebird database
325
		return NULL;
326
	}
327
328
	/**
329
	 * Create sql for batch insert
330
	 *
331
	 * @param string $table
332
	 * @param array $data
333
	 * @return array
334
	 */
335
	public function insertBatch($table, $data=[])
336
	{
337
		// Each member of the data array needs to be an array
338
		if ( ! is_array(current($data)))
339
		{
340
			return NULL;
341
		}
342
343
		// Start the block of sql statements
344
		$sql = "EXECUTE BLOCK AS BEGIN\n";
345
346
		$table = $this->quoteTable($table);
347
		$fields = \array_keys(\current($data));
348
349
		$insertTemplate = "INSERT INTO {$table} ("
350
			. implode(',', $this->quoteIdent($fields))
351
			. ") VALUES (";
352
353 View Code Duplication
		foreach($data as $item)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
354
		{
355
			// Quote string values
356
			$vals = array_map([$this, 'quote'], $item);
357
358
			// Add the values in the sql
359
			$sql .= $insertTemplate . implode(', ', $vals) . ");\n";
360
		}
361
362
		// End the block of SQL statements
363
		$sql .= "END";
364
365
		// Return a null array value so the query is run as it is,
366
		// not as a prepared statement, because a prepared statement
367
		// doesn't work for this type of query in Firebird.
368
		return [$sql, NULL];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array($sql, NULL); (array<string|null>) is incompatible with the return type of the parent method Query\Drivers\AbstractDriver::insertBatch of type null|array<string|array>.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
369
	}
370
}