Completed
Push — master ( bfc8bb...04db0e )
by Marco
20s queued 15s
created

OCI8Statement   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 303
Duplicated Lines 0 %

Test Coverage

Coverage 98.9%

Importance

Changes 0
Metric Value
wmc 43
eloc 108
dl 0
loc 303
rs 8.96
c 0
b 0
f 0
ccs 90
cts 91
cp 0.989

13 Methods

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