Completed
Pull Request — master (#3610)
by Sergei
06:29
created

SQLAnywhereStatement   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 323
Duplicated Lines 3.1 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 1.1%

Importance

Changes 0
Metric Value
wmc 52
lcom 1
cbo 2
dl 10
loc 323
ccs 2
cts 182
cp 0.011
rs 7.44
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 3
B bindParam() 0 30 8
A bindValue() 0 4 1
A closeCursor() 0 8 2
A columnCount() 0 4 1
A errorCode() 0 4 1
A errorInfo() 0 4 1
B execute() 0 22 6
B fetch() 0 46 11
B fetchAll() 0 25 6
A fetchColumn() 10 10 2
A getIterator() 0 4 1
A rowCount() 0 4 1
A setFetchMode() 0 6 3
A castObject() 0 35 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SQLAnywhereStatement 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 SQLAnywhereStatement, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Doctrine\DBAL\Driver\SQLAnywhere;
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 stdClass;
14
use const SASQL_BOTH;
15
use function array_key_exists;
16
use function func_get_args;
17
use function func_num_args;
18
use function gettype;
19
use function is_array;
20
use function is_int;
21
use function is_object;
22
use function is_resource;
23
use function is_string;
24
use function sasql_fetch_array;
25
use function sasql_fetch_assoc;
26
use function sasql_fetch_object;
27
use function sasql_fetch_row;
28
use function sasql_prepare;
29
use function sasql_stmt_affected_rows;
30
use function sasql_stmt_bind_param_ex;
31
use function sasql_stmt_errno;
32
use function sasql_stmt_error;
33
use function sasql_stmt_execute;
34
use function sasql_stmt_field_count;
35
use function sasql_stmt_reset;
36
use function sasql_stmt_result_metadata;
37
use function sprintf;
38
39
/**
40
 * SAP SQL Anywhere implementation of the Statement interface.
41
 */
42
class SQLAnywhereStatement implements IteratorAggregate, Statement
43
{
44
    /** @var resource The connection resource. */
45
    private $conn;
46
47
    /** @var string Name of the default class to instantiate when fetching class instances. */
48
    private $defaultFetchClass = '\stdClass';
49
50
    /** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */
51
    private $defaultFetchClassCtorArgs = [];
52
53
    /** @var int Default fetch mode to use. */
54
    private $defaultFetchMode = FetchMode::MIXED;
55
56
    /** @var resource The result set resource to fetch. */
57
    private $result;
58
59
    /** @var resource The prepared SQL statement to execute. */
60
    private $stmt;
61
62
    /** @var mixed[] The references to bound parameter values. */
63
    private $boundValues = [];
64
65
    /**
66
     * Prepares given statement for given connection.
67
     *
68
     * @param resource $conn The connection resource to use.
69
     * @param string   $sql  The SQL statement to prepare.
70
     *
71
     * @throws SQLAnywhereException
72
     */
73
    public function __construct($conn, $sql)
74
    {
75
        if (! is_resource($conn)) {
76
            throw new SQLAnywhereException('Invalid SQL Anywhere connection resource: ' . $conn);
77
        }
78
79
        $this->conn = $conn;
80
        $this->stmt = sasql_prepare($conn, $sql);
81
82
        if (! is_resource($this->stmt)) {
83
            throw SQLAnywhereException::fromSQLAnywhereError($conn);
84
        }
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     *
90
     * @throws SQLAnywhereException
91
     */
92
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
93
    {
94
        switch ($type) {
95
            case ParameterType::INTEGER:
96
            case ParameterType::BOOLEAN:
97
                $type = 'i';
98
                break;
99
100
            case ParameterType::LARGE_OBJECT:
101
                $type = 'b';
102
                break;
103
104
            case ParameterType::NULL:
105
            case ParameterType::STRING:
106
            case ParameterType::BINARY:
107
                $type = 's';
108
                break;
109
110
            default:
111
                throw new SQLAnywhereException('Unknown type: ' . $type);
112
        }
113
114
        $this->boundValues[$column] =& $variable;
115
116
        if (! sasql_stmt_bind_param_ex($this->stmt, $column - 1, $variable, $type, $variable === null)) {
117
            throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
118
        }
119
120
        return true;
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function bindValue($param, $value, $type = ParameterType::STRING)
127
    {
128
        return $this->bindParam($param, $value, $type);
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     *
134
     * @throws SQLAnywhereException
135
     */
136
    public function closeCursor()
137
    {
138
        if (! sasql_stmt_reset($this->stmt)) {
139
            throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
140
        }
141
142
        return true;
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148
    public function columnCount()
149
    {
150
        return sasql_stmt_field_count($this->stmt);
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function errorCode()
157
    {
158
        return sasql_stmt_errno($this->stmt);
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    public function errorInfo()
165
    {
166
        return sasql_stmt_error($this->stmt);
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     *
172
     * @throws SQLAnywhereException
173
     */
174
    public function execute($params = null)
175
    {
176
        if (is_array($params)) {
177
            $hasZeroIndex = array_key_exists(0, $params);
178
179
            foreach ($params as $key => $val) {
180
                if ($hasZeroIndex && is_int($key)) {
181
                    $this->bindValue($key + 1, $val);
182
                } else {
183
                    $this->bindValue($key, $val);
184
                }
185
            }
186
        }
187
188
        if (! sasql_stmt_execute($this->stmt)) {
189
            throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
190
        }
191
192
        $this->result = sasql_stmt_result_metadata($this->stmt);
193
194
        return true;
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     *
200
     * @throws SQLAnywhereException
201
     */
202
    public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
203
    {
204
        if (! is_resource($this->result)) {
205
            return false;
206
        }
207
208
        $fetchMode = $fetchMode ?: $this->defaultFetchMode;
209
210
        switch ($fetchMode) {
211
            case FetchMode::COLUMN:
212
                return $this->fetchColumn();
213
214
            case FetchMode::ASSOCIATIVE:
215
                return sasql_fetch_assoc($this->result);
216
217
            case FetchMode::MIXED:
218
                return sasql_fetch_array($this->result, SASQL_BOTH);
219
220
            case FetchMode::CUSTOM_OBJECT:
221
                $className = $this->defaultFetchClass;
222
                $ctorArgs  = $this->defaultFetchClassCtorArgs;
223
224
                if (func_num_args() >= 2) {
225
                    $args      = func_get_args();
226
                    $className = $args[1];
227
                    $ctorArgs  = $args[2] ?? [];
228
                }
229
230
                $result = sasql_fetch_object($this->result);
231
232
                if ($result instanceof stdClass) {
233
                    $result = $this->castObject($result, $className, $ctorArgs);
234
                }
235
236
                return $result;
237
238
            case FetchMode::NUMERIC:
239
                return sasql_fetch_row($this->result);
240
241
            case FetchMode::STANDARD_OBJECT:
242
                return sasql_fetch_object($this->result);
243
244
            default:
245
                throw new SQLAnywhereException('Fetch mode is not supported: ' . $fetchMode);
246
        }
247
    }
248
249
    /**
250
     * {@inheritdoc}
251
     */
252
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
253
    {
254
        $rows = [];
255
256
        switch ($fetchMode) {
257
            case FetchMode::CUSTOM_OBJECT:
258
                while (($row = $this->fetch(...func_get_args())) !== false) {
259
                    $rows[] = $row;
260
                }
261
                break;
262
263
            case FetchMode::COLUMN:
264
                while (($row = $this->fetchColumn()) !== false) {
265
                    $rows[] = $row;
266
                }
267
                break;
268
269
            default:
270
                while (($row = $this->fetch($fetchMode)) !== false) {
271
                    $rows[] = $row;
272
                }
273
        }
274
275
        return $rows;
276
    }
277
278
    /**
279
     * {@inheritdoc}
280
     */
281 View Code Duplication
    public function fetchColumn($columnIndex = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282
    {
283
        $row = $this->fetch(FetchMode::NUMERIC);
284
285
        if ($row === false) {
286
            return false;
287
        }
288
289
        return $row[$columnIndex] ?? null;
290
    }
291
292
    /**
293
     * {@inheritdoc}
294
     */
295 4
    public function getIterator()
296
    {
297 4
        return new StatementIterator($this);
298
    }
299
300
    /**
301
     * {@inheritdoc}
302
     */
303
    public function rowCount()
304
    {
305
        return sasql_stmt_affected_rows($this->stmt);
306
    }
307
308
    /**
309
     * {@inheritdoc}
310
     */
311
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
312
    {
313
        $this->defaultFetchMode          = $fetchMode;
314
        $this->defaultFetchClass         = $arg2 ?: $this->defaultFetchClass;
315
        $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
316
    }
317
318
    /**
319
     * Casts a stdClass object to the given class name mapping its' properties.
320
     *
321
     * @param stdClass      $sourceObject     Object to cast from.
322
     * @param string|object $destinationClass Name of the class or class instance to cast to.
323
     * @param mixed[]       $ctorArgs         Arguments to use for constructing the destination class instance.
324
     *
325
     * @return object
326
     *
327
     * @throws SQLAnywhereException
328
     */
329
    private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
330
    {
331
        if (! is_string($destinationClass)) {
332
            if (! is_object($destinationClass)) {
333
                throw new SQLAnywhereException(sprintf(
334
                    'Destination class has to be of type string or object, %s given.',
335
                    gettype($destinationClass)
336
                ));
337
            }
338
        } else {
339
            $destinationClass = new ReflectionClass($destinationClass);
340
            $destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
341
        }
342
343
        $sourceReflection           = new ReflectionObject($sourceObject);
344
        $destinationClassReflection = new ReflectionObject($destinationClass);
345
346
        foreach ($sourceReflection->getProperties() as $sourceProperty) {
347
            $sourceProperty->setAccessible(true);
348
349
            $name  = $sourceProperty->getName();
350
            $value = $sourceProperty->getValue($sourceObject);
351
352
            if ($destinationClassReflection->hasProperty($name)) {
353
                $destinationProperty = $destinationClassReflection->getProperty($name);
354
355
                $destinationProperty->setAccessible(true);
356
                $destinationProperty->setValue($destinationClass, $value);
357
            } else {
358
                $destinationClass->$name = $value;
359
            }
360
        }
361
362
        return $destinationClass;
363
    }
364
}
365