Failed Conditions
Pull Request — master (#38)
by Jonathan
19:06 queued 16:30
created

Reader::getRow()   C

Complexity

Conditions 7
Paths 13

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7.0222

Importance

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

130
        if ($this->isNeedBOMRemove && mb_strpos(/** @scrutinizer ignore-type */ $current, "\xEF\xBB\xBF", 0, 'utf-8') === 0) {
Loading history...
131 2
            $this->isNeedBOMRemove = false;
132
133 2
            $current = str_replace("\xEF\xBB\xBF", '', $current);
134
        }
135
136 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

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