Completed
Push — master ( dc488d...044b94 )
by Jonathan
9s
created

Reader::getLastLineNumber()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 0
dl 0
loc 12
ccs 7
cts 7
cp 1
crap 2
rs 9.4285
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 mb_strpos;
13
use function sprintf;
14
use function str_getcsv;
15
use function str_replace;
16
17
class Reader extends AbstractBase
18
{
19
    /** @var bool */
20
    private $headersInFirstRow = true;
21
22
    /** @var string[]|bool */
23
    private $headers = false;
24
25
    /** @var bool */
26
    private $init;
27
28
    /** @var bool|int */
29
    private $headerLine = false;
30
31
    /** @var bool|int */
32
    private $lastLine = false;
33
34
    /** @var bool */
35
    private $isNeedBOMRemove = true;
36
37 4
    public function __construct(string $path, string $mode = 'r+', bool $headersInFirstRow = true)
38
    {
39 4
        parent::__construct($path, $mode);
40
41 4
        $this->headersInFirstRow = $headersInFirstRow;
42 4
    }
43
44
    /**
45
     * @return string[]|bool
46
     */
47 6
    public function getHeaders()
48
    {
49 6
        $this->init();
50
51 6
        return $this->headers;
52
    }
53
54
    /**
55
     * @return string[]|bool
56
     */
57 26
    public function getRow()
58
    {
59 26
        $this->init();
60
61 26
        if ($this->isEof()) {
62 10
            return false;
63
        }
64
65 26
        $row     = $this->getCurrentRow();
66 26
        $isEmpty = $this->rowIsEmpty($row);
67
68 26
        if ($this->isEof() === false) {
0 ignored issues
show
introduced by
The condition $this->isEof() === false is always true.
Loading history...
69 26
            $this->getHandle()->next();
70
        }
71
72 26
        if ($isEmpty === false) {
73 26
            return ($this->headers !== false && is_array($this->headers)) ? array_combine($this->headers, $row) : $row;
74
        }
75
76 4
        return (isset($this->headers) && is_array($this->headers)) && (count($this->headers) !== count($row)) ? $this->getRow() : array_combine($this->headers, $row);
77
    }
78
79 30
    public function isEof() : bool
80
    {
81 30
        return $this->getHandle()->eof();
82
    }
83
84
    /**
85
     * @return string[][]
86
     */
87 8
    public function getAll() : array
88
    {
89 8
        $data = [];
90 8
        while ($row = $this->getRow()) {
91 8
            $data[] = $row;
92
        }
93
94 8
        return $data;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $data returns an array which contains values of type true which are incompatible with the documented value type string[].
Loading history...
95
    }
96
97 6
    public function getLineNumber() : int
98
    {
99 6
        return $this->getHandle()->key();
100
    }
101
102
    /**
103
     * @return int|bool
104
     */
105 4
    public function getLastLineNumber()
106
    {
107 4
        if ($this->lastLine !== false) {
108 2
            return $this->lastLine;
109
        }
110
111 4
        $this->getHandle()->seek($this->getHandle()->getSize());
112 4
        $lastLine = $this->getHandle()->key();
113
114 4
        $this->getHandle()->rewind();
115
116 4
        return $this->lastLine = $lastLine;
117
    }
118
119
    /**
120
     * @return string[]
121
     */
122 28
    public function getCurrentRow() : array
123
    {
124 28
        $current = $this->getHandle()->current();
125
126 28
        if ($this->isNeedBOMRemove && mb_strpos($current, "\xEF\xBB\xBF", 0, 'utf-8') === 0) {
0 ignored issues
show
Bug introduced by
It seems like $current can also be of type array; however, parameter $haystack of mb_strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

126
        if ($this->isNeedBOMRemove && mb_strpos(/** @scrutinizer ignore-type */ $current, "\xEF\xBB\xBF", 0, 'utf-8') === 0) {
Loading history...
127 2
            $this->isNeedBOMRemove = false;
128
129 2
            $current = str_replace("\xEF\xBB\xBF", '', $current);
130
        }
131
132 28
        return str_getcsv($current, $this->delimiter, $this->enclosure);
0 ignored issues
show
Bug introduced by
It seems like $current can also be of type array; however, parameter $input of str_getcsv() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

132
        return str_getcsv(/** @scrutinizer ignore-type */ $current, $this->delimiter, $this->enclosure);
Loading history...
133
    }
134
135 12
    public function advanceTo(int $lineNumber) : void
136
    {
137 12
        if ($this->headerLine > $lineNumber) {
138 2
            throw new LogicException(sprintf(
139 2
                'Line Number %s is before the header line that was set',
140 2
                $lineNumber
141
            ));
142 10
        } elseif ($this->headerLine === $lineNumber) {
143 2
            throw new LogicException(sprintf(
144 2
                'Line Number %s is equal to the header line that was set',
145 2
                $lineNumber
146
            ));
147
        }
148
149 8
        if ($lineNumber > 0) {
150 8
            $this->getHandle()->seek($lineNumber - 1);
151
        } // check the line before
152
153 8
        if ($this->isEof()) {
154 2
            throw new LogicException(sprintf(
155 2
                'Line Number %s is past the end of the file',
156 2
                $lineNumber
157
            ));
158
        }
159
160 6
        $this->getHandle()->seek($lineNumber);
161 6
    }
162
163 6
    public function setHeaderLine(int $lineNumber) : bool
164
    {
165 6
        if ($lineNumber === 0) {
166 2
            return false;
167
        }
168
169 4
        $this->headersInFirstRow = false;
170
171 4
        $this->headerLine = $lineNumber;
172
173 4
        $this->getHandle()->seek($lineNumber);
174
175
        // get headers
176 4
        $this->headers = $this->getRow();
177
178 4
        return true;
179
    }
180
181 26
    protected function init() : void
182
    {
183 26
        if ($this->init === true) {
184 24
            return;
185
        }
186
187 26
        $this->init = true;
188
189 26
        if ($this->headersInFirstRow !== true) {
190 6
            return;
191
        }
192
193 20
        $this->getHandle()->rewind();
194
195 20
        $this->headerLine = 0;
196
197 20
        $this->headers = $this->getRow();
198 20
    }
199
200
    /**
201
     * @param string[]|null[] $row
202
     */
203 26
    protected function rowIsEmpty(array $row) : bool
204
    {
205 26
        $emptyRow               = ($row === [null]);
206 26
        $emptyRowWithDelimiters = (array_filter($row) === []);
207 26
        $isEmpty                = false;
208
209 26
        if ($emptyRow) {
210 4
            $isEmpty = true;
211
212 4
            return $isEmpty;
213 26
        } elseif ($emptyRowWithDelimiters) {
214 2
            $isEmpty = true;
215
216 2
            return $isEmpty;
217
        }
218
219 26
        return $isEmpty;
220
    }
221
}
222