Passed
Pull Request — master (#3133)
by Michael
17:35
created

DB2Statement::fetchAll()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 7.5435

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 13
cts 20
cp 0.65
rs 8.6737
c 0
b 0
f 0
cc 6
eloc 14
nc 6
nop 3
crap 7.5435
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 call_user_func_array;
31
use function db2_bind_param;
32
use function db2_execute;
33
use function db2_fetch_array;
34
use function db2_fetch_assoc;
35
use function db2_fetch_both;
36
use function db2_fetch_object;
37
use function db2_free_result;
38
use function db2_num_fields;
39
use function db2_num_rows;
40
use function db2_stmt_error;
41
use function db2_stmt_errormsg;
42
use function func_get_args;
43
use function func_num_args;
44
use function gettype;
45
use function is_object;
46
use function is_string;
47
use function ksort;
48
use function sprintf;
49
use function strtolower;
50
51
class DB2Statement implements \IteratorAggregate, Statement
52
{
53
    /**
54
     * @var resource
55
     */
56
    private $_stmt;
57
58
    /**
59
     * @var array
60
     */
61
    private $_bindParam = [];
62
63
    /**
64
     * @var string Name of the default class to instantiate when fetching class instances.
65
     */
66
    private $defaultFetchClass = '\stdClass';
67
68
    /**
69
     * @var string Constructor arguments for the default class to instantiate when fetching class instances.
70
     */
71
    private $defaultFetchClassCtorArgs = [];
72
73
    /**
74
     * @var int
75
     */
76
    private $_defaultFetchMode = FetchMode::MIXED;
77
78
    /**
79
     * Indicates whether the statement is in the state when fetching results is possible
80
     *
81
     * @var bool
82
     */
83
    private $result = false;
84
85
    /**
86
     * DB2_BINARY, DB2_CHAR, DB2_DOUBLE, or DB2_LONG
87
     *
88
     * @var array
89
     */
90
    static private $_typeMap = [
91
        ParameterType::INTEGER => DB2_LONG,
92
        ParameterType::STRING  => DB2_CHAR,
93
    ];
94
95
    /**
96
     * @param resource $stmt
97
     */
98 221
    public function __construct($stmt)
99
    {
100 221
        $this->_stmt = $stmt;
101 221
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106 33
    public function bindValue($param, $value, $type = ParameterType::STRING)
107
    {
108 33
        return $this->bindParam($param, $value, $type);
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114 40
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
115
    {
116 40
        $this->_bindParam[$column] =& $variable;
117
118 40
        if ($type && isset(self::$_typeMap[$type])) {
119 39
            $type = self::$_typeMap[$type];
120
        } else {
121 4
            $type = DB2_CHAR;
122
        }
123
124 40
        if (!db2_bind_param($this->_stmt, $column, "variable", DB2_PARAM_IN, $type)) {
125
            throw new DB2Exception(db2_stmt_errormsg());
126
        }
127
128 40
        return true;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 19
    public function closeCursor()
135
    {
136 19
        if ( ! $this->_stmt) {
137
            return false;
138
        }
139
140 19
        $this->_bindParam = [];
141
142 19
        if (!db2_free_result($this->_stmt)) {
143
            return false;
144
        }
145
146 19
        $this->result = false;
147
148 19
        return true;
149
    }
150
151
    /**
152
     * {@inheritdoc}
153
     */
154 4
    public function columnCount()
155
    {
156 4
        if ( ! $this->_stmt) {
157
            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...
158
        }
159
160 4
        return db2_num_fields($this->_stmt);
161
    }
162
163
    /**
164
     * {@inheritdoc}
165
     */
166
    public function errorCode()
167
    {
168
        return db2_stmt_error();
169
    }
170
171
    /**
172
     * {@inheritdoc}
173
     */
174
    public function errorInfo()
175
    {
176
        return [
177
            db2_stmt_errormsg(),
178
            db2_stmt_error(),
179
        ];
180
    }
181
182
    /**
183
     * {@inheritdoc}
184
     */
185 213
    public function execute($params = null)
186
    {
187 213
        if ( ! $this->_stmt) {
188
            return false;
189
        }
190
191 213
        if ($params === null) {
192 190
            ksort($this->_bindParam);
193
194 190
            $params = [];
195
196 190
            foreach ($this->_bindParam as $column => $value) {
197 40
                $params[] = $value;
198
            }
199
        }
200
201 213
        $retval = db2_execute($this->_stmt, $params);
202
203 208
        if ($retval === false) {
204
            throw new DB2Exception(db2_stmt_errormsg());
205
        }
206
207 208
        $this->result = true;
208
209 208
        return $retval;
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215 212
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
216
    {
217 212
        $this->_defaultFetchMode         = $fetchMode;
218 212
        $this->defaultFetchClass         = $arg2 ? $arg2 : $this->defaultFetchClass;
219 212
        $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
0 ignored issues
show
Documentation Bug introduced by
It seems like $arg3 ? (array)$arg3 : $...faultFetchClassCtorArgs can also be of type array. However, the property $defaultFetchClassCtorArgs is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
220
221 212
        return true;
222
    }
223
224
    /**
225
     * {@inheritdoc}
226
     */
227 3
    public function getIterator()
228
    {
229 3
        return new StatementIterator($this);
230
    }
231
232
    /**
233
     * {@inheritdoc}
234
     */
235 199
    public function fetch($fetchMode = null, $cursorOrientation = \PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
236
    {
237
        // do not try fetching from the statement if it's not expected to contain result
238
        // in order to prevent exceptional situation
239 199
        if (!$this->result) {
240 9
            return false;
241
        }
242
243 190
        $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
244
        switch ($fetchMode) {
245 190
            case FetchMode::COLUMN:
246 1
                return $this->fetchColumn();
247
248 190
            case FetchMode::MIXED:
249 2
                return db2_fetch_both($this->_stmt);
250
251 188
            case FetchMode::ASSOCIATIVE:
252 134
                return db2_fetch_assoc($this->_stmt);
253
254 55
            case FetchMode::CUSTOM_OBJECT:
255 3
                $className = $this->defaultFetchClass;
256 3
                $ctorArgs  = $this->defaultFetchClassCtorArgs;
257
258 3
                if (func_num_args() >= 2) {
259 1
                    $args      = func_get_args();
260 1
                    $className = $args[1];
261 1
                    $ctorArgs  = $args[2] ?? [];
262
                }
263
264 3
                $result = db2_fetch_object($this->_stmt);
265
266 3
                if ($result instanceof \stdClass) {
267 3
                    $result = $this->castObject($result, $className, $ctorArgs);
268
                }
269
270 3
                return $result;
271
272 52
            case FetchMode::NUMERIC:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
273 51
                return db2_fetch_array($this->_stmt);
274
275 1
            case FetchMode::STANDARD_OBJECT:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
276 1
                return db2_fetch_object($this->_stmt);
277
278
            default:
279
                throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.');
280
        }
281
    }
282
283
    /**
284
     * {@inheritdoc}
285
     */
286 88
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
287
    {
288 88
        $rows = [];
289
290
        switch ($fetchMode) {
291 88
            case FetchMode::CUSTOM_OBJECT:
292 1
                while (($row = $this->fetch(...func_get_args())) !== false) {
293 1
                    $rows[] = $row;
294
                }
295 1
                break;
296 87
            case FetchMode::COLUMN:
297 4
                while (($row = $this->fetchColumn()) !== false) {
298 4
                    $rows[] = $row;
299
                }
300 4
                break;
301
            default:
302 83
                while (($row = $this->fetch($fetchMode)) !== false) {
303 76
                    $rows[] = $row;
304
                }
305
        }
306
307 88
        return $rows;
308
    }
309
310
    /**
311
     * {@inheritdoc}
312
     */
313 48
    public function fetchColumn($columnIndex = 0)
314
    {
315 48
        $row = $this->fetch(FetchMode::NUMERIC);
316
317 48
        if (false === $row) {
318 10
            return false;
319
        }
320
321 42
        return $row[$columnIndex] ?? null;
322
    }
323
324
    /**
325
     * {@inheritdoc}
326
     */
327 81
    public function rowCount()
328
    {
329 81
        return (@db2_num_rows($this->_stmt)) ? : 0;
330
    }
331
332
    /**
333
     * Casts a stdClass object to the given class name mapping its' properties.
334
     *
335
     * @param \stdClass     $sourceObject     Object to cast from.
336
     * @param string|object $destinationClass Name of the class or class instance to cast to.
337
     * @param array         $ctorArgs         Arguments to use for constructing the destination class instance.
338
     *
339
     * @return object
340
     *
341
     * @throws DB2Exception
342
     */
343 3
    private function castObject(\stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
344
    {
345 3
        if ( ! is_string($destinationClass)) {
346
            if ( ! is_object($destinationClass)) {
347
                throw new DB2Exception(sprintf(
348
                    'Destination class has to be of type string or object, %s given.', gettype($destinationClass)
349
                ));
350
            }
351
        } else {
352 3
            $destinationClass = new \ReflectionClass($destinationClass);
353 3
            $destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
354
        }
355
356 3
        $sourceReflection           = new \ReflectionObject($sourceObject);
357 3
        $destinationClassReflection = new \ReflectionObject($destinationClass);
358
        /** @var \ReflectionProperty[] $destinationProperties */
359 3
        $destinationProperties      = array_change_key_case($destinationClassReflection->getProperties(), \CASE_LOWER);
360
361 3
        foreach ($sourceReflection->getProperties() as $sourceProperty) {
362 3
            $sourceProperty->setAccessible(true);
363
364 3
            $name  = $sourceProperty->getName();
365 3
            $value = $sourceProperty->getValue($sourceObject);
366
367
            // Try to find a case-matching property.
368 3
            if ($destinationClassReflection->hasProperty($name)) {
369
                $destinationProperty = $destinationClassReflection->getProperty($name);
370
371
                $destinationProperty->setAccessible(true);
372
                $destinationProperty->setValue($destinationClass, $value);
373
374
                continue;
375
            }
376
377 3
            $name = strtolower($name);
378
379
            // Try to find a property without matching case.
380
            // Fallback for the driver returning either all uppercase or all lowercase column names.
381 3
            if (isset($destinationProperties[$name])) {
382
                $destinationProperty = $destinationProperties[$name];
383
384
                $destinationProperty->setAccessible(true);
385
                $destinationProperty->setValue($destinationClass, $value);
386
387
                continue;
388
            }
389
390 3
            $destinationClass->$name = $value;
391
        }
392
393 3
        return $destinationClass;
394
    }
395
}
396