Passed
Push — update-export-ignores ( 979259 )
by Michael
23:02
created

DB2Statement::getIterator()   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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Driver\IBMDB2;
21
22
use Doctrine\DBAL\Driver\Statement;
23
use Doctrine\DBAL\Driver\StatementIterator;
24
use Doctrine\DBAL\FetchMode;
25
use Doctrine\DBAL\ParameterType;
26
use const DB2_CHAR;
27
use const DB2_LONG;
28
use const DB2_PARAM_IN;
29
use function array_change_key_case;
30
use function db2_bind_param;
31
use function db2_execute;
32
use function db2_fetch_array;
33
use function db2_fetch_assoc;
34
use function db2_fetch_both;
35
use function db2_fetch_object;
36
use function db2_free_result;
37
use function db2_num_fields;
38
use function db2_num_rows;
39
use function db2_stmt_error;
40
use function db2_stmt_errormsg;
41
use function func_get_args;
42
use function func_num_args;
43
use function gettype;
44
use function is_object;
45
use function is_string;
46
use function ksort;
47
use function sprintf;
48
use function strtolower;
49
50
class DB2Statement implements \IteratorAggregate, Statement
51
{
52
    /**
53
     * @var resource
54
     */
55
    private $_stmt;
56
57
    /**
58
     * @var array
59
     */
60
    private $_bindParam = [];
61
62
    /**
63
     * @var string Name of the default class to instantiate when fetching class instances.
64
     */
65
    private $defaultFetchClass = '\stdClass';
66
67
    /**
68
     * @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances.
69
     */
70
    private $defaultFetchClassCtorArgs = [];
71
72
    /**
73
     * @var int
74
     */
75
    private $_defaultFetchMode = FetchMode::MIXED;
76
77
    /**
78
     * Indicates whether the statement is in the state when fetching results is possible
79
     *
80
     * @var bool
81
     */
82
    private $result = false;
83
84
    /**
85
     * DB2_BINARY, DB2_CHAR, DB2_DOUBLE, or DB2_LONG
86
     *
87
     * @var array
88
     */
89
    static private $_typeMap = [
90
        ParameterType::INTEGER => DB2_LONG,
91
        ParameterType::STRING  => DB2_CHAR,
92
    ];
93
94
    /**
95
     * @param resource $stmt
96
     */
97
    public function __construct($stmt)
98
    {
99
        $this->_stmt = $stmt;
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function bindValue($param, $value, $type = ParameterType::STRING)
106
    {
107
        return $this->bindParam($param, $value, $type);
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
114
    {
115
        $this->_bindParam[$column] =& $variable;
116
117
        if ($type && isset(self::$_typeMap[$type])) {
118
            $type = self::$_typeMap[$type];
119
        } else {
120
            $type = DB2_CHAR;
121
        }
122
123
        if (!db2_bind_param($this->_stmt, $column, "variable", DB2_PARAM_IN, $type)) {
124
            throw new DB2Exception(db2_stmt_errormsg());
125
        }
126
127
        return true;
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133
    public function closeCursor()
134
    {
135
        if ( ! $this->_stmt) {
136
            return false;
137
        }
138
139
        $this->_bindParam = [];
140
141
        if (!db2_free_result($this->_stmt)) {
142
            return false;
143
        }
144
145
        $this->result = false;
146
147
        return true;
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153
    public function columnCount()
154
    {
155
        if ( ! $this->_stmt) {
156
            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...
157
        }
158
159
        return db2_num_fields($this->_stmt);
160
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165
    public function errorCode()
166
    {
167
        return db2_stmt_error();
168
    }
169
170
    /**
171
     * {@inheritdoc}
172
     */
173
    public function errorInfo()
174
    {
175
        return [
176
            db2_stmt_errormsg(),
177
            db2_stmt_error(),
178
        ];
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184
    public function execute($params = null)
185
    {
186
        if ( ! $this->_stmt) {
187
            return false;
188
        }
189
190
        if ($params === null) {
191
            ksort($this->_bindParam);
192
193
            $params = [];
194
195
            foreach ($this->_bindParam as $column => $value) {
196
                $params[] = $value;
197
            }
198
        }
199
200
        $retval = db2_execute($this->_stmt, $params);
201
202
        if ($retval === false) {
203
            throw new DB2Exception(db2_stmt_errormsg());
204
        }
205
206
        $this->result = true;
207
208
        return $retval;
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
215
    {
216
        $this->_defaultFetchMode         = $fetchMode;
217
        $this->defaultFetchClass         = $arg2 ?: $this->defaultFetchClass;
218
        $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
219
220
        return true;
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     */
226
    public function getIterator()
227
    {
228
        return new StatementIterator($this);
229
    }
230
231
    /**
232
     * {@inheritdoc}
233
     */
234
    public function fetch($fetchMode = null, $cursorOrientation = \PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
235
    {
236
        // do not try fetching from the statement if it's not expected to contain result
237
        // in order to prevent exceptional situation
238
        if (!$this->result) {
239
            return false;
240
        }
241
242
        $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
243
        switch ($fetchMode) {
244
            case FetchMode::COLUMN:
245
                return $this->fetchColumn();
246
247
            case FetchMode::MIXED:
248
                return db2_fetch_both($this->_stmt);
249
250
            case FetchMode::ASSOCIATIVE:
251
                return db2_fetch_assoc($this->_stmt);
252
253
            case FetchMode::CUSTOM_OBJECT:
254
                $className = $this->defaultFetchClass;
255
                $ctorArgs  = $this->defaultFetchClassCtorArgs;
256
257
                if (func_num_args() >= 2) {
258
                    $args      = func_get_args();
259
                    $className = $args[1];
260
                    $ctorArgs  = $args[2] ?? [];
261
                }
262
263
                $result = db2_fetch_object($this->_stmt);
264
265
                if ($result instanceof \stdClass) {
266
                    $result = $this->castObject($result, $className, $ctorArgs);
267
                }
268
269
                return $result;
270
271
            case FetchMode::NUMERIC:
272
                return db2_fetch_array($this->_stmt);
273
274
            case FetchMode::STANDARD_OBJECT:
275
                return db2_fetch_object($this->_stmt);
276
277
            default:
278
                throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.');
279
        }
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     */
285
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
286
    {
287
        $rows = [];
288
289
        switch ($fetchMode) {
290
            case FetchMode::CUSTOM_OBJECT:
291
                while (($row = $this->fetch(...func_get_args())) !== false) {
292
                    $rows[] = $row;
293
                }
294
                break;
295
            case FetchMode::COLUMN:
296
                while (($row = $this->fetchColumn()) !== false) {
297
                    $rows[] = $row;
298
                }
299
                break;
300
            default:
301
                while (($row = $this->fetch($fetchMode)) !== false) {
302
                    $rows[] = $row;
303
                }
304
        }
305
306
        return $rows;
307
    }
308
309
    /**
310
     * {@inheritdoc}
311
     */
312
    public function fetchColumn($columnIndex = 0)
313
    {
314
        $row = $this->fetch(FetchMode::NUMERIC);
315
316
        if (false === $row) {
317
            return false;
318
        }
319
320
        return $row[$columnIndex] ?? null;
321
    }
322
323
    /**
324
     * {@inheritdoc}
325
     */
326
    public function rowCount()
327
    {
328
        return (@db2_num_rows($this->_stmt)) ? : 0;
329
    }
330
331
    /**
332
     * Casts a stdClass object to the given class name mapping its' properties.
333
     *
334
     * @param \stdClass     $sourceObject     Object to cast from.
335
     * @param string|object $destinationClass Name of the class or class instance to cast to.
336
     * @param array         $ctorArgs         Arguments to use for constructing the destination class instance.
337
     *
338
     * @return object
339
     *
340
     * @throws DB2Exception
341
     */
342
    private function castObject(\stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
343
    {
344
        if ( ! is_string($destinationClass)) {
345
            if ( ! is_object($destinationClass)) {
346
                throw new DB2Exception(sprintf(
347
                    'Destination class has to be of type string or object, %s given.', gettype($destinationClass)
348
                ));
349
            }
350
        } else {
351
            $destinationClass = new \ReflectionClass($destinationClass);
352
            $destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
353
        }
354
355
        $sourceReflection           = new \ReflectionObject($sourceObject);
356
        $destinationClassReflection = new \ReflectionObject($destinationClass);
357
        /** @var \ReflectionProperty[] $destinationProperties */
358
        $destinationProperties      = array_change_key_case($destinationClassReflection->getProperties(), \CASE_LOWER);
359
360
        foreach ($sourceReflection->getProperties() as $sourceProperty) {
361
            $sourceProperty->setAccessible(true);
362
363
            $name  = $sourceProperty->getName();
364
            $value = $sourceProperty->getValue($sourceObject);
365
366
            // Try to find a case-matching property.
367
            if ($destinationClassReflection->hasProperty($name)) {
368
                $destinationProperty = $destinationClassReflection->getProperty($name);
369
370
                $destinationProperty->setAccessible(true);
371
                $destinationProperty->setValue($destinationClass, $value);
372
373
                continue;
374
            }
375
376
            $name = strtolower($name);
377
378
            // Try to find a property without matching case.
379
            // Fallback for the driver returning either all uppercase or all lowercase column names.
380
            if (isset($destinationProperties[$name])) {
381
                $destinationProperty = $destinationProperties[$name];
382
383
                $destinationProperty->setAccessible(true);
384
                $destinationProperty->setValue($destinationClass, $value);
385
386
                continue;
387
            }
388
389
            $destinationClass->$name = $value;
390
        }
391
392
        return $destinationClass;
393
    }
394
}
395