OCI8Statement::fetchColumn()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 5
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 12
ccs 0
cts 6
cp 0
crap 12
rs 10
1
<?php
2
3
/**
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 *
7
 * @see https://github.com/ecphp
8
 */
9
10
declare(strict_types=1);
11
12
namespace EcPhp\DoctrineOci8\Doctrine\DBAL\Driver\OCI8;
13
14
use Doctrine\DBAL\Driver\OCI8\OCI8Statement as BaseStatement;
15
use Doctrine\DBAL\ParameterType;
16
use LogicException;
17
use PDO;
18
19
use function array_map;
20
use function count;
21
use function is_array;
22
use function is_numeric;
23
use function is_resource;
24
use function max;
25
use function oci_bind_array_by_name;
26
use function oci_bind_by_name;
27
use function reset;
28
29
use const OCI_B_BLOB;
30
use const OCI_B_CLOB;
31
use const OCI_B_CURSOR;
32
use const OCI_BOTH;
33
use const SQLT_AFC;
34
use const SQLT_CHR;
35
use const SQLT_INT;
36
37
class OCI8Statement extends BaseStatement
0 ignored issues
show
Deprecated Code introduced by
The class Doctrine\DBAL\Driver\OCI8\OCI8Statement has been deprecated: Use {@link Statement} instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

37
class OCI8Statement extends /** @scrutinizer ignore-deprecated */ BaseStatement
Loading history...
38
{
39
    protected bool $checkedForCursorFields = false;
40
41
    protected array $cursorFields = [];
42
43
    private array $references = [];
44
45
    /**
46
     * Used because parent::fetchAll() calls $this->fetch().
47
     */
48
    private bool $returningCursors = false;
49
50
    /**
51
     * Used because parent::fetchAll() calls $this->fetch().
52
     */
53
    private bool $returningResources = false;
54
55
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null): bool
56
    {
57
        $origCol = $column;
58
59
        $column = $this->_paramMap[$column] ?? $column;
60
61
        [$type, $ociType] = $this->normalizeType($type);
62
63
        // Type: Cursor.
64
        if (PDO::PARAM_STMT === $type || OCI_B_CURSOR === $ociType) {
65
            /** @var OCI8Connection $conn Because my IDE complains. */
66
            $conn = $this->_conn;
67
            $variable = $conn->newCursor();
68
69
            return $this->bindByName($column, $variable->_sth, -1, OCI_B_CURSOR);
70
        }
71
72
        // Type: Null. (Must come *after* types that can expect $variable to be null, like 'cursor'.)
73
        if (null === $variable) {
74
            return $this->bindByName($column, $variable);
75
        }
76
77
        // Type: Array.
78
        if (is_array($variable)) {
79
            $length = $length ?? -1;
80
81
            if (!$ociType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ociType of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
82
                $ociType = PDO::PARAM_INT === $type ? SQLT_INT : SQLT_CHR;
83
            }
84
85
            return $this->bindArrayByName(
86
                $column,
87
                $variable,
88
                max(count($variable), 1),
89
                empty($variable) ? 0 : $length,
90
                $ociType
91
            );
92
        }
93
94
        // Type: Lob
95
        if (OCI_B_CLOB === $ociType || OCI_B_BLOB === $ociType) {
96
            $type = PDO::PARAM_LOB;
97
        } elseif ($ociType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ociType of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
98
            return $this->bindByName($column, $variable, $length ?? -1, $ociType);
99
        }
100
101
        return parent::bindParam($origCol, $variable, $type, $length);
102
    }
103
104
    public function bindValue($param, $value, $type = ParameterType::STRING): bool
105
    {
106
        [$type, $ociType] = $this->normalizeType($type);
107
108
        if (PDO::PARAM_STMT === $type || OCI_B_CURSOR === $ociType) {
109
            throw new LogicException('You must call "bindParam()" to bind a cursor.');
110
        }
111
112
        return parent::bindValue($param, $value, $type);
113
    }
114
115
    public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
