Completed
Pull Request — 2.10.x (#4009)
by Grégoire
08:50
created

SQLAnywhereStatement::execute()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 0
cts 18
cp 0
rs 8.9457
c 0
b 0
f 0
cc 6
nc 4
nop 1
crap 42
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
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