Failed Conditions
Push — fix/SQLAnywhere16Platform-exce... ( cea9c4 )
by Michael
24:04
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 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
    public function __construct($stmt)
99
    {
100
        $this->_stmt = $stmt;
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106
    public function bindValue($param, $value, $type = ParameterType::STRING)
107
    {
108
        return $this->bindParam($param, $value, $type);
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
115
    {
116
        $this->_bindParam[$column] =& $variable;
117
118
        if ($type && isset(self::$_typeMap[$type])) {
119
            $type = self::$_typeMap[$type];
120
        } else {
121
            $type = DB2_CHAR;
122
        }
123
124
        if (!db2_bind_param($this->_stmt, $column, "variable", DB2_PARAM_IN, $type)) {
125
            throw new DB2Exception(db2_stmt_errormsg());
126
        }
127
128
        return true;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134
    public function closeCursor()
135
    {
136
        if ( ! $this->_stmt) {
137
            return false;
138
        }
139
140
        $this->_bindParam = [];
141
142
        if (!db2_free_result($this->_stmt)) {
143
            return false;
144
        }
145
146
        $this->result = false;
147
148
        return true;
149
    }
150
151
    /**
152
     * {@inheritdoc}
153
     */
154
    public function columnCount()
155
    {
156
        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
        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
    public function execute($params = null)
186
    {
187
        if ( ! $this->_stmt) {
188
            return false;
189
        }
190
191
        if ($params === null) {
192
            ksort($this->_bindParam);
193
194
            $params = [];
195
196
            foreach ($this->_bindParam as $column => $value) {
197
                $params[] = $value;
198
            }
199
        }
200
201
        $retval = db2_execute($this->_stmt, $params);
202
203
        if ($retval === false) {
204
            throw new DB2Exception(db2_stmt_errormsg());
205
        }
206
207
        $this->result = true;
208
209
        return $retval;
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
216
    {
217
        $this->_defaultFetchMode         = $fetchMode;
218
        $this->defaultFetchClass         = $arg2 ? $arg2 : $this->defaultFetchClass;
219
        $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
        return true;
222
    }
223
224
    /**
225
     * {@inheritdoc}
226
     */
227
    public function getIterator()
228
    {
229
        return new StatementIterator($this);
230
    }
231
232
    /**
233
     * {@inheritdoc}
234
     */
235
    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
        if (!$this->result) {
240
            return false;
241
        }
242
243
        $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
244
        switch ($fetchMode) {
245
            case FetchMode::COLUMN:
246
                return $this->fetchColumn();
247
248
            case FetchMode::MIXED:
249
                return db2_fetch_both($this->_stmt);
250
251
            case FetchMode::ASSOCIATIVE:
252
                return db2_fetch_assoc($this->_stmt);
253
254
            case FetchMode::CUSTOM_OBJECT:
255
                $className = $this->defaultFetchClass;
256
                $ctorArgs  = $this->defaultFetchClassCtorArgs;
257
258
                if (func_num_args() >= 2) {
259
                    $args      = func_get_args();
260
                    $className = $args[1];
261
                    $ctorArgs  = $args[2] ?? [];
262
                }
263
264
                $result = db2_fetch_object($this->_stmt);
265
266
                if ($result instanceof \stdClass) {
267
                    $result = $this->castObject($result, $className, $ctorArgs);
268
                }
269
270
                return $result;
271
272
            case FetchMode::NUMERIC:
273
                return db2_fetch_array($this->_stmt);
274
275
            case FetchMode::STANDARD_OBJECT:
276
                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
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
287
    {
288
        $rows = [];
289
290
        switch ($fetchMode) {
291
            case FetchMode::CUSTOM_OBJECT:
292
                while (($row = $this->fetch(...func_get_args())) !== false) {
293
                    $rows[] = $row;
294
                }
295
                break;
296
            case FetchMode::COLUMN:
297
                while (($row = $this->fetchColumn()) !== false) {
298
                    $rows[] = $row;
299
                }
300
                break;
301
            default:
302
                while (($row = $this->fetch($fetchMode)) !== false) {
303
                    $rows[] = $row;
304
                }
305
        }
306
307
        return $rows;
308
    }
309
310
    /**
311
     * {@inheritdoc}
312
     */
313
    public function fetchColumn($columnIndex = 0)
314
    {
315
        $row = $this->fetch(FetchMode::NUMERIC);
316
317
        if (false === $row) {
318
            return false;
319
        }
320
321
        return $row[$columnIndex] ?? null;
322
    }
323
324
    /**
325
     * {@inheritdoc}
326
     */
327
    public function rowCount()
328
    {
329
        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
    private function castObject(\stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
344
    {
345
        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
            $destinationClass = new \ReflectionClass($destinationClass);
353
            $destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
354
        }
355
356
        $sourceReflection           = new \ReflectionObject($sourceObject);
357
        $destinationClassReflection = new \ReflectionObject($destinationClass);
358
        /** @var \ReflectionProperty[] $destinationProperties */
359
        $destinationProperties      = array_change_key_case($destinationClassReflection->getProperties(), \CASE_LOWER);
360
361
        foreach ($sourceReflection->getProperties() as $sourceProperty) {
362
            $sourceProperty->setAccessible(true);
363
364
            $name  = $sourceProperty->getName();
365
            $value = $sourceProperty->getValue($sourceObject);
366
367
            // Try to find a case-matching property.
368
            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
            $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
            if (isset($destinationProperties[$name])) {
382
                $destinationProperty = $destinationProperties[$name];
383
384
                $destinationProperty->setAccessible(true);
385
                $destinationProperty->setValue($destinationClass, $value);
386
387
                continue;
388
            }
389
390
            $destinationClass->$name = $value;
391
        }
392
393
        return $destinationClass;
394
    }
395
}
396