116
    {
117
        [$fetchMode, $returnResources, $returnCursors] = $this->processFetchMode($fetchMode, true);
118
119
        // "Globals" are checked because parent::fetchAll() calls $this->fetch().
120
        $row = parent::fetch($fetchMode, $cursorOrientation, $cursorOffset);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Driver\OCI8\OCI8Statement::fetch() has been deprecated: Use fetchNumeric(), fetchAssociative() or fetchOne() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

120
        $row = /** @scrutinizer ignore-deprecated */ parent::fetch($fetchMode, $cursorOrientation, $cursorOffset);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
121
122
        if (!$returnResources) {
123
            $this->fetchCursorFields($row, $fetchMode, $returnCursors);
124
        }
125
126
        return $row;
127
    }
128
129
    /**
130
     * @param null|mixed $fetchMode
131
     * @param null|mixed $fetchArgument
132
     * @param null|mixed $ctorArgs
133
     *
134
     * @throws \Doctrine\DBAL\Driver\OCI8\OCI8Exception
135
     */
136
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
137
    {
138
        [$fetchMode, $this->returningResources, $this->returningCursors] = $this->processFetchMode($fetchMode);
139
140
        // "Globals" are set because parent::fetchAll() calls $this->fetch().
141
        $results = parent::fetchAll($fetchMode, $fetchArgument, $ctorArgs);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Driver\OCI...I8Statement::fetchAll() has been deprecated: Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

141
        $results = /** @scrutinizer ignore-deprecated */ parent::fetchAll($fetchMode, $fetchArgument, $ctorArgs);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
142
143
        if (
144
            !$this->returningResources
145
            && $results
146
            && is_array($results)
147
            && OCI_BOTH !== self::$fetchModeMap[$fetchMode] // handled by $this->fetch() in parent::fetchAll().
148
        ) {
149
            if (PDO::FETCH_COLUMN !== $fetchMode) {
150
                foreach ($results as &$row) {
151
                    $this->fetchCursorFields($row, $fetchMode, $this->returningCursors);
152
                }
153
                unset($row);
154
            } elseif (is_resource(reset($results))) {
155
                $results = array_map(function ($value) use ($fetchMode) {
156
                    return $this->fetchCursorValue($value, $fetchMode, $this->returningCursors);
157
                }, $results);
158
            }
159
        }
160
161
        $this->returningResources =
162
        $this->returningCursors = false;
163
        $this->resetCursorFields();
164
165
        return $results;
166
    }
167
168
    public function fetchColumn($columnIndex = 0, $fetchMode = null)
169
    {
170
        [$fetchMode, $returnResources, $returnCursors] = $this->processFetchMode($fetchMode);
171
172
        /** @var array|bool|resource|string|null $columnValue */
173
        $columnValue = parent::fetchColumn($columnIndex);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Driver\OCI...tatement::fetchColumn() has been deprecated: Use fetchOne() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

173
        $columnValue = /** @scrutinizer ignore-deprecated */ parent::fetchColumn($columnIndex);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
174
175
        if (!$returnResources && is_resource($columnValue)) {
176
            return $this->fetchCursorValue($columnValue, $fetchMode, $returnCursors);
177
        }
178
179
        return $columnValue;
180
    }
181
182
    /**
183
     * @param string $column
184
     * @param mixed  $variable
185
     * @param int    $maxTableLength
186
     * @param int    $maxItemLength
187
     * @param int    $type
188
     */
189
    protected function bindArrayByName(
190
        $column,
191
        &$variable,
192
        $maxTableLength,
193
        $maxItemLength = -1,
194
        $type = SQLT_AFC
195
    ): bool {
196
        // For PHP 7's OCI8 extension (prevents garbage collection).
197
        $this->references[$column] = &$variable;
198
199
        return oci_bind_array_by_name($this->_sth, $column, $variable, $maxTableLength, $maxItemLength, $type);
200
    }
201
202
    /**
203
     * @param string $column
204
     * @param mixed  $variable
205
     * @param int    $maxLength
206
     * @param int    $type
207
     */
208
    protected function bindByName($column, &$variable, $maxLength = -1, $type = SQLT_CHR): bool
209
    {
210
        // For PHP 7's OCI8 extension (prevents garbage collection).
211
        $this->references[$column] = &$variable;
212
213
        return oci_bind_by_name($this->_sth, $column, $variable, $maxLength, $type);
214
    }
215
216
    /**
217
     * @param array|mixed $row
218
     * @param int         $fetchMode
219
     * @param bool        $returnCursors
220
     *
221
     * @throws \Doctrine\DBAL\Driver\OCI8\OCI8Exception
222
     */
223
    protected function fetchCursorFields(&$row, $fetchMode, $returnCursors): void
