Failed Conditions
Pull Request — master (#3319)
by Massimiliano
11:33
created

DB2Statement   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 334
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 49
eloc 124
dl 0
loc 334
ccs 0
cts 184
cp 0
rs 8.48
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A errorCode() 0 3 1
A errorInfo() 0 5 1
A columnCount() 0 7 2
A execute() 0 25 5
A setFetchMode() 0 7 3
A fetchColumn() 0 9 2
A bindParam() 0 15 4
A rowCount() 0 3 2
A closeCursor() 0 15 3
B castObject() 0 52 6
A getIterator() 0 3 1
A __construct() 0 3 1
A bindValue() 0 3 1
B fetch() 0 45 11
A fetchAll() 0 22 6

How to fix   Complexity   

Complex Class

Complex classes like DB2Statement often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DB2Statement, and based on these observations, apply Extract Interface, too.

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