Result::resultCount()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
/** @noinspection PhpComposerExtensionStubsInspection */
4
5
declare(strict_types=1);
6
7
namespace EngineWorks\DBAL\Sqlite;
8
9
use EngineWorks\DBAL\CommonTypes;
10
use EngineWorks\DBAL\Result as ResultInterface;
11
use EngineWorks\DBAL\Traits\ResultImplementsCountable;
12
use EngineWorks\DBAL\Traits\ResultImplementsIterator;
13
use Error;
14
use SQLite3Result;
15
16
/**
17
 * Result class implementing EngineWorks\DBAL\Result based on Sqlite3 functions
18
 */
19
class Result implements ResultInterface
20
{
21
    use ResultImplementsCountable;
22
    use ResultImplementsIterator;
23
24
    private const TYPES = [
25
        SQLITE3_INTEGER => CommonTypes::TINT,
26
        SQLITE3_FLOAT => CommonTypes::TNUMBER,
27
        SQLITE3_TEXT => CommonTypes::TTEXT,
28
    ];
29
30
    /**
31
     * Sqlite3 element
32
     * @var SQLite3Result
33
     */
34
    private $query;
35
36
    /**
37
     * The number of the result rows
38
     * @var int
39
     */
40
    private $numRows;
41
42
    /**
43
     * Set of fieldname and commontype to use instead of detectedTypes
44
     * @var array<string, string>
45
     */
46
    private $overrideTypes;
47
48
    /**
49
     * The place where getFields result is cached
50
     * @var array<int, array{name: string, table: string, commontype: string}>|null
51
     */
52
    private $cachedGetFields;
53
54
    /**
55
     * each call to fetchArray() returns the next result from SQLite3Result in an array,
56
     * until there are no more results, whereupon the next fetchArray() call will return FALSE.
57
     *
58
     * HOWEVER an additional call of fetchArray() at this point will reset back to the beginning of the result
59
     * set and once again return the first result. This does not seem to explicitly documented.
60
     *
61
     * http://php.net/manual/en/sqlite3result.fetcharray.php#115856
62
     *
63
     * @var bool
64
     */
65
    private $hasReachEOL = false;
66
67
    /**
68
     * Result based on Sqlite3
69
     * @param SQLite3Result $result
70
     * @param array<string, string> $overrideTypes
71
     */
72 51
    public function __construct(SQLite3Result $result, array $overrideTypes = [])
73
    {
74 51
        $this->query = $result;
75 51
        $this->overrideTypes = $overrideTypes;
76 51
        $this->numRows = $this->obtainNumRows();
77
    }
78
79
    /**
80
     * Close the query and remove property association
81
     */
82 41
    public function __destruct()
83
    {
84
        // suppress errors because the query may already been closed
85
        // see https://bugs.php.net/bug.php?id=72502
86
        // since PHP 8.0 the @ operator no longer silences fatal errors
87
        // on PHP lower than 8.0 it was just a WARNING
88
        try {
89
            /**
90
             * @scrutinizer ignore-unhandled
91
             * @noinspection PhpUsageOfSilenceOperatorInspection
92
             */
93 41
            @$this->query->finalize();
94
        } catch (Error $exception) { // phpcs:ignore
95
        }
96
    }
97
98
    /**
99
     * @param int $mode one constant value of SQLITE3 Modes
100
     * @return mixed[]|false
101
     */
102 51
    private function internalFetch(int $mode)
103
    {
104 51
        if ($this->hasReachEOL) {
105 11
            return false;
106
        }
107
108 51
        $values = $this->query->fetchArray($mode);
109 51
        if (! is_array($values)) {
0 ignored issues
show
introduced by
The condition is_array($values) is always true.
Loading history...
110 51
            $this->hasReachEOL = true;
111 51
            return false;
112
        }
113
114 45
        return $values;
115
    }
116
117 51
    private function internalReset(): bool
118
    {
119 51
        if (! $this->hasReachEOL) {
120 5
            return $this->query->reset();
121
        }
122 51
        $this->hasReachEOL = false;
123 51
        return true;
124
    }
125
126
    /**
127
     * Internal method to retrieve the number of rows if not supplied from constructor
128
     *
129
     * @return int
130
     */
131 51
    private function obtainNumRows(): int
132
    {
133 51
        $count = 0;
134 51
        if (false !== $this->internalFetch(SQLITE3_NUM)) {
0 ignored issues
show
introduced by
The condition false !== $this->interna...BAL\Sqlite\SQLITE3_NUM) is always false.
Loading history...
135 45
            $this->getFields();
136 45
            $count = 1;
137
        }
138 51
        while (false !== $this->internalFetch(SQLITE3_NUM)) {
139 26
            $count = $count + 1;
140
        }
141 51
        $this->internalReset();
142 51
        return $count;
143
    }
