Completed
Push — ci-mysql8 ( 306e40...6f2209 )
by Sergei
02:42
created

DB2Statement::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1.037

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 3
cp 0.6667
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
crap 1.037
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 223
    public function __construct($stmt)
99
    {
100 223
        $this->_stmt = $stmt;
101 223
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106 34
    public function bindValue($param, $value, $type = ParameterType::STRING)
107
    {
108 34
        return $this->bindParam($param, $value, $type);
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114 41
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
115
    {
116 41
        $this->_bindParam[$column] =& $variable;
117
118 41
        if ($type && isset(self::$_typeMap[$type])) {
119 39
            $type = self::$_typeMap[$type];
120
        } else {
121 5
            $type = DB2_CHAR;
122
        }
123
124 41
        if (!db2_bind_param($this->_stmt, $column, "variable", DB2_PARAM_IN, $type)) {
125
            throw new DB2Exception(db2_stmt_errormsg());
126
        }
127
128 41
        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 215
    public function execute($params = null)
186
    {
187 215
        if ( ! $this->_stmt) {
188
            return false;
189
        }
190
191 215
        if ($params === null) {
192 192
            ksort($this->_bindParam);
193
194 192
            $params = [];
195
196 192
            foreach ($this->_bindParam as $column => $value) {
197 41
                $params[] = $value;
198
            }
199
        }
200
201 215
        $retval = db2_execute($this->_stmt, $params);
202
203 210
        if ($retval === false) {
204
            throw new DB2Exception(db2_stmt_errormsg());
205
        }
206
207 210
        $this->result = true;
208
209 210
        return $retval;
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215 214
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
216
    {
217 214
        $this->_defaultFetchMode         = $fetchMode;
218 214
        $this->defaultFetchClass         = $arg2 ? $arg2 : $this->defaultFetchClass;
219 214
        $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 214
        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 201
    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 201
        if (!$this->result) {
240 9
            return false;
241
        }
242
243 192
        $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
244
        switch ($fetchMode) {
245 192
            case FetchMode::COLUMN:
246 1
                return $this->fetchColumn();
247
248 192
            case FetchMode::MIXED:
249 2
                return db2_fetch_both($this->_stmt);
250
251 190
            case FetchMode::ASSOCIATIVE:
252 135
                return db2_fetch_assoc($this->_stmt);
253
254 57
            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 54
            case FetchMode::NUMERIC:
273 53
                return db2_fetch_array($this->_stmt);
274
275 1
            case FetchMode::STANDARD_OBJECT:
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 89
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
287
    {
288 89
        $rows = [];
289
290
        switch ($fetchMode) {
291 89
            case FetchMode::CUSTOM_OBJECT:
292 1
                while (($row = $this->fetch(...func_get_args())) !== false) {
293 1
                    $rows[] = $row;
294
                }
295 1
                break;
296 88
            case FetchMode::COLUMN:
297 4
                while (($row = $this->fetchColumn()) !== false) {
298 4
                    $rows[] = $row;
299
                }
300 4
                break;
301
            default:
302 84
                while (($row = $this->fetch($fetchMode)) !== false) {
303 77
                    $rows[] = $row;
304
                }
305
        }
306
307 89
        return $rows;
308
    }
309
310
    /**
311
     * {@inheritdoc}
312
     */
313 50
    public function fetchColumn($columnIndex = 0)
314
    {
315 50
        $row = $this->fetch(FetchMode::NUMERIC);
316
317 50
        if (false === $row) {
318 10
            return false;
319
        }
320
321 44
        return $row[$columnIndex] ?? null;
322
    }
323
324
    /**
325
     * {@inheritdoc}
326
     */
327 83
    public function rowCount()
328
    {
329 83
        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