Failed Conditions
Pull Request — master (#3309)
by Sergei
64:54
created

DB2Statement::getIterator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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