Failed Conditions
Push — master ( 091e26...b74a19 )
by Sergei
17s queued 12s
created

DB2Statement::execute()   B

Complexity

Conditions 8
Paths 25

Size

Total Lines 41
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 10.6235

Importance

Changes 0
Metric Value
eloc 20
dl 0
loc 41
ccs 19
cts 29
cp 0.6552
rs 8.4444
c 0
b 0
f 0
cc 8
nc 25
nop 1
crap 10.6235
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 error_get_last;
34
use function fclose;
35
use function func_get_args;
36
use function func_num_args;
37
use function fwrite;
38
use function gettype;
39
use function is_object;
40
use function is_resource;
41
use function is_string;
42
use function ksort;
43
use function sprintf;
44
use function stream_copy_to_stream;
45
use function stream_get_meta_data;
46
use function strtolower;
47
use function tmpfile;
48
49
class DB2Statement implements IteratorAggregate, Statement
50
{
51
    /** @var resource */
52
    private $stmt;
53
54
    /** @var mixed[] */
55
    private $bindParam = [];
56
57
    /**
58
     * Map of LOB parameter positions to the tuples containing reference to the variable bound to the driver statement
59
     * and the temporary file handle bound to the underlying statement
60
     *
61
     * @var mixed[][]
62
     */
63
    private $lobs = [];
64
65
    /** @var string Name of the default class to instantiate when fetching class instances. */
66
    private $defaultFetchClass = '\stdClass';
67
68
    /** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */
69
    private $defaultFetchClassCtorArgs = [];
70
71
    /** @var int */
72
    private $defaultFetchMode = FetchMode::MIXED;
73
74
    /**
75
     * Indicates whether the statement is in the state when fetching results is possible
76
     *
77
     * @var bool
78
     */
79
    private $result = false;
80
81
    /**
82
     * @param resource $stmt
83
     */
84 226
    public function __construct($stmt)
85
    {
86 226
        $this->stmt = $stmt;
87 226
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92 36
    public function bindValue($param, $value, $type = ParameterType::STRING)
93
    {
94 36
        return $this->bindParam($param, $value, $type);
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100 44
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
101
    {
102
        switch ($type) {
103 44
            case ParameterType::INTEGER:
104 24
                $this->bind($column, $variable, DB2_PARAM_IN, DB2_LONG);
105 24
                break;
106
107 39
            case ParameterType::LARGE_OBJECT:
108 7
                if (isset($this->lobs[$column])) {
109
                    [, $handle] = $this->lobs[$column];
110
                    fclose($handle);
111
                }
112
113 7
                $handle = $this->createTemporaryFile();
114 7
                $path   = stream_get_meta_data($handle)['uri'];
115
116 7
                $this->bind($column, $path, DB2_PARAM_FILE, DB2_BINARY);
117
118 7
                $this->lobs[$column] = [&$variable, $handle];
119 7
                break;
120
121
            default:
122 37
                $this->bind($column, $variable, DB2_PARAM_IN, DB2_CHAR);
123 37
                break;
124
        }
125
126 44
        return true;
127
    }
128
129
    /**
130
     * @param int|string $parameter Parameter position or name
131
     * @param mixed      $variable
132
     *
133
     * @throws DB2Exception
134
     */
135 44
    private function bind($parameter, &$variable, int $parameterType, int $dataType) : void
136
    {
137 44
        $this->bindParam[$parameter] =& $variable;
138
139 44
        if (! db2_bind_param($this->stmt, $parameter, 'variable', $parameterType, $dataType)) {
0 ignored issues
show
Bug introduced by
It seems like $parameter can also be of type string; however, parameter $parameter_number of db2_bind_param() does only seem to accept integer, 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

139
        if (! db2_bind_param($this->stmt, /** @scrutinizer ignore-type */ $parameter, 'variable', $parameterType, $dataType)) {
Loading history...
140
            throw new DB2Exception(db2_stmt_errormsg());
141
        }
142 44
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147 19
    public function closeCursor()
148
    {
149 19
        if (! $this->stmt) {
150
            return false;
151
        }
152
153 19
        $this->bindParam = [];
154
155 19
        if (! db2_free_result($this->stmt)) {
156
            return false;
157
        }
158
159 19
        $this->result = false;
160
161 19
        return true;
162
    }
163
164
    /**
165
     * {@inheritdoc}
166
     */
167 4
    public function columnCount()
168
    {
169 4
        if (! $this->stmt) {
170
            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...
171
        }
172
173 4
        return db2_num_fields($this->stmt);
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179
    public function errorCode()
180
    {
181
        return db2_stmt_error();
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187
    public function errorInfo()
188
    {
189
        return [
190
            db2_stmt_errormsg(),
191
            db2_stmt_error(),
192
        ];
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     */
198 218
    public function execute($params = null)
199
    {
200 218
        if (! $this->stmt) {
201
            return false;
202
        }
203
204 218
        if ($params === null) {
205 195
            ksort($this->bindParam);
206
207 195
            $params = [];
208
209 195
            foreach ($this->bindParam as $column => $value) {
210 44
                $params[] = $value;
211
            }
212
        }
213
214 218
        foreach ($this->lobs as [$source, $target]) {
215 7
            if (is_resource($source)) {
216 3
                $this->copyStreamToStream($source, $target);
217
218 3
                continue;
219
            }
220
221 5
            $this->writeStringToStream($source, $target);
222
        }
223
224 218
        $retval = db2_execute($this->stmt, $params);
225
226 213
        foreach ($this->lobs as [, $handle]) {
227 7
            fclose($handle);
228
        }
229
230 213
        $this->lobs = [];
231
232 213
        if ($retval === false) {
233
            throw new DB2Exception(db2_stmt_errormsg());
234
        }
235
236 213
        $this->result = true;
237
238 213
        return $retval;
239
    }
240
241
    /**
242
     * {@inheritdoc}
243
     */
244 217
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
245
    {
246 217
        $this->defaultFetchMode          = $fetchMode;
247 217
        $this->defaultFetchClass         = $arg2 ?: $this->defaultFetchClass;
248 217
        $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
249
250 217
        return true;
251
    }
252
253
    /**
254
     * {@inheritdoc}
255
     */
256 3
    public function getIterator()
257
    {
258 3
        return new StatementIterator($this);
259
    }
260
261
    /**
262
     * {@inheritdoc}
263
     */
264 204
    public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
265
    {
266
        // do not try fetching from the statement if it's not expected to contain result
267
        // in order to prevent exceptional situation
268 204
        if (! $this->result) {
269 9
            return false;
270
        }
271
272 195
        $fetchMode = $fetchMode ?: $this->defaultFetchMode;
273
        switch ($fetchMode) {
274 195
            case FetchMode::COLUMN:
275 1
                return $this->fetchColumn();
276
277 195
            case FetchMode::MIXED:
278 2
                return db2_fetch_both($this->stmt);
279
280 193
            case FetchMode::ASSOCIATIVE:
281 133
                return db2_fetch_assoc($this->stmt);
282
283 62
            case FetchMode::CUSTOM_OBJECT:
284 3
                $className = $this->defaultFetchClass;
285 3
                $ctorArgs  = $this->defaultFetchClassCtorArgs;
286
287 3
                if (func_num_args() >= 2) {
288 1
                    $args      = func_get_args();
289 1
                    $className = $args[1];
290 1
                    $ctorArgs  = $args[2] ?? [];
291
                }
292
293 3
                $result = db2_fetch_object($this->stmt);
294
295 3
                if ($result instanceof stdClass) {
296 3
                    $result = $this->castObject($result, $className, $ctorArgs);
297
                }
298
299 3
                return $result;
300
301 59
            case FetchMode::NUMERIC:
302 58
                return db2_fetch_array($this->stmt);
303
304 1
            case FetchMode::STANDARD_OBJECT:
305 1
                return db2_fetch_object($this->stmt);
306
307
            default:
308
                throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.');
309
        }
310
    }
311
312
    /**
313
     * {@inheritdoc}
314
     */
315 92
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
316
    {
317 92
        $rows = [];
318
319
        switch ($fetchMode) {
320 92
            case FetchMode::CUSTOM_OBJECT:
321 1
                while (($row = $this->fetch(...func_get_args())) !== false) {
322 1
                    $rows[] = $row;
323
                }
324 1
                break;
325 91
            case FetchMode::COLUMN:
326 9
                while (($row = $this->fetchColumn()) !== false) {
327 9
                    $rows[] = $row;
328
                }
329 9
                break;
330
            default:
331 82
                while (($row = $this->fetch($fetchMode)) !== false) {
332 75
                    $rows[] = $row;
333
                }
334
        }
335
336 92
        return $rows;
337
    }
338
339
    /**
340
     * {@inheritdoc}
341
     */
342 55
    public function fetchColumn($columnIndex = 0)
343
    {
344 55
        $row = $this->fetch(FetchMode::NUMERIC);
345
346 55
        if ($row === false) {
347 15
            return false;
348
        }
349
350 49
        return $row[$columnIndex] ?? null;
351
    }
352
353
    /**
354
     * {@inheritdoc}
355
     */
356 85
    public function rowCount()
357
    {
358 85
        return @db2_num_rows($this->stmt) ? : 0;
359
    }
360
361
    /**
362
     * Casts a stdClass object to the given class name mapping its' properties.
363
     *
364
     * @param stdClass      $sourceObject     Object to cast from.
365
     * @param string|object $destinationClass Name of the class or class instance to cast to.
366
     * @param mixed[]       $ctorArgs         Arguments to use for constructing the destination class instance.
367
     *
368
     * @return object
369
     *
370
     * @throws DB2Exception
371
     */
372 3
    private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
373
    {
374 3
        if (! is_string($destinationClass)) {
375
            if (! is_object($destinationClass)) {
376
                throw new DB2Exception(sprintf(
377
                    'Destination class has to be of type string or object, %s given.',
378
                    gettype($destinationClass)
379
                ));
380
            }
381
        } else {
382 3
            $destinationClass = new ReflectionClass($destinationClass);
383 3
            $destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
384
        }
385
386 3
        $sourceReflection           = new ReflectionObject($sourceObject);
387 3
        $destinationClassReflection = new ReflectionObject($destinationClass);
388
        /** @var ReflectionProperty[] $destinationProperties */
389 3
        $destinationProperties = array_change_key_case($destinationClassReflection->getProperties(), CASE_LOWER);
390
391 3
        foreach ($sourceReflection->getProperties() as $sourceProperty) {
392 3
            $sourceProperty->setAccessible(true);
393
394 3
            $name  = $sourceProperty->getName();
395 3
            $value = $sourceProperty->getValue($sourceObject);
396
397
            // Try to find a case-matching property.
398 3
            if ($destinationClassReflection->hasProperty($name)) {
399
                $destinationProperty = $destinationClassReflection->getProperty($name);
400
401
                $destinationProperty->setAccessible(true);
402
                $destinationProperty->setValue($destinationClass, $value);
403
404
                continue;
405
            }
406
407 3
            $name = strtolower($name);
408
409
            // Try to find a property without matching case.
410
            // Fallback for the driver returning either all uppercase or all lowercase column names.
411 3
            if (isset($destinationProperties[$name])) {
412
                $destinationProperty = $destinationProperties[$name];
413
414
                $destinationProperty->setAccessible(true);
415
                $destinationProperty->setValue($destinationClass, $value);
416
417
                continue;
418
            }
419
420 3
            $destinationClass->$name = $value;
421
        }
422
423 3
        return $destinationClass;
424
    }
425
426
    /**
427
     * @return resource
428
     *
429
     * @throws DB2Exception
430
     */
431 7
    private function createTemporaryFile()
432
    {
433 7
        $handle = @tmpfile();
434
435 7
        if ($handle === false) {
436
            throw new DB2Exception('Could not create temporary file: ' . error_get_last()['message']);
437
        }
438
439 7
        return $handle;
440
    }
441
442
    /**
443
     * @param resource $source
444
     * @param resource $target
445
     *
446
     * @throws DB2Exception
447
     */
448 3
    private function copyStreamToStream($source, $target) : void
449
    {
450 3
        if (@stream_copy_to_stream($source, $target) === false) {
451
            throw new DB2Exception('Could not copy source stream to temporary file: ' . error_get_last()['message']);
452
        }
453 3
    }
454
455
    /**
456
     * @param resource $target
457
     *
458
     * @throws DB2Exception
459
     */
460 5
    private function writeStringToStream(string $string, $target) : void
461
    {
462 5
        if (@fwrite($target, $string) === false) {
463
            throw new DB2Exception('Could not write string to temporary file: ' . error_get_last()['message']);
464
        }
465 5
    }
466
}
467