224
    {
225
        if (!is_array($row)) {
226
            $this->resetCursorFields();
227
        } elseif (!$this->checkedForCursorFields) {
228
            // This will also call fetchCursorField() on each cursor field of the first row.
229
            $this->findCursorFields($row, $fetchMode, $returnCursors);
230
        } elseif ($this->cursorFields) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cursorFields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
231
            $shared = [];
232
233
            foreach ($this->cursorFields as $field) {
234
                $key = (string) $row[$field];
235
236
                if (isset($shared[$key])) {
237
                    $row[$field] = $shared[$key];
238
239
                    continue;
240
                }
241
                $row[$field] = $this->fetchCursorValue($row[$field], $fetchMode, $returnCursors);
242
                $shared[$key] = &$row[$field];
243
            }
244
        }
245
    }
246
247
    /**
248
     * @param resource $resource
249
     * @param int $fetchMode
250
     * @param bool $returnCursor
251
     *
252
     * @throws \Doctrine\DBAL\Driver\OCI8\OCI8Exception
253
     *
254
     * @return array|mixed|OCI8Cursor
255
     */
256
    protected function fetchCursorValue($resource, $fetchMode, $returnCursor)
257
    {
258
        /** @var OCI8Connection $conn Because my IDE complains. */
259
        $conn = $this->_conn;
260
        $cursor = $conn->newCursor($resource);
261
262
        if ($returnCursor) {
263
            return $cursor;
264
        }
265
266
        $cursor->execute();
267
        $results = $cursor->fetchAll($fetchMode);
268
        $cursor->closeCursor();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Driver\OCI...tatement::closeCursor() has been deprecated: Use free() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

268
        /** @scrutinizer ignore-deprecated */ $cursor->closeCursor();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
269
270
        return $results;
271
    }
272
273
    /**
274
     * @param int   $fetchMode
275
     * @param bool  $returnCursors
276
     *
277
     * @throws \Doctrine\DBAL\Driver\OCI8\OCI8Exception
278
     */
279
    protected function findCursorFields(array &$row, $fetchMode, $returnCursors): void
280
    {
281
        $shared = [];
282
283
        foreach ($row as $field => $value) {
284
            if (!is_resource($value)) {
285
                continue;
286
            }
287
            $this->cursorFields[] = $field;
288
            $key = (string) $value;
289
290
            if (isset($shared[$key])) {
291
                $row[$field] = $shared[$key];
292
293
                continue;
294
            }
295
            // We are already here, so might as well process it.
296
            $row[$field] = $this->fetchCursorValue($row[$field], $fetchMode, $returnCursors);
297
            $shared[$key] = &$row[$field];
298
        }
299
        $this->checkedForCursorFields = true;
300
    }
301
302
    /**
303
     * @param int|string $type
304
     */
305
    protected function normalizeType($type): array
306
    {
307
        $ociType = null;
308
309
        // Figure out the type.
310
        if (is_numeric($type)) {
311
            $type = (int) $type;
312
313
            if (OCI8::isParamConstant($type)) {
314
                $ociType = OCI8::decodeParamConstant($type);
315
            }
316
        } elseif ('cursor' === strtolower($type)) {
317
            $type = PDO::PARAM_STMT;
318
            $ociType = OCI_B_CURSOR;
319
        }
320
321
        return [$type, $ociType];
322
    }
323
324
    /**
325
     * @param int  $fetchMode
326
     * @param bool $checkGlobal
327
     */
328
    protected function processFetchMode($fetchMode, $checkGlobal = false): array
329
    {
330
        $returnResources = ($checkGlobal && $this->returningResources) || ($fetchMode & OCI8::RETURN_RESOURCES);
331
        $returnCursors = ($checkGlobal && $this->returningCursors) || ($fetchMode & OCI8::RETURN_CURSORS);
332
        // Must unset the flags or there will be an error.
333
        $fetchMode &= ~(OCI8::RETURN_RESOURCES + OCI8::RETURN_CURSORS);
334
        $fetchMode = (int) ($fetchMode ?: $this->_defaultFetchMode);
335
336
        return [$fetchMode, $returnResources, $returnCursors];
337
    }
338
339
    protected function resetCursorFields(): void
340
    {
341
        $this->cursorFields = [];
342
        $this->checkedForCursorFields = false;
343
    }
344
}
345