Failed Conditions
Push — master ( ac0e13...24dbc4 )
by Sergei
22s queued 15s
created

OCI8Statement   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 312
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 118
dl 0
loc 312
rs 9.0399
c 0
b 0
f 0
wmc 42

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A convertParameterType() 0 11 3
A bindValue() 0 3 1
A closeCursor() 0 10 2
A bindParam() 0 31 5
A rowCount() 0 9 2
A columnCount() 0 9 2
A fetchColumn() 0 19 4
B fetchAll() 0 49 9
A setFetchMode() 0 3 1
A getIterator() 0 3 1
A execute() 0 26 6
A fetch() 0 25 5

How to fix   Complexity   

Complex Class

Complex classes like OCI8Statement 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 OCI8Statement, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Driver\OCI8;
6
7
use Doctrine\DBAL\Driver\Statement;
8
use Doctrine\DBAL\Driver\StatementIterator;
9
use Doctrine\DBAL\Exception\InvalidColumnIndex;
10
use Doctrine\DBAL\FetchMode;
11
use Doctrine\DBAL\ParameterType;
12
use InvalidArgumentException;
13
use IteratorAggregate;
14
use function array_key_exists;
15
use function assert;
16
use function count;
17
use function is_int;
18
use function is_resource;
19
use function oci_bind_by_name;
20
use function oci_cancel;
21
use function oci_error;
22
use function oci_execute;
23
use function oci_fetch_all;
24
use function oci_fetch_array;
25
use function oci_fetch_object;
26
use function oci_new_descriptor;
27
use function oci_num_fields;
28
use function oci_num_rows;
29
use function oci_parse;
30
use function sprintf;
31
use const OCI_ASSOC;
32
use const OCI_B_BIN;
33
use const OCI_B_BLOB;
34
use const OCI_BOTH;
35
use const OCI_COMMIT_ON_SUCCESS;
36
use const OCI_D_LOB;
37
use const OCI_FETCHSTATEMENT_BY_COLUMN;
38
use const OCI_FETCHSTATEMENT_BY_ROW;
39
use const OCI_NO_AUTO_COMMIT;
40
use const OCI_NUM;
41
use const OCI_RETURN_LOBS;
42
use const OCI_RETURN_NULLS;
43
use const OCI_TEMP_BLOB;
44
use const SQLT_CHR;
45
46
/**
47
 * The OCI8 implementation of the Statement interface.
48
 */
