Failed Conditions
Pull Request — master (#3309)
by Sergei
63:12
created

DB2Statement::bindValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 1
cts 1
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
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 fclose;
34
use function func_get_args;
35
use function func_num_args;
36
use function fwrite;
37
use function gettype;
38
use function is_object;
39
use function is_resource;
40
use function is_string;
41
use function ksort;
42
use function sprintf;
43
use function stream_copy_to_stream;
44
use function stream_get_meta_data;
45
use function strtolower;
46
use function tmpfile;
47
48
class DB2Statement implements IteratorAggregate, Statement
49
{
50
    /** @var resource */
51
    private $stmt;
52
53
    /** @var mixed[] */
54
    private $bindParam = [];
55
56
    /**
57
     * Map of LOB parameter positions to the tuples containing reference to the variable bound to the driver statement
58
     * and the temporary file handle bound to the underlying statement
59
     *
60
     * @var mixed[][]
61
     */
62
    private $lobs = [];
63
64
    /** @var string Name of the default class to instantiate when fetching class instances. */
65
    private $defaultFetchClass = '\stdClass';
66
67
    /** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */
68
    private $defaultFetchClassCtorArgs = [];
69
70
    /** @var int */
71
    private $defaultFetchMode = FetchMode::MIXED;
72
73
    /**
74
     * Indicates whether the statement is in the state when fetching results is possible
75
     *
76
     * @var bool
77 223
     */
78
    private $result = false;
79 223
80 223
    /**
81
     * @param resource $stmt
82
     */
83
    public function __construct($stmt)
84
    {
85 34
        $this->stmt = $stmt;
86
    }
87 34
88
    /**
89
     * {@inheritdoc}
90
     */
91
    public function bindValue($param, $value, $type = ParameterType::STRING)
92
    {
93 41
        return $this->bindParam($param, $value, $type);
94
    }
95 41
96
    /**
97 41
     * {@inheritdoc}
98 39
     */
99
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
100 5
    {
101
        switch ($type) {
102
            case ParameterType::INTEGER:
103 41
                $this->bind($column, $variable, DB2_PARAM_IN, DB2_LONG);
104
                break;
105
106
            case ParameterType::LARGE_OBJECT:
107 41
                $handle              = tmpfile();
108
                $this->lobs[$column] = [&$variable, $handle];
109
110
                $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

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