Failed Conditions
Push — 3.0.x ( 66f057...14f5f1 )
by Sergei
36s queued 17s
created

DB2Statement::castObject()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 52
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 28
dl 0
loc 52
rs 8.8497
c 0
b 0
f 0
cc 6
nc 9
nop 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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