49
final class OCI8Statement implements IteratorAggregate, Statement
50
{
51
    /** @var resource */
52
    protected $_dbh;
53
54
    /** @var resource */
55
    protected $_sth;
56
57
    /** @var ExecutionMode */
58
    protected $executionMode;
59
60
    /** @var int[] */
61
    protected static $fetchModeMap = [
62
        FetchMode::MIXED       => OCI_BOTH,
63
        FetchMode::ASSOCIATIVE => OCI_ASSOC,
64
        FetchMode::NUMERIC     => OCI_NUM,
65
        FetchMode::COLUMN      => OCI_NUM,
66
    ];
67
68
    /** @var int */
69
    protected $_defaultFetchMode = FetchMode::MIXED;
70
71
    /** @var string[] */
72
    protected $_paramMap = [];
73
74
    /**
75
     * Holds references to bound parameter values.
76
     *
77
     * This is a new requirement for PHP7's oci8 extension that prevents bound values from being garbage collected.
78
     *
79
     * @var mixed[]
80
     */
81
    private $boundValues = [];
82
83
    /**
84
     * Indicates whether the statement is in the state when fetching results is possible
85
     *
86
     * @var bool
87
     */
88
    private $result = false;
89
90
    /**
91
     * Creates a new OCI8Statement that uses the given connection handle and SQL statement.
92
     *
93
     * @param resource $dbh   The connection handle.
94
     * @param string   $query The SQL query.
95
     *
96
     * @throws OCI8Exception
97
     */
98
    public function __construct($dbh, string $query, ExecutionMode $executionMode)
99
    {
100
        [$query, $paramMap] = (new ConvertPositionalToNamedPlaceholders())($query);
101
102
        $stmt = oci_parse($dbh, $query);
103
        assert(is_resource($stmt));
104
105
        $this->_sth          = $stmt;
106
        $this->_dbh          = $dbh;
107
        $this->_paramMap     = $paramMap;
108
        $this->executionMode = $executionMode;
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114
    public function bindValue($param, $value, int $type = ParameterType::STRING) : void
115
    {
116
        $this->bindParam($param, $value, $type, null);
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    public function bindParam($param, &$variable, int $type = ParameterType::STRING, ?int $length = null) : void
123
    {
124
        if (is_int($param)) {
125
            if (! isset($this->_paramMap[$param])) {
126
                throw new OCI8Exception(sprintf('Could not find variable mapping with index %d, in the SQL statement', $param));
127
            }
128
129
            $param = $this->_paramMap[$param];
130
        }
131
132
        if ($type === ParameterType::LARGE_OBJECT) {
133
            $lob = oci_new_descriptor($this->_dbh, OCI_D_LOB);
134
135
            $class = 'OCI-Lob';
136
            assert($lob instanceof $class);
137
138
            $lob->writetemporary($variable, OCI_TEMP_BLOB);
139
140
            $variable =& $lob;
141
        }
142
143
        $this->boundValues[$param] =& $variable;
144
145
        if (! oci_bind_by_name(
146
            $this->_sth,
147
            $param,
148
            $variable,
149
            $length ?? -1,
150
            $this->convertParameterType($type)
151
        )) {
152
            throw OCI8Exception::fromErrorInfo(oci_error($this->_sth));
153
        }
154
    }
155
156
    /**
157
     * Converts DBAL parameter type to oci8 parameter type
158
     */
159
    private function convertParameterType(int $type) : int
160
    {
161
        switch ($type) {
162
            case ParameterType::BINARY:
163
                return OCI_B_BIN;
164
165
            case ParameterType::LARGE_OBJECT:
166
                return OCI_B_BLOB;
167
168
            default:
169
                return SQLT_CHR;
170
        }
171
    }
172
173
    public function closeCursor() : void
174
    {
175
        // not having the result means there's nothing to close
176
        if (! $this->result) {
177
            return;
178
        }
179
180
        oci_cancel($this->_sth);
181
182
        $this->result = false;
183
    }
184
185
    public function columnCount() : int
186
    {
187
        $count = oci_num_fields($this->_sth);
188
189
        if ($count !== false) {
190
            return $count;
191
        }
192
193
        return 0;
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199
    public function execute(?array $params = null) : void
200
    {
201
        if ($params !== null) {
202
            foreach ($params as $key => $val) {
203
                if (is_int($key)) {
204
                    $param = $key + 1;
205
                } else {
206
                    $param = $key;
207
                }
208
209
                $this->bindValue($param, $val);
210
            }
211
        }
212
213
        if ($this->executionMode->isAutoCommitEnabled()) {
214
            $mode = OCI_COMMIT_ON_SUCCESS;
215
        } else {
216
            $mode = OCI_NO_AUTO_COMMIT;
217
        }
218
219
        $ret = @oci_execute($this->_sth, $mode);
220
        if (! $ret) {
221
            throw OCI8Exception::fromErrorInfo(oci_error($this->_sth));
222
        }
223
224
        $this->result = true;
225
    }
226
227
    /**
228
     * {@inheritdoc}
229
     */
230
    public function setFetchMode(int $fetchMode, ...$args) : void
231
    {
232
        $this->_defaultFetchMode = $fetchMode;
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    public function getIterator()
239
    {
240
        return new StatementIterator($this);
241
    }
242
243
    /**
244
     * {@inheritdoc}
245
     */
246
    public function fetch(?int $fetchMode = null, ...$args)
247
    {
248
        // do not try fetching from the statement if it's not expected to contain result
249
        // in order to prevent exceptional situation
250
        if (! $this->result) {
251
            return false;
252
        }
253
254
        $fetchMode = $fetchMode ?? $this->_defaultFetchMode;
255
256
        if ($fetchMode === FetchMode::COLUMN) {
257
            return $this->fetchColumn();
258
        }
259
260
        if ($fetchMode === FetchMode::STANDARD_OBJECT) {
261
            return oci_fetch_object($this->_sth);
262
        }
263
264
        if (! isset(self::$fetchModeMap[$fetchMode])) {
265
            throw new InvalidArgumentException(sprintf('Invalid fetch mode %d.', $fetchMode));
266
        }
267
268
        return oci_fetch_array(
269
            $this->_sth,
270
            self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | OCI_RETURN_LOBS
271
        );
272
    }
273
274
    /**
275
     * {@inheritdoc}
276
     */
277
    public function fetchAll(?int $fetchMode = null, ...$args) : array
278
    {
279
        $fetchMode = $fetchMode ?? $this->_defaultFetchMode;
280
281
        $result = [];
282
283
        if ($fetchMode === FetchMode::STANDARD_OBJECT) {
284
            while ($row = $this->fetch($fetchMode)) {
285
                $result[] = $row;
286
            }
287
288
            return $result;
289
        }
290
291
        if (! isset(self::$fetchModeMap[$fetchMode])) {
292
            throw new InvalidArgumentException(sprintf('Invalid fetch mode %d.', $fetchMode));
293
        }
294
295
        if (self::$fetchModeMap[$fetchMode] === OCI_BOTH) {
296
            while ($row = $this->fetch($fetchMode)) {
297
                $result[] = $row;
298
            }
299
        } else {
300
            $fetchStructure = OCI_FETCHSTATEMENT_BY_ROW;
301
302
            if ($fetchMode === FetchMode::COLUMN) {
303
                $fetchStructure = OCI_FETCHSTATEMENT_BY_COLUMN;
304
            }
305
306
            // do not try fetching from the statement if it's not expected to contain result
307
            // in order to prevent exceptional situation
308
            if (! $this->result) {
309
                return [];
310
            }
311
312
            oci_fetch_all(
313
                $this->_sth,
314
                $result,
315
                0,
316
                -1,
317
                self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS
318
            );
319
320
            if ($fetchMode === FetchMode::COLUMN) {
321
                $result = $result[0];
322
            }
323
        }
324
325
        return $result;
326
    }
327
328
    /**
329
     * {@inheritdoc}
330
     */
331
    public function fetchColumn(int $columnIndex = 0)
332
    {
333
        // do not try fetching from the statement if it's not expected to contain result
334
        // in order to prevent exceptional situation
335
        if (! $this->result) {
336
            return false;
337
        }
338
339
        $row = oci_fetch_array($this->_sth, OCI_NUM | OCI_RETURN_NULLS | OCI_RETURN_LOBS);
340
341
        if ($row === false) {
342
            return false;
343
        }
344
345
        if (! array_key_exists($columnIndex, $row)) {
346
            throw InvalidColumnIndex::new($columnIndex, count($row));
347
        }
348
349
        return $row[$columnIndex];
350
    }
351
352
    public function rowCount() : int
353
    {
354
        $count = oci_num_rows($this->_sth);
355
356
        if ($count !== false) {
357
            return $count;
358
        }
359
360
        return 0;
361
    }
362
}
363