Completed
Pull Request — master (#210)
by ignace nyamagana
04:08
created

RecordSet::__destruct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
/**
3
* This file is part of the League.csv library
4
*
5
* @license http://opensource.org/licenses/MIT
6
* @link https://github.com/thephpleague/csv/
7
* @version 9.0.0
8
* @package League.csv
9
*
10
* For the full copyright and license information, please view the LICENSE
11
* file that was distributed with this source code.
12
*/
13
declare(strict_types=1);
14
15
namespace League\Csv;
16
17
use CallbackFilterIterator;
18
use Countable;
19
use DomDocument;
20
use Generator;
21
use Iterator;
22
use IteratorAggregate;
23
use JsonSerializable;
24
use League\Csv\Config\ValidatorTrait;
25
use LimitIterator;
26
27
/**
28
 *  A class to manage extracting and filtering a CSV
29
 *
30
 * @package League.csv
31
 * @since  3.0.0
32
 *
33
 */
34
class RecordSet implements JsonSerializable, IteratorAggregate, Countable
35
{
36
    use ValidatorTrait;
37
38
    /**
39
     * Charset Encoding for the CSV
40
     *
41
     * This information is used when converting the CSV to XML or JSON
42
     *
43
     * @var string
44
     */
45
    protected $conversion_input_encoding = 'UTF-8';
46
47
    /**
48
     * The CSV iterator result
49
     *
50
     * @var Iterator
51
     */
52
    protected $iterator;
53
54
    /**
55
     * The CSV header
56
     *
57
     * @var array
58
     */
59
    protected $header = [];
60
61
    /**
62
     * New instance
63
     *
64
     * @param Iterator $iterator a CSV iterator created from Statement
65
     * @param array    $header   the CSV header
66
     */
67 114
    public function __construct(Iterator $iterator, array $header = [])
68
    {
69 114
        $this->iterator = $iterator;
70 114
        $this->header = $header;
71 114
    }
72
73
    /**
74
     * @inheritdoc
75
     */
76 114
    public function __destruct()
77
    {
78 114
        $this->iterator = null;
79 114
    }
80
81
    /**
82
     * Returns the column header associate with the RecordSet
83
     *
84
     * @return string[]
85
     */
86 8
    public function getHeader()
87
    {
88 8
        return $this->header;
89
    }
90
91
    /**
92
     * Gets the source CSV encoding charset
93
     *
94
     * @return string
95
     */
96 2
    public function getConversionInputEncoding(): string
97
    {
98 2
        return $this->conversion_input_encoding;
99
    }
100
101
    /**
102
     * Sets the CSV encoding charset
103
     *
104
     * @param string $str
105
     *
106
     * @return static
107
     */
108 6
    public function setConversionInputEncoding(string $str): self
109
    {
110 6
        $str = str_replace('_', '-', $str);
111 6
        $str = filter_var($str, FILTER_SANITIZE_STRING, ['flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH]);
112 6
        if (empty($str)) {
113 2
            throw new Exception('you should use a valid charset');
114
        }
115 4
        $this->conversion_input_encoding = strtoupper($str);
116
117 4
        return $this;
118
    }
119
120
    /**
121
     * Returns a HTML table representation of the CSV Table
122
     *
123
     * @param string $class_attr optional classname
124
     *
125
     * @return string
126
     */
127 2
    public function toHTML(string $class_attr = 'table-csv-data'): string
128
    {
129 2
        $doc = $this->toXML('table', 'tr', 'td');
130 2
        $doc->documentElement->setAttribute('class', $class_attr);
131
132 2
        return $doc->saveHTML($doc->documentElement);
133
    }
134
135
    /**
136
     * Transforms a CSV into a XML
137
     *
138
     * @param string $root_name XML root node name
139
     * @param string $row_name  XML row node name
140
     * @param string $cell_name XML cell node name
141
     *
142
     * @return DomDocument
143
     */
144 4
    public function toXML(string $root_name = 'csv', string $row_name = 'row', string $cell_name = 'cell'): DomDocument
145
    {
146 4
        $doc = new DomDocument('1.0', 'UTF-8');
147 4
        $root = $doc->createElement($root_name);
148 4
        foreach ($this->convertToUtf8($this->iterator) as $row) {
149 4
            $rowElement = $doc->createElement($row_name);
150
            array_walk($row, function ($value) use (&$rowElement, $doc, $cell_name) {
151 4
                $content = $doc->createTextNode($value);
152 4
                $cell = $doc->createElement($cell_name);
153 4
                $cell->appendChild($content);
154 4
                $rowElement->appendChild($cell);
155 4
            });
156 4
            $root->appendChild($rowElement);
157
        }
158 4
        $doc->appendChild($root);
159
160 4
        return $doc;
161
    }
162
163
    /**
164
     * Convert Csv file into UTF-8
165
     *
166
     * @param Iterator $iterator
167
     *
168
     * @return Iterator
169
     */
170 8
    protected function convertToUtf8(Iterator $iterator): Iterator
171
    {
172 8
        if (stripos($this->conversion_input_encoding, 'UTF-8') !== false) {
173 6
            return $iterator;
174
        }
175
176
        $convert_cell = function ($value) {
177 2
            return mb_convert_encoding((string) $value, 'UTF-8', $this->conversion_input_encoding);
178 2
        };
179
180
        $convert_row = function (array $row) use ($convert_cell) {
181 2
            $res = [];
182 2
            foreach ($row as $key => $value) {
183 2
                $res[$convert_cell($key)] = $convert_cell($value);
184
            }
185
186 2
            return $res;
187 2
        };
188
189 2
        return new MapIterator($iterator, $convert_row);
190
    }
191
192
    /**
193
     * @inheritdoc
194
     */
195 2
    public function getIterator(): Iterator
196
    {
197 2
        return $this->iterator;
198
    }
199
200
    /**
201
     * @inheritdoc
202
     */
203 2
    public function count()
204
    {
205 2
        return iterator_count($this->iterator);
206
    }
207
208
    /**
209
     * @inheritdoc
210
     */
211 4
    public function jsonSerialize()
212
    {
213 4
        return iterator_to_array($this->convertToUtf8($this->iterator), false);
214
    }
215
216
    /**
217
     * Returns a sequential array of all CSV lines
218
     *
219
     * @return array
220
     */
221 64
    public function fetchAll(): array
222
    {
223 64
        return iterator_to_array($this->iterator, false);
224
    }
225
226
    /**
227
     * Returns a single row from the CSV
228
     *
229
     * By default if no offset is provided the first row of the CSV is selected
230
     *
231
     * @param int $offset the CSV row offset
232
     *
233
     * @return array
234
     */
235 6
    public function fetchOne(int $offset = 0): array
236
    {
237 6
        $offset = $this->filterInteger($offset, 0, 'the submitted offset is invalid');
238 4
        $it = new LimitIterator($this->iterator, $offset, 1);
239 4
        $it->rewind();
240
241 4
        return (array) $it->current();
242
    }
243
244
    /**
245
     * Returns the next value from a single CSV column
246
     *
247
     * By default if no column index is provided the first column of the CSV is selected
248
     *
249
     * @param string|int $column CSV column index
250
     *
251
     * @return Iterator
252
     */
253 16
    public function fetchColumn($column = 0): Iterator
254
    {
255 16
        $column = $this->getFieldIndex($column, 'the column index value is invalid');
256
        $filter = function (array $row) use ($column) {
257 14
            return isset($row[$column]);
258 14
        };
259
260
        $select = function ($row) use ($column) {
261 10
            return $row[$column];
262 14
        };
263
264 14
        return new MapIterator(new CallbackFilterIterator($this->iterator, $filter), $select);
265
    }
266
267
    /**
268
     * Filter a field name against the CSV header if any
269
     *
270
     * @param string|int $field         the field name or the field index
271
     * @param string     $error_message the associated error message
272
     *
273
     * @throws Exception if the field is invalid
274
     *
275
     * @return string|int
276
     */
277 24
    protected function getFieldIndex($field, $error_message)
278
    {
279 24
        if (false !== array_search($field, $this->header, true)) {
280 2
            return $field;
281
        }
282
283 22
        $index = $this->filterInteger($field, 0, $error_message);
284 22
        if (empty($this->header)) {
285 18
            return $index;
286
        }
287
288 4
        if (false !== ($index = array_search($index, array_flip($this->header), true))) {
289 2
            return $index;
290
        }
291
292 2
        throw new Exception($error_message);
293
    }
294
295
    /**
296
     * Fetches the next key-value pairs from a result set (first
297
     * column is the key, second column is the value).
298
     *
299
     * By default if no column index is provided:
300
     * - the first CSV column is used to provide the keys
301
     * - the second CSV column is used to provide the value
302
     *
303
     * @param string|int $offset_index The column index to serve as offset
304
     * @param string|int $value_index  The column index to serve as value
305
     *
306
     * @return Generator
307
     */
308 8
    public function fetchPairs($offset_index = 0, $value_index = 1): Generator
309
    {
310 8
        $offset = $this->getFieldIndex($offset_index, 'the offset index value is invalid');
311 8
        $value = $this->getFieldIndex($value_index, 'the value index value is invalid');
312
        $filter = function ($row) use ($offset) {
313 8
            return isset($row[$offset]);
314 8
        };
315
316 8
        $select = function ($row) use ($offset, $value) {
317 6
            return [$row[$offset], $row[$value] ?? null];
318 8
        };
319
320 8
        $it = new MapIterator(new CallbackFilterIterator($this->iterator, $filter), $select);
321 8
        foreach ($it as $row) {
322 6
            yield $row[0] => $row[1];
323
        }
324 8
    }
325
}
326