1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Doctrine\DBAL\Driver\SQLAnywhere; |
4
|
|
|
|
5
|
|
|
use Doctrine\DBAL\Driver\Statement; |
6
|
|
|
use Doctrine\DBAL\Driver\StatementIterator; |
7
|
|
|
use Doctrine\DBAL\FetchMode; |
8
|
|
|
use Doctrine\DBAL\ParameterType; |
9
|
|
|
use IteratorAggregate; |
10
|
|
|
use PDO; |
11
|
|
|
use ReflectionClass; |
12
|
|
|
use ReflectionObject; |
13
|
|
|
use stdClass; |
14
|
|
|
use function array_key_exists; |
15
|
|
|
use function func_get_args; |
16
|
|
|
use function func_num_args; |
17
|
|
|
use function gettype; |
18
|
|
|
use function is_array; |
19
|
|
|
use function is_int; |
20
|
|
|
use function is_object; |
21
|
|
|
use function is_resource; |
22
|
|
|
use function is_string; |
23
|
|
|
use function sasql_fetch_array; |
24
|
|
|
use function sasql_fetch_assoc; |
25
|
|
|
use function sasql_fetch_object; |
26
|
|
|
use function sasql_fetch_row; |
27
|
|
|
use function sasql_prepare; |
28
|
|
|
use function sasql_stmt_affected_rows; |
29
|
|
|
use function sasql_stmt_bind_param_ex; |
30
|
|
|
use function sasql_stmt_errno; |
31
|
|
|
use function sasql_stmt_error; |
32
|
|
|
use function sasql_stmt_execute; |
33
|
|
|
use function sasql_stmt_field_count; |
34
|
|
|
use function sasql_stmt_reset; |
35
|
|
|
use function sasql_stmt_result_metadata; |
36
|
|
|
use function sprintf; |
37
|
|
|
use const SASQL_BOTH; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* SAP SQL Anywhere implementation of the Statement interface. |
41
|
|
|
*/ |
42
|
|
|
class SQLAnywhereStatement implements IteratorAggregate, Statement |
43
|
|
|
{ |
44
|
|
|
/** @var resource The connection resource. */ |
45
|
|
|
private $conn; |
46
|
|
|
|
47
|
|
|
/** @var string Name of the default class to instantiate when fetching class instances. */ |
48
|
|
|
private $defaultFetchClass = '\stdClass'; |
49
|
|
|
|
50
|
|
|
/** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */ |
51
|
|
|
private $defaultFetchClassCtorArgs = []; |
52
|
|
|
|
53
|
|
|
/** @var int Default fetch mode to use. */ |
54
|
|
|
private $defaultFetchMode = FetchMode::MIXED; |
55
|
|
|
|
56
|
|
|
/** @var resource The result set resource to fetch. */ |
57
|
|
|
private $result; |
58
|
|
|
|
59
|
|
|
/** @var resource The prepared SQL statement to execute. */ |
60
|
|
|
private $stmt; |
61
|
|
|
|
62
|
|
|
/** @var mixed[] The references to bound parameter values. */ |
63
|
|
|
private $boundValues = []; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Prepares given statement for given connection. |
67
|
|
|
* |
68
|
|
|
* @param resource $conn The connection resource to use. |
69
|
|
|
* @param string $sql The SQL statement to prepare. |
70
|
|
|
* |
71
|
|
|
* @throws SQLAnywhereException |
72
|
|
|
*/ |
73
|
|
|
public function __construct($conn, $sql) |
74
|
|
|
{ |
75
|
|
|
if (! is_resource($conn)) { |
76
|
|
|
throw new SQLAnywhereException('Invalid SQL Anywhere connection resource: ' . $conn); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
$this->conn = $conn; |
80
|
|
|
$this->stmt = sasql_prepare($conn, $sql); |
81
|
|
|
|
82
|
|
|
if (! is_resource($this->stmt)) { |
83
|
|
|
throw SQLAnywhereException::fromSQLAnywhereError($conn); |
84
|
|
|
} |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* {@inheritdoc} |
89
|
|
|
* |
90
|
|
|
* @throws SQLAnywhereException |
91
|
|
|
*/ |
92
|
|
|
public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null) |
93
|
|
|
{ |
94
|
|
|
switch ($type) { |
95
|
|
|
case ParameterType::INTEGER: |
96
|
|
|
case ParameterType::BOOLEAN: |
97
|
|
|
$type = 'i'; |
98
|
|
|
break; |
99
|
|
|
|
100
|
|
|
case ParameterType::LARGE_OBJECT: |
101
|
|
|
$type = 'b'; |
102
|
|
|
break; |
103
|
|
|
|
104
|
|
|
case ParameterType::NULL: |
105
|
|
|
case ParameterType::STRING: |
106
|
|
|
case ParameterType::BINARY: |
107
|
|
|
$type = 's'; |
108
|
|
|
break; |
109
|
|
|
|
110
|
|
|
default: |
111
|
|
|
throw new SQLAnywhereException('Unknown type: ' . $type); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
$this->boundValues[$column] =& $variable; |
115
|
|
|
|
116
|
|
|
if (! sasql_stmt_bind_param_ex($this->stmt, $column - 1, $variable, $type, $variable === null)) { |
117
|
|
|
throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
return true; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* {@inheritdoc} |
125
|
|
|
*/ |
126
|
|
|
public function bindValue($param, $value, $type = ParameterType::STRING) |
127
|
|
|
{ |
128
|
|
|
return $this->bindParam($param, $value, $type); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* {@inheritdoc} |
133
|
|
|
* |
134
|
|
|
* @throws SQLAnywhereException |
135
|
|
|
*/ |
136
|
|
|
public function closeCursor() |
137
|
|
|
{ |
138
|
|
|
if (! sasql_stmt_reset($this->stmt)) { |
139
|
|
|
throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
return true; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* {@inheritdoc} |
147
|
|
|
*/ |
148
|
|
|
public function columnCount() |
149
|
|
|
{ |
150
|
|
|
return sasql_stmt_field_count($this->stmt); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* {@inheritdoc} |
155
|
|
|
*/ |
156
|
|
|
public function errorCode() |
157
|
|
|
{ |
158
|
|
|
return sasql_stmt_errno($this->stmt); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* {@inheritdoc} |
163
|
|
|
*/ |
164
|
|
|
public function errorInfo() |
165
|
|
|
{ |
166
|
|
|
return sasql_stmt_error($this->stmt); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* {@inheritdoc} |
171
|
|
|
* |
172
|
|
|
* @throws SQLAnywhereException |
173
|
|
|
*/ |
174
|
|
|
public function execute($params = null) |
175
|
|
|
{ |
176
|
|
|
if (is_array($params)) { |
177
|
|
|
$hasZeroIndex = array_key_exists(0, $params); |
178
|
|
|
|
179
|
|
|
foreach ($params as $key => $val) { |
180
|
|
|
if ($hasZeroIndex && is_int($key)) { |
181
|
|
|
$this->bindValue($key + 1, $val); |
182
|
|
|
} else { |
183
|
|
|
$this->bindValue($key, $val); |
184
|
|
|
} |
185
|
|
|
} |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
if (! sasql_stmt_execute($this->stmt)) { |
189
|
|
|
throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
$this->result = sasql_stmt_result_metadata($this->stmt); |
193
|
|
|
|
194
|
|
|
return true; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* {@inheritdoc} |
199
|
|
|
* |
200
|
|
|
* @throws SQLAnywhereException |
201
|
|
|
*/ |
202
|
|
|
public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) |
203
|
|
|
{ |
204
|
|
|
if (! is_resource($this->result)) { |
205
|
|
|
return false; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
$fetchMode = $fetchMode ?: $this->defaultFetchMode; |
209
|
|
|
|
210
|
|
|
switch ($fetchMode) { |
211
|
|
|
case FetchMode::COLUMN: |
212
|
|
|
return $this->fetchColumn(); |
213
|
|
|
|
214
|
|
|
case FetchMode::ASSOCIATIVE: |
215
|
|
|
return sasql_fetch_assoc($this->result); |
216
|
|
|
|
217
|
|
|
case FetchMode::MIXED: |
218
|
|
|
return sasql_fetch_array($this->result, SASQL_BOTH); |
219
|
|
|
|
220
|
|
|
case FetchMode::CUSTOM_OBJECT: |
221
|
|
|
$className = $this->defaultFetchClass; |
222
|
|
|
$ctorArgs = $this->defaultFetchClassCtorArgs; |
223
|
|
|
|
224
|
|
|
if (func_num_args() >= 2) { |
225
|
|
|
$args = func_get_args(); |
226
|
|
|
$className = $args[1]; |
227
|
|
|
$ctorArgs = $args[2] ?? []; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
$result = sasql_fetch_object($this->result); |
231
|
|
|
|
232
|
|
|
if ($result instanceof stdClass) { |
233
|
|
|
$result = $this->castObject($result, $className, $ctorArgs); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
return $result; |
237
|
|
|
|
238
|
|
|
case FetchMode::NUMERIC: |
239
|
|
|
return sasql_fetch_row($this->result); |
240
|
|
|
|
241
|
|
|
case FetchMode::STANDARD_OBJECT: |
242
|
|
|
return sasql_fetch_object($this->result); |
243
|
|
|
|
244
|
|
|
default: |
245
|
|
|
throw new SQLAnywhereException('Fetch mode is not supported: ' . $fetchMode); |
246
|
|
|
} |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* {@inheritdoc} |
251
|
|
|
*/ |
252
|
|
|
public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) |
253
|
|
|
{ |
254
|
|
|
$rows = []; |
255
|
|
|
|
256
|
|
|
switch ($fetchMode) { |
257
|
|
|
case FetchMode::CUSTOM_OBJECT: |
258
|
|
|
while (($row = $this->fetch(...func_get_args())) !== false) { |
259
|
|
|
$rows[] = $row; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
break; |
263
|
|
|
|
264
|
|
|
case FetchMode::COLUMN: |
265
|
|
|
while (($row = $this->fetchColumn()) !== false) { |
266
|
|
|
$rows[] = $row; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
break; |
270
|
|
|
|
271
|
|
|
default: |
272
|
|
|
while (($row = $this->fetch($fetchMode)) !== false) { |
273
|
|
|
$rows[] = $row; |
274
|
|
|
} |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
return $rows; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* {@inheritdoc} |
282
|
|
|
*/ |
283
|
|
View Code Duplication |
public function fetchColumn($columnIndex = 0) |
|
|
|
|
284
|
|
|
{ |
285
|
|
|
$row = $this->fetch(FetchMode::NUMERIC); |
286
|
|
|
|
287
|
|
|
if ($row === false) { |
288
|
|
|
return false; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
return $row[$columnIndex] ?? null; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* {@inheritdoc} |
296
|
|
|
*/ |
297
|
2 |
|
public function getIterator() |
298
|
|
|
{ |
299
|
2 |
|
return new StatementIterator($this); |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* {@inheritdoc} |
304
|
|
|
*/ |
305
|
|
|
public function rowCount() |
306
|
|
|
{ |
307
|
|
|
return sasql_stmt_affected_rows($this->stmt); |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* {@inheritdoc} |
312
|
|
|
*/ |
313
|
|
|
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) |
314
|
|
|
{ |
315
|
|
|
$this->defaultFetchMode = $fetchMode; |
316
|
|
|
$this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass; |
317
|
|
|
$this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs; |
318
|
|
|
|
319
|
|
|
return true; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
/** |
323
|
|
|
* Casts a stdClass object to the given class name mapping its' properties. |
324
|
|
|
* |
325
|
|
|
* @param stdClass $sourceObject Object to cast from. |
326
|
|
|
* @param string|object $destinationClass Name of the class or class instance to cast to. |
327
|
|
|
* @param mixed[] $ctorArgs Arguments to use for constructing the destination class instance. |
328
|
|
|
* |
329
|
|
|
* @return object |
330
|
|
|
* |
331
|
|
|
* @throws SQLAnywhereException |
332
|
|
|
*/ |
333
|
|
|
private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = []) |
334
|
|
|
{ |
335
|
|
|
if (! is_string($destinationClass)) { |
336
|
|
|
if (! is_object($destinationClass)) { |
337
|
|
|
throw new SQLAnywhereException(sprintf( |
338
|
|
|
'Destination class has to be of type string or object, %s given.', |
339
|
|
|
gettype($destinationClass) |
340
|
|
|
)); |
341
|
|
|
} |
342
|
|
|
} else { |
343
|
|
|
$destinationClass = new ReflectionClass($destinationClass); |
344
|
|
|
$destinationClass = $destinationClass->newInstanceArgs($ctorArgs); |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
$sourceReflection = new ReflectionObject($sourceObject); |
348
|
|
|
$destinationClassReflection = new ReflectionObject($destinationClass); |
349
|
|
|
|
350
|
|
|
foreach ($sourceReflection->getProperties() as $sourceProperty) { |
351
|
|
|
$sourceProperty->setAccessible(true); |
352
|
|
|
|
353
|
|
|
$name = $sourceProperty->getName(); |
354
|
|
|
$value = $sourceProperty->getValue($sourceObject); |
355
|
|
|
|
356
|
|
|
if ($destinationClassReflection->hasProperty($name)) { |
357
|
|
|
$destinationProperty = $destinationClassReflection->getProperty($name); |
358
|
|
|
|
359
|
|
|
$destinationProperty->setAccessible(true); |
360
|
|
|
$destinationProperty->setValue($destinationClass, $value); |
361
|
|
|
} else { |
362
|
|
|
$destinationClass->$name = $value; |
363
|
|
|
} |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
return $destinationClass; |
367
|
|
|
} |
368
|
|
|
} |
369
|
|
|
|
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.