Completed
Pull Request — master (#3309)
by Sergei
34:29 queued 31:35
created

DB2Statement::errorCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
crap 2
1
<?php
2
3
namespace Doctrine\DBAL\Driver\IBMDB2;
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 ReflectionProperty;
14
use stdClass;
15
use const CASE_LOWER;
16
use const DB2_BINARY;
17
use const DB2_CHAR;
18
use const DB2_LONG;
19
use const DB2_PARAM_FILE;
20
use const DB2_PARAM_IN;
21
use function array_change_key_case;
22
use function db2_bind_param;
23
use function db2_execute;
24
use function db2_fetch_array;
25
use function db2_fetch_assoc;
26
use function db2_fetch_both;
27
use function db2_fetch_object;
28
use function db2_free_result;
29
use function db2_num_fields;
30
use function db2_num_rows;
31
use function db2_stmt_error;
32
use function db2_stmt_errormsg;
33
use function func_get_args;
34
use function func_num_args;
35
use function fwrite;
36
use function gettype;
37
use function is_object;
38
use function is_resource;
39
use function is_string;
40
use function ksort;
41
use function sprintf;
42
use function stream_copy_to_stream;
43
use function stream_get_meta_data;
44
use function strtolower;
45
use function tmpfile;
46
47
class DB2Statement implements IteratorAggregate, Statement
48
{
49
    /** @var resource */
50
    private $stmt;
51
52
    /** @var mixed[] */
53
    private $bindParam = [];
54
55
    /**
56
     * Map of LOB parameter positions to the tuples containing reference to the variable bound to the driver statement
57
     * and the temporary file handle bound to the underlying statement
58
     *
59
     * @var mixed[][]
60
     */
61
    private $lobs = [];
62
63
    /** @var string Name of the default class to instantiate when fetching class instances. */
64
    private $defaultFetchClass = '\stdClass';
65
66
    /** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */
67
    private $defaultFetchClassCtorArgs = [];
68
69
    /** @var int */
70
    private $defaultFetchMode = FetchMode::MIXED;
71
72
    /**
73
     * Indicates whether the statement is in the state when fetching results is possible
74
     *
75
     * @var bool
76
     */
77
    private $result = false;
78
79
    /**
80
     * @param resource $stmt
81
     */
82 226
    public function __construct($stmt)
83
    {
84 226
        $this->stmt = $stmt;
85 226
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90 36
    public function bindValue($param, $value, $type = ParameterType::STRING)
91
    {
92 36
        return $this->bindParam($param, $value, $type);
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98 44
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
99
    {
100
        switch ($type) {
101 44
            case ParameterType::INTEGER:
102 24
                $this->bind($column, $variable, DB2_PARAM_IN, DB2_LONG);
103 24
                break;
104
105 39
            case ParameterType::LARGE_OBJECT:
106 7
                $handle              = tmpfile();
107 7
                $this->lobs[$column] = [&$variable, $handle];
108
109 7
                $path = stream_get_meta_data($handle)['uri'];
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $stream of stream_get_meta_data() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

109
                $path = stream_get_meta_data(/** @scrutinizer ignore-type */ $handle)['uri'];
Loading history...
110
111 7
                $this->bind($column, $path, DB2_PARAM_FILE, DB2_BINARY);
112 7
                break;
113
114
            default:
115 37
                $this->bind($column, $variable, DB2_PARAM_IN, DB2_CHAR);
116 37
                break;
117
        }
118
119 44
        return true;
120
    }
121
122
    /**
123
     * @throws DB2Exception
124
     */
125 44
    private function bind($column, &$variable, int $parameterType, int $dataType) : void
126
    {
127 44
        $this->bindParam[$column] =& $variable;
128
129 44
        if (! db2_bind_param($this->stmt, $column, 'variable', $parameterType, $dataType)) {
130
            throw new DB2Exception(db2_stmt_errormsg());
131
        }
132 44
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137 19
    public function closeCursor()
138
    {
139 19
        if (! $this->stmt) {
140
            return false;
141
        }
142
143 19
        $this->bindParam = [];
144
145 19
        if (! db2_free_result($this->stmt)) {
146
            return false;
147
        }
148
149 19
        $this->result = false;
150
151 19
        return true;
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157 4
    public function columnCount()
158
    {
159 4
        if (! $this->stmt) {
160
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the return type mandated by Doctrine\DBAL\Driver\Res...tatement::columnCount() of integer.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
161
        }
162
163 4
        return db2_num_fields($this->stmt);
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     */
169
    public function errorCode()
170
    {
171
        return db2_stmt_error();
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177
    public function errorInfo()
178
    {
179
        return [
180
            db2_stmt_errormsg(),
181
            db2_stmt_error(),
182
        ];
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188 218
    public function execute($params = null)
189
    {
190 218
        if (! $this->stmt) {
191
            return false;
192
        }
193
194 218
        if ($params === null) {
195 195
            ksort($this->bindParam);
196
197 195
            $params = [];
198
199 195
            foreach ($this->bindParam as $column => $value) {
200 44
                $params[] = $value;
201
            }
202
        }
203
204 218
        foreach ($this->lobs as [$source, $target]) {
205 7
            if (is_resource($source)) {
206 3
                stream_copy_to_stream($source, $target);
207
208 3
                continue;
209
            }
210
211 5
            fwrite($target, $source);
212
        }
213
214 218
        $retval = db2_execute($this->stmt, $params);
215
216
        // release temporary file handles
217 213
        $this->lobs = [];
218
219 213
        if ($retval === false) {
220
            throw new DB2Exception(db2_stmt_errormsg());
221
        }
222
223 213
        $this->result = true;
224
225 213
        return $retval;
226
    }
227
228
    /**
229
     * {@inheritdoc}
230
     */
231 217
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
232
    {
233 217
        $this->defaultFetchMode          = $fetchMode;
234 217
        $this->defaultFetchClass         = $arg2 ?: $this->defaultFetchClass;
235 217
        $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
236
237 217
        return true;
238
    }
239
240
    /**
241
     * {@inheritdoc}
242
     */
243 3
    public function getIterator()
244
    {
245 3
        return new StatementIterator($this);
246
    }
247
248
    /**
249
     * {@inheritdoc}
250
     */
251 204
    public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
252
    {
253
        // do not try fetching from the statement if it's not expected to contain result
254
        // in order to prevent exceptional situation
255 204
        if (! $this->result) {
256 9
            return false;
257
        }
258
259 195
        $fetchMode = $fetchMode ?: $this->defaultFetchMode;
260
        switch ($fetchMode) {
261 195
            case FetchMode::COLUMN:
262 1
                return $this->fetchColumn();
263
264 195
            case FetchMode::MIXED:
265 2
                return db2_fetch_both($this->stmt);
266
267 193
            case FetchMode::ASSOCIATIVE:
268 133
                return db2_fetch_assoc($this->stmt);
269
270 62
            case FetchMode::CUSTOM_OBJECT:
271 3
                $className = $this->defaultFetchClass;
272 3
                $ctorArgs  = $this->defaultFetchClassCtorArgs;
273
274 3
                if (func_num_args() >= 2) {
275 1
                    $args      = func_get_args();
276 1
                    $className = $args[1];
277 1
                    $ctorArgs  = $args[2] ?? [];
278
                }
279
280 3
                $result = db2_fetch_object($this->stmt);
281
282 3
                if ($result instanceof stdClass) {
283 3
                    $result = $this->castObject($result, $className, $ctorArgs);
284
                }
285
286 3
                return $result;
287
288 59
            case FetchMode::NUMERIC:
289 58
                return db2_fetch_array($this->stmt);
290
291 1
            case FetchMode::STANDARD_OBJECT:
292 1
                return db2_fetch_object($this->stmt);
293
294
            default:
295
                throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.');
296
        }
297
    }
298
299
    /**
300
     * {@inheritdoc}
301
     */
302 92
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
303
    {
304 92
        $rows = [];
305
306
        switch ($fetchMode) {
307 92
            case FetchMode::CUSTOM_OBJECT:
308 1
                while (($row = $this->fetch(...func_get_args())) !== false) {
309 1
                    $rows[] = $row;
310
                }
311 1
                break;
312 91
            case FetchMode::COLUMN:
313 9
                while (($row = $this->fetchColumn()) !== false) {
314 9
                    $rows[] = $row;
315
                }
316 9
                break;
317
            default:
318 82
                while (($row = $this->fetch($fetchMode)) !== false) {
319 75
                    $rows[] = $row;
320
                }
321
        }
322
323 92
        return $rows;
324
    }
325
326
    /**
327
     * {@inheritdoc}
328
     */
329 55
    public function fetchColumn($columnIndex = 0)
330
    {
331 55
        $row = $this->fetch(FetchMode::NUMERIC);
332
333 55
        if ($row === false) {
334 15
            return false;
335
        }
336
337 49
        return $row[$columnIndex] ?? null;
338
    }
339
340
    /**
341
     * {@inheritdoc}
342
     */
343 85
    public function rowCount()
344
    {
345 85
        return @db2_num_rows($this->stmt) ? : 0;
346
    }
347
348
    /**
349
     * Casts a stdClass object to the given class name mapping its' properties.
350
     *
351
     * @param stdClass      $sourceObject     Object to cast from.
352
     * @param string|object $destinationClass Name of the class or class instance to cast to.
353
     * @param mixed[]       $ctorArgs         Arguments to use for constructing the destination class instance.
354
     *
355
     * @return object
356
     *
357
     * @throws DB2Exception
358
     */
359 3
    private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
360
    {
361 3
        if (! is_string($destinationClass)) {
362
            if (! is_object($destinationClass)) {
363
                throw new DB2Exception(sprintf(
364
                    'Destination class has to be of type string or object, %s given.',
365
                    gettype($destinationClass)
366
                ));
367
            }
368
        } else {
369 3
            $destinationClass = new ReflectionClass($destinationClass);
370 3
            $destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
371
        }
372
373 3
        $sourceReflection           = new ReflectionObject($sourceObject);
374 3
        $destinationClassReflection = new ReflectionObject($destinationClass);
375
        /** @var ReflectionProperty[] $destinationProperties */
376 3
        $destinationProperties = array_change_key_case($destinationClassReflection->getProperties(), CASE_LOWER);
377
378 3
        foreach ($sourceReflection->getProperties() as $sourceProperty) {
379 3
            $sourceProperty->setAccessible(true);
380
381 3
            $name  = $sourceProperty->getName();
382 3
            $value = $sourceProperty->getValue($sourceObject);
383
384
            // Try to find a case-matching property.
385 3
            if ($destinationClassReflection->hasProperty($name)) {
386
                $destinationProperty = $destinationClassReflection->getProperty($name);
387
388
                $destinationProperty->setAccessible(true);
389
                $destinationProperty->setValue($destinationClass, $value);
390
391
                continue;
392
            }
393
394 3
            $name = strtolower($name);
395
396
            // Try to find a property without matching case.
397
            // Fallback for the driver returning either all uppercase or all lowercase column names.
398 3
            if (isset($destinationProperties[$name])) {
399
                $destinationProperty = $destinationProperties[$name];
400
401
                $destinationProperty->setAccessible(true);
402
                $destinationProperty->setValue($destinationClass, $value);
403
404
                continue;
405
            }
406
407 3
            $destinationClass->$name = $value;
408
        }
409
410 3
        return $destinationClass;
411
    }
412
}
413