Completed
Pull Request — master (#3070)
by Sergei
64:44
created

OCI8Statement   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 281
Duplicated Lines 0 %

Test Coverage

Coverage 66.89%

Importance

Changes 0
Metric Value
wmc 40
eloc 104
c 0
b 0
f 0
dl 0
loc 281
ccs 101
cts 151
cp 0.6689
rs 9.2

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