Reader::getHeadersFromRow()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 0
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace EasyCSV;
6
7
use LogicException;
8
use function array_combine;
9
use function array_filter;
10
use function count;
11
use function is_array;
12
use function is_string;
13
use function mb_strpos;
14
use function sprintf;
15
use function str_getcsv;
16
use function str_replace;
17
18
class Reader extends AbstractBase
19
{
20
    /** @var bool */
21
    private $headersInFirstRow = true;
22
23
    /** @var string[]|bool */
24
    private $headers = false;
25
26
    /** @var bool */
27
    private $init;
28
29
    /** @var bool|int */
30
    private $headerLine = false;
31
32
    /** @var bool|int */
33
    private $lastLine = false;
34
35
    /** @var bool */
36
    private $isNeedBOMRemove = true;
37
38 4
    public function __construct(string $path, string $mode = 'r+', bool $headersInFirstRow = true)
39
    {
40 4
        parent::__construct($path, $mode);
41
42 4
        $this->headersInFirstRow = $headersInFirstRow;
43 4
    }
44
45
    /**
46
     * @return string[]|bool
47
     */
48 6
    public function getHeaders()
49
    {
50 6
        $this->init();
51
52 6
        return $this->headers;
53
    }
54
55
    /**
56
     * @return mixed[]|bool
57
     */
58 26
    public function getRow()
59
    {
60 26
        $this->init();
61
62 26
        if ($this->isEof()) {
63 10
            return false;
64
        }
65
66 26
        $row     = $this->getCurrentRow();
67 26
        $isEmpty = $this->rowIsEmpty($row);
68
69 26
        if ($this->isEof() === false) {
0 ignored issues
show
introduced by
The condition $this->isEof() === false is always true.
Loading history...
70 26
            $this->getHandle()->next();
71
        }
72
73 26
        if ($isEmpty === false) {
74 26
            return $this->headers !== false && is_array($this->headers) ? array_combine($this->headers, $row) : $row;
75
        }
76
77 4
        if ((isset($this->headers) && is_array($this->headers)) && (count($this->headers) !== count($row))) {
78 4
            return $this->getRow();
79
        }
80
81 2
        if (is_array($this->headers)) {
82 2
            return array_combine($this->headers, $row);
83
        }
84
85
        return false;
86
    }
87
88 30
    public function isEof() : bool
89
    {
90 30
        return $this->getHandle()->eof();
91
    }
92
93
    /**
94
     * @return mixed[]
95
     */
96 8
    public function getAll() : array
97
    {
98 8
        $data = [];
99 8
        while ($row = $this->getRow()) {
100 8
            $data[] = $row;
101
        }
102
103 8
        return $data;
104
    }
105
106 6
    public function getLineNumber() : int
107
    {
108 6
        return $this->getHandle()->key();
109
    }
110
111
    /**
112
     * @return int|bool
113
     */
114 4
    public function getLastLineNumber()
115
    {
116 4
        if ($this->lastLine !== false) {
117 2
            return $this->lastLine;
118
        }
119
120 4
        $this->getHandle()->seek($this->getHandle()->getSize());
121 4
        $lastLine = $this->getHandle()->key();
122
123 4
        $this->getHandle()->rewind();
124
125 4
        return $this->lastLine = $lastLine;
126
    }
127
128
    /**
129
     * @return string[]
130
     */
131 28
    public function getCurrentRow() : array
132
    {
133 28
        $current = $this->getHandle()->current();
134
135 28
        if (! is_string($current)) {
136
            return [];
137
        }
138
139 28
        if ($this->isNeedBOMRemove && mb_strpos($current, "\xEF\xBB\xBF", 0, 'utf-8') === 0) {
140 2
            $this->isNeedBOMRemove = false;
141
142 2
            $current = str_replace("\xEF\xBB\xBF", '', $current);
143
        }
144
145 28
        return str_getcsv($current, $this->delimiter, $this->enclosure);
146
    }
147
148 12
    public function advanceTo(int $lineNumber) : void
149
    {
150 12
        if ($this->headerLine > $lineNumber) {
151 2
            throw new LogicException(sprintf(
152 2
                'Line Number %s is before the header line that was set',
153 2
                $lineNumber
154
            ));
155
        }
156
157 10
        if ($this->headerLine === $lineNumber) {
158 2
            throw new LogicException(sprintf(
159 2
                'Line Number %s is equal to the header line that was set',
160 2
                $lineNumber
161
            ));
162
        }
163
164 8
        if ($lineNumber > 0) {
165 8
            $this->getHandle()->seek($lineNumber - 1);
166
        } // check the line before
167
168 8
        if ($this->isEof()) {
169 2
            throw new LogicException(sprintf(
170 2
                'Line Number %s is past the end of the file',
171 2
                $lineNumber
172
            ));
173
        }
174
175 6
        $this->getHandle()->seek($lineNumber);
176 6
    }
177
178 6
    public function setHeaderLine(int $lineNumber) : bool
179
    {
180 6
        if ($lineNumber === 0) {
181 2
            return false;
182
        }
183
184 4
        $this->headersInFirstRow = false;
185
186 4
        $this->headerLine = $lineNumber;
187
188 4
        $this->getHandle()->seek($lineNumber);
189
190
        // get headers
191 4
        $this->headers = $this->getHeadersFromRow();
192
193 4
        return true;
194
    }
195
196 26
    protected function init() : void
197
    {
198 26
        if ($this->init === true) {
199 24
            return;
200
        }
201
202 26
        $this->init = true;
203
204 26
        if ($this->headersInFirstRow !== true) {
205 6
            return;
206
        }
207
208 20
        $this->getHandle()->rewind();
209
210 20
        $this->headerLine = 0;
211
212 20
        $this->headers = $this->getHeadersFromRow();
213 20
    }
214
215
    /**
216
     * @param string[]|null[] $row
217
     */
218 26
    protected function rowIsEmpty(array $row) : bool
219
    {
220 26
        $emptyRow               = ($row === [null]);
221 26
        $emptyRowWithDelimiters = (array_filter($row) === []);
222 26
        $isEmpty                = false;
223
224 26
        if ($emptyRow) {
225 4
            return true;
226
        }
227
228 26
        if ($emptyRowWithDelimiters) {
229 2
            return true;
230
        }
231
232 26
        return $isEmpty;
233
    }
234
235
    /**
236
     * @return string[]
237
     */
238 24
    private function getHeadersFromRow() : array
239
    {
240 24
        $row = $this->getRow();
241
242 24
        return is_array($row) ? $row : [];
243
    }
244
}
245