Failed Conditions
Push — code-cleanup ( b53488 )
by Jonathan
14:26
created

lib/EasyCSV/Reader.php (1 issue)

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
    public function __construct(string $path, string $mode = 'r+', bool $headersInFirstRow = true)
37
    {
38
        parent::__construct($path, $mode);
39
40
        $this->headersInFirstRow = $headersInFirstRow;
41
    }
42
43
    /**
44
     * @return string[]|bool
45
     */
46
    public function getHeaders()
47
    {
48
        $this->init();
49
50
        return $this->headers;
51
    }
52
53
    /**
54
     * @return string[]|bool
55
     */
56
    public function getRow()
57
    {
58
        $this->init();
59
60
        if ($this->isEof()) {
61
            return false;
62
        }
63
64
        $row     = $this->getCurrentRow();
65
        $isEmpty = $this->rowIsEmpty($row);
66
67
        if ($this->isEof() === false) {
68
            $this->getHandle()->next();
69
        }
70
71
        if ($isEmpty === false) {
72
            return ($this->headers !== false && is_array($this->headers)) ? array_combine($this->headers, $row) : $row;
73
        }
74
75
        if ($isEmpty) {
76
            // empty row, transparently try the next row
77
            return $this->getRow();
78
        }
79
80
        return false;
81
    }
82
83
    public function isEof() : bool
84
    {
85
        return $this->getHandle()->eof();
86
    }
87
88
    /**
89
     * @return string[][]
90
     */
91
    public function getAll() : array
92
    {
93
        $data = [];
94
        while ($row = $this->getRow()) {
95
            $data[] = $row;
96
        }
97
98
        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
    public function getLineNumber() : int
102
    {
103
        return $this->getHandle()->key();
104
    }
105
106
    /**
107
     * @return int|bool
108
     */
109
    public function getLastLineNumber()
110
    {
111
        if ($this->lastLine !== false) {
112
            return $this->lastLine;
113
        }
114
115
        $this->getHandle()->seek($this->getHandle()->getSize());
116
        $lastLine = $this->getHandle()->key();
117
118
        $this->getHandle()->rewind();
119
120
        return $this->lastLine = $lastLine;
121
    }
122
123
    /**
124
     * @return string[]
125
     */
126
    public function getCurrentRow() : array
127
    {
128
        $current = $this->getHandle()->current();
129
130
        if ($this->isNeedBOMRemove && mb_strpos($current, "\xEF\xBB\xBF", 0, 'utf-8') === 0) {
131
            $this->isNeedBOMRemove = false;
132
133
            $current = str_replace("\xEF\xBB\xBF", '', $current);
134
        }
135
136
        return str_getcsv($current, $this->delimiter, $this->enclosure);
137
    }
138
139
    public function advanceTo(int $lineNumber) : void
140
    {
141
        if ($this->headerLine > $lineNumber) {
142
            throw new LogicException(sprintf(
143
                'Line Number %s is before the header line that was set',
144
                $lineNumber
145
            ));
146
        } elseif ($this->headerLine === $lineNumber) {
147
            throw new LogicException(sprintf(
148
                'Line Number %s is equal to the header line that was set',
149
                $lineNumber
150
            ));
151
        }
152
153
        if ($lineNumber > 0) {
154
            $this->getHandle()->seek($lineNumber - 1);
155
        } // check the line before
156
157
        if ($this->isEof()) {
158
            throw new LogicException(sprintf(
159
                'Line Number %s is past the end of the file',
160
                $lineNumber
161
            ));
162
        }
163
164
        $this->getHandle()->seek($lineNumber);
165
    }
166
167
    public function setHeaderLine(int $lineNumber) : bool
168
    {
169
        if ($lineNumber === 0) {
170
            return false;
171
        }
172
173
        $this->headersInFirstRow = false;
174
175
        $this->headerLine = $lineNumber;
176
177
        $this->getHandle()->seek($lineNumber);
178
179
        // get headers
180
        $this->headers = $this->getRow();
181
182
        return true;
183
    }
184
185
    protected function init() : void
186
    {
187
        if ($this->init === true) {
188
            return;
189
        }
190
191
        $this->init = true;
192
193
        if ($this->headersInFirstRow !== true) {
194
            return;
195
        }
196
197
        $this->getHandle()->rewind();
198
199
        $this->headerLine = 0;
200
201
        $this->headers = $this->getRow();
202
    }
203
204
    /**
205
     * @param string[]|null[] $row
206
     */
207
    protected function rowIsEmpty(array $row) : bool
208
    {
209
        $emptyRow               = ($row === [null]);
210
        $emptyRowWithDelimiters = (array_filter($row) === []);
211
        $isEmpty                = false;
212
213
        if ($emptyRow) {
214
            $isEmpty = true;
215
216
            return $isEmpty;
217
        } elseif ($emptyRowWithDelimiters) {
218
            $isEmpty = true;
219
220
            return $isEmpty;
221
        }
222
223
        return $isEmpty;
224
    }
225
}
226