Completed
Push — master ( 1eba78...18908c )
by Sergei
62:05 queued 10s
created

src/Driver/SQLAnywhere/SQLAnywhereStatement.php (2 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Driver\SQLAnywhere;
6
7
use Doctrine\DBAL\Driver\Statement;
8
use Doctrine\DBAL\Driver\StatementIterator;
9
use Doctrine\DBAL\Exception\GetVariableType;
10
use Doctrine\DBAL\Exception\InvalidColumnIndex;
11
use Doctrine\DBAL\FetchMode;
12
use Doctrine\DBAL\ParameterType;
13
use IteratorAggregate;
14
use ReflectionClass;
15
use ReflectionObject;
16
use stdClass;
17
use function array_key_exists;
18
use function assert;
19
use function count;
20
use function is_int;
21
use function is_object;
22
use function is_resource;
23
use function is_string;
24
use function sasql_fetch_array;
25
use function sasql_fetch_assoc;
26
use function sasql_fetch_object;
0 ignored issues
show
The function sasql_fetch_object was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
27
use function sasql_fetch_row;
28
use function sasql_prepare;
29
use function sasql_stmt_affected_rows;
30
use function sasql_stmt_bind_param_ex;
31
use function sasql_stmt_execute;
32
use function sasql_stmt_field_count;
33
use function sasql_stmt_reset;
34
use function sasql_stmt_result_metadata;
35
use function sprintf;
36
use const SASQL_BOTH;
37
38
/**
39
 * SAP SQL Anywhere implementation of the Statement interface.
40
 */
41
final class SQLAnywhereStatement implements IteratorAggregate, Statement
42
{
43
    /** @var resource The connection resource. */
44
    private $conn;
45
46
    /** @var string Name of the default class to instantiate when fetching class instances. */
47
    private $defaultFetchClass = '\stdClass';
48
49
    /** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */
50
    private $defaultFetchClassCtorArgs = [];
51
52
    /** @var int Default fetch mode to use. */
53
    private $defaultFetchMode = FetchMode::MIXED;
54
55
    /** @var resource|null The result set resource to fetch. */
56
    private $result;
57
58
    /** @var resource The prepared SQL statement to execute. */
59
    private $stmt;
60
61
    /** @var mixed[] The references to bound parameter values. */
62
    private $boundValues = [];
63
64
    /**
65
     * Prepares given statement for given connection.
66
     *
67
     * @param resource $conn The connection resource to use.
68
     * @param string   $sql  The SQL statement to prepare.
69
     *
70
     * @throws SQLAnywhereException
71
     */
72
    public function __construct($conn, string $sql)
73
    {
74
        if (! is_resource($conn)) {
75
            throw new SQLAnywhereException(sprintf(
76
                'Invalid SQL Anywhere connection resource, %s given.',
77
                (new GetVariableType())->__invoke($conn)
78
            ));
79
        }
80
81
        $this->conn = $conn;
82
        $this->stmt = sasql_prepare($conn, $sql);
83
84
        if (! is_resource($this->stmt)) {
85
            throw SQLAnywhereException::fromSQLAnywhereError($conn);
86
        }
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     *
92
     * @throws SQLAnywhereException
93
     */
94
    public function bindParam($param, &$variable, int $type = ParameterType::STRING, ?int $length = null) : void
95
    {
96
        assert(is_int($param));
97
98
        switch ($type) {
99
            case ParameterType::INTEGER:
100
            case ParameterType::BOOLEAN:
101
                $type = 'i';
102
                break;
103
104
            case ParameterType::LARGE_OBJECT:
105
                $type = 'b';
106
                break;
107
108
            case ParameterType::NULL:
109
            case ParameterType::STRING:
110
            case ParameterType::BINARY:
111
                $type = 's';
112
                break;
113
114
            default:
115
                throw new SQLAnywhereException(sprintf('Unknown type %d.', $type));
116
        }
117
118
        $this->boundValues[$param] =& $variable;
119
120
        if (! sasql_stmt_bind_param_ex($this->stmt, $param - 1, $variable, $type, $variable === null)) {
121
            throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
122
        }
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128
    public function bindValue($param, $value, int $type = ParameterType::STRING) : void
129
    {
130
        $this->bindParam($param, $value, $type);
131
    }
132
133
    public function closeCursor() : void
134
    {
135
        sasql_stmt_reset($this->stmt);
136
    }
137
138
    public function columnCount() : int
139
    {
140
        return sasql_stmt_field_count($this->stmt);
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     *
146
     * @throws SQLAnywhereException
147
     */
148
    public function execute(?array $params = null) : void
149
    {
150
        if ($params !== null) {
151
            $hasZeroIndex = array_key_exists(0, $params);
152
153
            foreach ($params as $key => $val) {
154
                if ($hasZeroIndex && is_int($key)) {
155
                    $this->bindValue($key + 1, $val);
156
                } else {
157
                    $this->bindValue($key, $val);
158
                }
159
            }
160
        }
161
162
        if (! sasql_stmt_execute($this->stmt)) {
163
            throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
164
        }
165
166
        $this->result = sasql_stmt_result_metadata($this->stmt);
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     *
172
     * @throws SQLAnywhereException
173
     */
174
    public function fetch(?int $fetchMode = null, ...$args)
175
    {
176
        if (! is_resource($this->result)) {
177
            return false;
178
        }
179
180
        $fetchMode = $fetchMode ?? $this->defaultFetchMode;
181
182
        switch ($fetchMode) {
183
            case FetchMode::COLUMN:
184
                return $this->fetchColumn();
185
186
            case FetchMode::ASSOCIATIVE:
187
                return sasql_fetch_assoc($this->result);
188
189
            case FetchMode::MIXED:
190
                return sasql_fetch_array($this->result, SASQL_BOTH);
191
192
            case FetchMode::CUSTOM_OBJECT:
193
                $className = $this->defaultFetchClass;
194
                $ctorArgs  = $this->defaultFetchClassCtorArgs;
195
196
                if (count($args) > 0) {
197
                    $className = $args[0];
198
                    $ctorArgs  = $args[1] ?? [];
199
                }
200
201
                $result = sasql_fetch_object($this->result);
0 ignored issues
show
The function sasql_fetch_object was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

201
                $result = /** @scrutinizer ignore-call */ sasql_fetch_object($this->result);
Loading history...
202
203
                if ($result instanceof stdClass) {
204
                    $result = $this->castObject($result, $className, $ctorArgs);
205
                }
206
207
                return $result;
208
209
            case FetchMode::NUMERIC:
210
                return sasql_fetch_row($this->result);
211
212
            case FetchMode::STANDARD_OBJECT:
213
                return sasql_fetch_object($this->result);
214
215
            default:
216
                throw new SQLAnywhereException(sprintf('Fetch mode is not supported %d.', $fetchMode));
217
        }
218
    }
219
220
    /**
221
     * {@inheritdoc}
222
     */
223
    public function fetchAll(?int $fetchMode = null, ...$args) : array
224
    {
225
        $rows = [];
226
227
        switch ($fetchMode) {
228
            case FetchMode::CUSTOM_OBJECT:
229
                while (($row = $this->fetch($fetchMode, ...$args)) !== false) {
230
                    $rows[] = $row;
231
                }
232
233
                break;
234
235
            case FetchMode::COLUMN:
236
                while (($row = $this->fetchColumn()) !== false) {
237
                    $rows[] = $row;
238
                }
239
240
                break;
241
242
            default:
243
                while (($row = $this->fetch($fetchMode)) !== false) {
244
                    $rows[] = $row;
245
                }
246
        }
247
248
        return $rows;
249
    }
250
251
    /**
252
     * {@inheritdoc}
253
     */
254
    public function fetchColumn(int $columnIndex = 0)
255
    {
256
        $row = $this->fetch(FetchMode::NUMERIC);
257
258
        if ($row === false) {
259
            return false;
260
        }
261
262
        if (! array_key_exists($columnIndex, $row)) {
263
            throw InvalidColumnIndex::new($columnIndex, count($row));
264
        }
265
266
        return $row[$columnIndex];
267
    }
268
269
    /**
270
     * {@inheritdoc}
271
     */
272
    public function getIterator()
273
    {
274
        return new StatementIterator($this);
275
    }
276
277
    public function rowCount() : int
278
    {
279
        return sasql_stmt_affected_rows($this->stmt);
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     */
285
    public function setFetchMode(int $fetchMode, ...$args) : void
286
    {
287
        $this->defaultFetchMode = $fetchMode;
288
289
        if (isset($args[0])) {
290
            $this->defaultFetchClass = $args[0];
291
        }
292
293
        if (! isset($args[1])) {
294
            return;
295
        }
296
297
        $this->defaultFetchClassCtorArgs = (array) $args[1];
298
    }
299
300
    /**
301
     * Casts a stdClass object to the given class name mapping its' properties.
302
     *
303
     * @param stdClass      $sourceObject     Object to cast from.
304
     * @param string|object $destinationClass Name of the class or class instance to cast to.
305
     * @param mixed[]       $ctorArgs         Arguments to use for constructing the destination class instance.
306
     *
307
     * @throws SQLAnywhereException
308
     */
309
    private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = []) : object
310
    {
311
        if (! is_string($destinationClass)) {
312
            if (! is_object($destinationClass)) {
313
                throw new SQLAnywhereException(sprintf(
314
                    'Destination class has to be of type string or object, "%s" given.',
315
                    (new GetVariableType())->__invoke($destinationClass)
316
                ));
317
            }
318
        } else {
319
            $destinationClass = new ReflectionClass($destinationClass);
320
            $destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
321
        }
322
323
        $sourceReflection           = new ReflectionObject($sourceObject);
324
        $destinationClassReflection = new ReflectionObject($destinationClass);
325
326
        foreach ($sourceReflection->getProperties() as $sourceProperty) {
327
            $sourceProperty->setAccessible(true);
328
329
            $name  = $sourceProperty->getName();
330
            $value = $sourceProperty->getValue($sourceObject);
331
332
            if ($destinationClassReflection->hasProperty($name)) {
333
                $destinationProperty = $destinationClassReflection->getProperty($name);
334
335
                $destinationProperty->setAccessible(true);
336
                $destinationProperty->setValue($destinationClass, $value);
337
            } else {
338
                $destinationClass->$name = $value;
339
            }
340
        }
341
342
        return $destinationClass;
343
    }
344
}
345