144
145 47
    public function getFields(): array
146
    {
147 47
        if (null === $this->cachedGetFields) {
148 47
            $this->cachedGetFields = $this->obtainFields();
149
        }
150
151 47
        return $this->cachedGetFields;
152
    }
153
154
    /** @return array<int, array{name: string, table: string, commontype: string}> */
155 47
    private function obtainFields(): array
156
    {
157 47
        $fields = [];
158 47
        $numcolumns = $this->query->numColumns();
159 47
        for ($i = 0; $i < $numcolumns; $i++) {
160 47
            $columnName = $this->query->columnName($i);
161 47
            $fields[] = [
162 47
                'name' => $columnName,
163 47
                'commontype' => $this->getCommonType($columnName, $this->query->columnType($i)),
164 47
                'table' => '',
165 47
            ];
166
        }
167 47
        return $fields;
168
    }
169
170
    /**
171
     * Private function to get the CommonType from the information of the field
172
     *
173
     * @param string $columnName
174
     * @param int|false $fieldIndex
175
     * @return string
176
     */
177 47
    private function getCommonType(string $columnName, $fieldIndex): string
178
    {
179 47
        if (isset($this->overrideTypes[$columnName])) {
180 6
            return $this->overrideTypes[$columnName];
181
        }
182 47
        if (false === $fieldIndex) {
183 4
            return CommonTypes::TTEXT;
184
        }
185 45
        return self::TYPES[$fieldIndex] ?? CommonTypes::TTEXT;
186
    }
187
188 11
    public function getIdFields(): bool
189
    {
190 11
        return false;
191
    }
192
193 19
    public function resultCount(): int
194
    {
195 19
        return $this->numRows;
196
    }
197
198 41
    public function fetchRow()
199
    {
200
        /** @var array<string, scalar|null> $return */
201 41
        $return = $this->internalFetch(SQLITE3_ASSOC);
202 41
        return (! is_array($return)) ? false : $return;
0 ignored issues
show
introduced by
The condition is_array($return) is always true.
Loading history...
203
    }
204
205 3
    public function moveTo(int $offset): bool
206
    {
207
        // there are no records
208 3
        if ($this->resultCount() <= 0) {
209 1
            return false;
210
        }
211
        // the offset is out of bounds
212 2
        if ($offset < 0 || $offset > $this->resultCount() - 1) {
213 1
            return false;
214
        }
215
        // if the offset is on previous
216 1
        if (! $this->moveFirst()) {
217
            return false;
218
        }
219
        // move to the offset
220 1
        for ($i = 0; $i < $offset; $i++) {
221 1
            if (false === $this->internalFetch(SQLITE3_NUM)) {
222
                return false;
223
            }
224
        }
225 1
        return true;
226
    }
227
228 6
    public function moveFirst(): bool
229
    {
230 6
        if ($this->resultCount() <= 0) {
231 1
            return false;
232
        }
233 5
        return $this->internalReset();
234
    }
235
}
236