Completed
Pull Request — master (#178)
by ignace nyamagana
03:07
created

Records   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 270
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 23
c 4
b 0
f 0
lcom 1
cbo 3
dl 0
loc 270
rs 10

14 Methods

Rating   Name   Duplication   Size   Complexity  
A validateKeys() 0 8 3
A isValidKey() 0 4 3
A __construct() 0 5 1
A getIterator() 0 4 1
A count() 0 4 1
A fetchAll() 0 4 1
A fetchOne() 0 7 1
A fetchColumn() 0 14 1
A fetchPairs() 0 19 3
A fetchAssoc() 0 14 2
A getAssocInfos() 0 20 2
A jsonSerialize() 0 4 1
A toHTML() 0 7 1
A toXML() 0 18 2
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 8.1.1
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
namespace League\Csv;
14
15
use Countable;
16
use DomDocument;
17
use Generator;
18
use InvalidArgumentException;
19
use Iterator;
20
use IteratorAggregate;
21
use JsonSerializable;
22
use League\Csv\Config\Validator;
23
24
/**
25
 * A class to extract and convert data from a CSV
26
 *
27
 * @package League.csv
28
 * @since  3.0.0
29
 *
30
 */
31
class Records implements Countable, IteratorAggregate, JsonSerializable
32
{
33
    use Validator;
34
35
    /**
36
     * @var AbstractCsv
37
     */
38
    protected $csv;
39
40
    /**
41
     * @var Query
42
     */
43
    protected $query;
44
45
    /**
46
     * New Instance
47
     *
48
     * @param AbstractCsv $csv
49
     * @param Query       $query
50
     */
51
    public function __construct(AbstractCsv $csv, Query $query)
52
    {
53
        $this->csv = $csv;
54
        $this->query = $query;
55
    }
56
57
    /**
58
     * @inheritdoc
59
     */
60
    public function getIterator()
61
    {
62
        return $this->query->__invoke($this->csv);
63
    }
64
65
    /**
66
     * @inheritdoc
67
     */
68
    public function count()
69
    {
70
        return iterator_count($this);
71
    }
72
73
    /**
74
     * Returns a sequential array of all CSV lines
75
     *
76
     * The callable function will be applied to each Iterator item
77
     *
78
     * @return array
79
     */
80
    public function fetchAll()
81
    {
82
        return iterator_to_array($this, false);
83
    }
84
85
    /**
86
     * Returns a single row from the CSV
87
     *
88
     * By default if no offset is provided the first row of the CSV is selected
89
     *
90
     * @param int $offset the CSV row offset
91
     *
92
     * @return array
93
     */
94
    public function fetchOne($offset = 0)
95
    {
96
        $iterator = $this->query->setOffset($offset)->setLimit(1)->__invoke($this->csv);
97
        $iterator->rewind();
98
99
        return (array) $iterator->current();
100
    }
101
102
    /**
103
     * Returns the next value from a single CSV column
104
     *
105
     * The callable function will be applied to each value to be return
106
     *
107
     * By default if no column index is provided the first column of the CSV is selected
108
     *
109
     * @param int $column_index CSV column index
110
     *
111
     * @return Iterator
112
     */
113
    public function fetchColumn($column_index = 0)
114
    {
115
        $column_index = $this->validateInteger($column_index, 0, 'the column index must be a positive integer or 0');
116
117
        $filter_column = function ($row) use ($column_index) {
118
            return isset($row[$column_index]);
119
        };
120
121
        $select_column = function ($row) use ($column_index) {
122
            return $row[$column_index];
123
        };
124
125
        return new MapIterator($this->query->addFilter($filter_column)->__invoke($this->csv), $select_column);
126
    }
127
128
    /**
129
     * Fetches the next key-value pairs from a result set (first
130
     * column is the key, second column is the value).
131
     *
132
     * By default if no column index is provided:
133
     * - the first CSV column is used to provide the keys
134
     * - the second CSV column is used to provide the value
135
     *
136
     * @param int $offset_index The column index to serve as offset
137
     * @param int $value_index  The column index to serve as value
138
     *
139
     * @return Generator
140
     */
141
    public function fetchPairs($offset_index = 0, $value_index = 1)
142
    {
143
        $offset_index = $this->validateInteger($offset_index, 0, 'the offset column index must be a positive integer or 0');
144
        $value_index = $this->validateInteger($value_index, 0, 'the value column index must be a positive integer or 0');
145
        $filter_pairs = function ($row) use ($offset_index) {
146
            return isset($row[$offset_index]);
147
        };
148
        $select_pairs = function ($row) use ($offset_index, $value_index) {
149
            return [
150
                $row[$offset_index],
151
                isset($row[$value_index]) ? $row[$value_index] : null,
152
            ];
153
        };
154
155
        $iterator = $this->query->addFilter($filter_pairs)->__invoke($this->csv);
156
        foreach (new MapIterator($iterator, $select_pairs) as $row) {
157
            yield $row[0] => $row[1];
158
        }
159
    }
160
161
    /**
162
     * Fetch the next row from a result set
163
     *
164
     * The rows are presented as associated arrays
165
     * The callable function will be applied to each row
166
     *
167
     * @param int|array $offset_or_keys the name for each key member OR the row Index to be
168
     *                                  used as the associated named keys
169
     *
170
     * @throws InvalidArgumentException If the submitted keys are invalid
171
     *
172
     * @return Iterator
173
     */
174
    public function fetchAssoc($offset_or_keys = 0)
175
    {
176
        list($iterator, $keys) = $this->getAssocInfos($offset_or_keys);
177
        $keys_count = count($keys);
178
        $combine_array = function (array $row) use ($keys, $keys_count) {
179
            if ($keys_count != count($row)) {
180
                $row = array_slice(array_pad($row, $keys_count, null), 0, $keys_count);
181
            }
182
183
            return array_combine($keys, $row);
184
        };
185
186
        return new MapIterator($iterator, $combine_array);
0 ignored issues
show
Compatibility introduced by
$iterator of type object<Traversable> is not a sub-type of object<Iterator>. It seems like you assume a child interface of the interface Traversable to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
187
    }
188
189
    /**
190
     * Selects the array to be used as key for the fetchAssoc method
191
     *
192
     * @param int|array $offset_or_keys the assoc key OR the row Index to be used
193
     *                                  as the key index
194
     *
195
     * @throws InvalidArgumentException If the row index and/or the resulting array is invalid
196
     *
197
     * @return array
198
     */
199
    protected function getAssocInfos($offset_or_keys)
200
    {
201
        if (is_array($offset_or_keys)) {
202
            return [$this->getIterator(), $this->validateKeys($offset_or_keys)];
203
        }
204
205
        $offset_or_keys = $this->validateInteger(
206
            $offset_or_keys,
207
            0,
208
            'the row index must be a positive integer, 0 or a non empty array'
209
        );
210
211
        $keys = (new static($this->csv, (new Query())->setLimit(1)))->fetchOne($offset_or_keys);
212
        $keys = $this->validateKeys($keys);
213
        $filterOutRow = function ($row, $rowIndex) use ($offset_or_keys) {
214
            return $rowIndex != $offset_or_keys;
215
        };
216
217
        return [$this->query->addFilter($filterOutRow)->__invoke($this->csv), $keys];
218
    }
219
220
    /**
221
     * Validates the array to be used by the fetchAssoc method
222
     *
223
     * @param array $keys
224
     *
225
     * @throws InvalidArgumentException If the submitted array fails the assertion
226
     *
227
     * @return array
228
     */
229
    protected function validateKeys(array $keys)
230
    {
231
        if (empty($keys) || $keys !== array_unique(array_filter($keys, [$this, 'isValidKey']))) {
232
            throw new InvalidArgumentException('Use a flat array with unique string values');
233
        }
234
235
        return $keys;
236
    }
237
238
    /**
239
     * Returns whether the submitted value can be used as string
240
     *
241
     * @param mixed $value
242
     *
243
     * @return bool
244
     */
245
    protected function isValidKey($value)
246
    {
247
        return is_scalar($value) || (is_object($value) && method_exists($value, '__toString'));
248
    }
249
250
    /**
251
     * @inheritdoc
252
     */
253
    public function jsonSerialize()
254
    {
255
        return $this->fetchAll();
256
    }
257
258
    /**
259
     * Returns a HTML table representation of the CSV Table
260
     *
261
     * @param string $class_attr optional classname
262
     *
263
     * @return string
264
     */
265
    public function toHTML($class_attr = 'table-csv-data')
266
    {
267
        $doc = $this->toXML('table', 'tr', 'td');
268
        $doc->documentElement->setAttribute('class', $class_attr);
269
270
        return $doc->saveHTML($doc->documentElement);
271
    }
272
273
    /**
274
     * Transforms a CSV into a XML
275
     *
276
     * @param string $root_name XML root node name
277
     * @param string $row_name  XML row node name
278
     * @param string $cell_name XML cell node name
279
     *
280
     * @return DomDocument
281
     */
282
    public function toXML($root_name = 'csv', $row_name = 'row', $cell_name = 'cell')
283
    {
284
        $doc = new DomDocument('1.0', 'UTF-8');
285
        $root = $doc->createElement($root_name);
286
        foreach ($this->getIterator() as $row) {
287
            $rowElement = $doc->createElement($row_name);
288
            array_walk($row, function ($value) use (&$rowElement, $doc, $cell_name) {
289
                $content = $doc->createTextNode($value);
290
                $cell = $doc->createElement($cell_name);
291
                $cell->appendChild($content);
292
                $rowElement->appendChild($cell);
293
            });
294
            $root->appendChild($rowElement);
295
        }
296
        $doc->appendChild($root);
297
298
        return $doc;
299
    }
300
}
301