Completed
Push — master ( 8575c2...a53269 )
by Marco
02:31 queued 01:15
created

DB2Statement::__construct()   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 1
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
25
class DB2Statement implements \IteratorAggregate, Statement
26
{
27
    /**
28
     * @var resource
29
     */
30
    private $_stmt;
31
32
    /**
33
     * @var array
34
     */
35
    private $_bindParam = [];
36
37
    /**
38
     * @var string Name of the default class to instantiate when fetch mode is \PDO::FETCH_CLASS.
39
     */
40
    private $defaultFetchClass = '\stdClass';
41
42
    /**
43
     * @var string Constructor arguments for the default class to instantiate when fetch mode is \PDO::FETCH_CLASS.
44
     */
45
    private $defaultFetchClassCtorArgs = [];
46
47
    /**
48
     * @var integer
49
     */
50
    private $_defaultFetchMode = \PDO::FETCH_BOTH;
51
52
    /**
53
     * Indicates whether the statement is in the state when fetching results is possible
54
     *
55
     * @var bool
56
     */
57
    private $result = false;
58
59
    /**
60
     * DB2_BINARY, DB2_CHAR, DB2_DOUBLE, or DB2_LONG
61
     *
62
     * @var array
63
     */
64
    static private $_typeMap = [
65
        \PDO::PARAM_INT => DB2_LONG,
66
        \PDO::PARAM_STR => DB2_CHAR,
67
    ];
68
69
    /**
70
     * @param resource $stmt
71
     */
72
    public function __construct($stmt)
73
    {
74
        $this->_stmt = $stmt;
75
    }
76
77
    /**
78
     * {@inheritdoc}
79
     */
80
    public function bindValue($param, $value, $type = null)
81
    {
82
        return $this->bindParam($param, $value, $type);
83
    }
84
85
    /**
86
     * {@inheritdoc}
87
     */
88
    public function bindParam($column, &$variable, $type = null, $length = null)
89
    {
90
        $this->_bindParam[$column] =& $variable;
91
92
        if ($type && isset(self::$_typeMap[$type])) {
93
            $type = self::$_typeMap[$type];
94
        } else {
95
            $type = DB2_CHAR;
96
        }
97
98
        if (!db2_bind_param($this->_stmt, $column, "variable", DB2_PARAM_IN, $type)) {
99
            throw new DB2Exception(db2_stmt_errormsg());
100
        }
101
102
        return true;
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108
    public function closeCursor()
109
    {
110
        if ( ! $this->_stmt) {
111
            return false;
112
        }
113
114
        $this->_bindParam = [];
115
116
        if (!db2_free_result($this->_stmt)) {
117
            return false;
118
        }
119
120
        $this->result = false;
121
122
        return true;
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128
    public function columnCount()
129
    {
130
        if ( ! $this->_stmt) {
131
            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...
132
        }
133
134
        return db2_num_fields($this->_stmt);
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140
    public function errorCode()
141
    {
142
        return db2_stmt_error();
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148
    public function errorInfo()
149
    {
150
        return [
151
            db2_stmt_errormsg(),
152
            db2_stmt_error(),
153
        ];
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159
    public function execute($params = null)
160
    {
161
        if ( ! $this->_stmt) {
162
            return false;
163
        }
164
165
        if ($params === null) {
166
            ksort($this->_bindParam);
167
168
            $params = [];
169
170
            foreach ($this->_bindParam as $column => $value) {
171
                $params[] = $value;
172
            }
173
        }
174
175
        $retval = @db2_execute($this->_stmt, $params);
176
177
        if ($retval === false) {
178
            throw new DB2Exception(db2_stmt_errormsg());
179
        }
180
181
        $this->result = true;
182
183
        return $retval;
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
190
    {
191
        $this->_defaultFetchMode         = $fetchMode;
192
        $this->defaultFetchClass         = $arg2 ? $arg2 : $this->defaultFetchClass;
193
        $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...
194
195
        return true;
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201
    public function getIterator()
202
    {
203
        return new StatementIterator($this);
204
    }
205
206
    /**
207
     * {@inheritdoc}
208
     */
209
    public function fetch($fetchMode = null, $cursorOrientation = \PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
210
    {
211
        // do not try fetching from the statement if it's not expected to contain result
212
        // in order to prevent exceptional situation
213
        if (!$this->result) {
214
            return false;
215
        }
216
217
        $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
218
        switch ($fetchMode) {
219
            case \PDO::FETCH_COLUMN:
220
                return $this->fetchColumn();
221
222
            case \PDO::FETCH_BOTH:
223
                return db2_fetch_both($this->_stmt);
224
            case \PDO::FETCH_ASSOC:
225
                return db2_fetch_assoc($this->_stmt);
226
            case \PDO::FETCH_CLASS:
227
                $className = $this->defaultFetchClass;
228
                $ctorArgs  = $this->defaultFetchClassCtorArgs;
229
230
                if (func_num_args() >= 2) {
231
                    $args      = func_get_args();
232
                    $className = $args[1];
233
                    $ctorArgs  = $args[2] ?? [];
234
                }
235
236
                $result = db2_fetch_object($this->_stmt);
237
238
                if ($result instanceof \stdClass) {
239
                    $result = $this->castObject($result, $className, $ctorArgs);
0 ignored issues
show
Bug introduced by
It seems like $ctorArgs can also be of type string; however, parameter $ctorArgs of Doctrine\DBAL\Driver\IBM...Statement::castObject() does only seem to accept array, 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

239
                    $result = $this->castObject($result, $className, /** @scrutinizer ignore-type */ $ctorArgs);
Loading history...
240
                }
241
242
                return $result;
243
            case \PDO::FETCH_NUM:
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...
244
                return db2_fetch_array($this->_stmt);
245
            case \PDO::FETCH_OBJ:
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...
246
                return db2_fetch_object($this->_stmt);
247
            default:
248
                throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.');
249
        }
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
256
    {
257
        $rows = [];
258
259
        switch ($fetchMode) {
260
            case \PDO::FETCH_CLASS:
261
                while ($row = call_user_func_array([$this, 'fetch'], func_get_args())) {
262
                    $rows[] = $row;
263
                }
264
                break;
265
            case \PDO::FETCH_COLUMN:
266
                while ($row = $this->fetchColumn()) {
267
                    $rows[] = $row;
268
                }
269
                break;
270
            default:
271
                while ($row = $this->fetch($fetchMode)) {
272
                    $rows[] = $row;
273
                }
274
        }
275
276
        return $rows;
277
    }
278
279
    /**
280
     * {@inheritdoc}
281
     */
282
    public function fetchColumn($columnIndex = 0)
283
    {
284
        $row = $this->fetch(\PDO::FETCH_NUM);
285
286
        if (false === $row) {
287
            return false;
288
        }
289
290
        return $row[$columnIndex] ?? null;
291
    }
292
293
    /**
294
     * {@inheritdoc}
295
     */
296
    public function rowCount()
297
    {
298
        return (@db2_num_rows($this->_stmt)) ? : 0;
299
    }
300
301
    /**
302
     * Casts a stdClass object to the given class name mapping its' properties.
303
     *
304
     * @param \stdClass     $sourceObject     Object to cast from.
305
     * @param string|object $destinationClass Name of the class or class instance to cast to.
306
     * @param array         $ctorArgs         Arguments to use for constructing the destination class instance.
307
     *
308
     * @return object
309
     *
310
     * @throws DB2Exception
311
     */
312
    private function castObject(\stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
313
    {
314
        if ( ! is_string($destinationClass)) {
315
            if ( ! is_object($destinationClass)) {
316
                throw new DB2Exception(sprintf(
317
                    'Destination class has to be of type string or object, %s given.', gettype($destinationClass)
318
                ));
319
            }
320
        } else {
321
            $destinationClass = new \ReflectionClass($destinationClass);
322
            $destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
323
        }
324
325
        $sourceReflection           = new \ReflectionObject($sourceObject);
326
        $destinationClassReflection = new \ReflectionObject($destinationClass);
0 ignored issues
show
Bug introduced by
It seems like $destinationClass can also be of type string; however, parameter $argument of ReflectionObject::__construct() does only seem to accept object, 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

326
        $destinationClassReflection = new \ReflectionObject(/** @scrutinizer ignore-type */ $destinationClass);
Loading history...
327
        /** @var \ReflectionProperty[] $destinationProperties */
328
        $destinationProperties      = array_change_key_case($destinationClassReflection->getProperties(), \CASE_LOWER);
329
330
        foreach ($sourceReflection->getProperties() as $sourceProperty) {
331
            $sourceProperty->setAccessible(true);
332
333
            $name  = $sourceProperty->getName();
334
            $value = $sourceProperty->getValue($sourceObject);
335
336
            // Try to find a case-matching property.
337
            if ($destinationClassReflection->hasProperty($name)) {
338
                $destinationProperty = $destinationClassReflection->getProperty($name);
339
340
                $destinationProperty->setAccessible(true);
341
                $destinationProperty->setValue($destinationClass, $value);
0 ignored issues
show
Unused Code introduced by
The call to ReflectionProperty::setValue() has too many arguments starting with $value. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

341
                $destinationProperty->/** @scrutinizer ignore-call */ 
342
                                      setValue($destinationClass, $value);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
342
343
                continue;
344
            }
345
346
            $name = strtolower($name);
347
348
            // Try to find a property without matching case.
349
            // Fallback for the driver returning either all uppercase or all lowercase column names.
350
            if (isset($destinationProperties[$name])) {
351
                $destinationProperty = $destinationProperties[$name];
352
353
                $destinationProperty->setAccessible(true);
354
                $destinationProperty->setValue($destinationClass, $value);
355
356
                continue;
357
            }
358
359
            $destinationClass->$name = $value;
360
        }
361
362
        return $destinationClass;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $destinationClass also could return the type string which is incompatible with the documented return type object.
Loading history...
363
    }
364
}
365