Completed
Pull Request — master (#178)
by ignace nyamagana
02:42
created

Records::validateKeys()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 12
rs 8.8571
cc 5
eloc 6
nc 2
nop 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 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 CallbackFilterIterator;
16
use Countable;
17
use DomDocument;
18
use Generator;
19
use InvalidArgumentException;
20
use Iterator;
21
use IteratorAggregate;
22
use JsonSerializable;
23
use League\Csv\Config\Validator;
24
25
/**
26
 * A class to extract and convert data from a CSV
27
 *
28
 * @package League.csv
29
 * @since  3.0.0
30
 *
31
 */
32
class Records implements Countable, IteratorAggregate, JsonSerializable
33
{
34
    use Validator;
35
36
    /**
37
     * @var AbstractCsv
38
     */
39
    protected $csv;
40
41
    /**
42
     * @var Iterator
43
     */
44
    protected $iterator;
45
46
    /**
47
     * New Instance
48
     *
49
     * @param AbstractCsv $csv
50
     * @param Query       $query
51
     */
52
    public function __construct(AbstractCsv $csv, Query $query)
53
    {
54
        $this->csv = $csv;
55
        $this->iterator = $query($csv);
56
    }
57
58
    /**
59
     * @inheritdoc
60
     */
61
    public function __destruct()
62
    {
63
        $this->csv = null;
64
        $this->iterator = null;
65
    }
66
67
    /**
68
     * Returns a HTML table representation of the CSV Table
69
     *
70
     * @param string $class_attr optional classname
71
     *
72
     * @return string
73
     */
74
    public function toHTML($class_attr = 'table-csv-data')
75
    {
76
        $doc = $this->toXML('table', 'tr', 'td');
77
        $doc->documentElement->setAttribute('class', $class_attr);
78
79
        return $doc->saveHTML($doc->documentElement);
80
    }
81
82
    /**
83
     * Transforms a CSV into a XML
84
     *
85
     * @param string $root_name XML root node name
86
     * @param string $row_name  XML row node name
87
     * @param string $cell_name XML cell node name
88
     *
89
     * @return DomDocument
90
     */
91
    public function toXML($root_name = 'csv', $row_name = 'row', $cell_name = 'cell')
92
    {
93
        $doc = new DomDocument('1.0', 'UTF-8');
94
        $root = $doc->createElement($root_name);
95
        foreach ($this->iterator as $row) {
96
            $rowElement = $doc->createElement($row_name);
97
            array_walk($row, function ($value) use (&$rowElement, $doc, $cell_name) {
98
                $content = $doc->createTextNode($value);
99
                $cell = $doc->createElement($cell_name);
100
                $cell->appendChild($content);
101
                $rowElement->appendChild($cell);
102
            });
103
            $root->appendChild($rowElement);
104
        }
105
        $doc->appendChild($root);
106
107
        return $doc;
108
    }
109
110
    /**
111
     * @inheritdoc
112
     */
113
    public function getIterator()
114
    {
115
        return $this->iterator;
116
    }
117
118
    /**
119
     * @inheritdoc
120
     */
121
    public function count()
122
    {
123
        return iterator_count($this);
124
    }
125
126
    /**
127
     * @inheritdoc
128
     */
129
    public function jsonSerialize()
130
    {
131
        return $this->fetchAll();
132
    }
133
134
    /**
135
     * Returns a sequential array of all founded records
136
     *
137
     * @return array
138
     */
139
    public function fetchAll()
140
    {
141
        return iterator_to_array($this, false);
142
    }
143
144
    /**
145
     * Returns the first row from the founded records
146
     *
147
     * @return array
148
     */
149
    public function fetchOne()
150
    {
151
        $this->iterator->rewind();
152
153
        return (array) $this->iterator->current();
154
    }
155
156
    /**
157
     * Returns the next value from a specific record column
158
     *
159
     * By default if no column index is provided the first column of the founded records is returned
160
     *
161
     * @param int $column_index CSV column index
162
     *
163
     * @return Iterator
164
     */
165
    public function fetchColumn($column_index = 0)
166
    {
167
        $column_index = $this->validateInteger($column_index, 0, 'the column index must be a positive integer or 0');
168
169
        $filter = function ($row) use ($column_index) {
170
            return isset($row[$column_index]);
171
        };
172
173
        $select = function ($row) use ($column_index) {
174
            return $row[$column_index];
175
        };
176
177
        return new MapIterator(new CallbackFilterIterator($this->iterator, $filter), $select);
178
    }
179
180
    /**
181
     * Fetches the next key-value pairs from a result set (first
182
     * column is the key, second column is the value).
183
     *
184
     * By default if no column index is provided:
185
     * - the first CSV column is used to provide the keys
186
     * - the second CSV column is used to provide the value
187
     *
188
     * @param int $offset_id The column index to serve as offset
189
     * @param int $value_id  The column index to serve as value
190
     *
191
     * @return Generator
192
     */
193
    public function fetchPairs($offset_id = 0, $value_id = 1)
194
    {
195
        $offset_id = $this->validateInteger($offset_id, 0, 'the offset column index must be a positive integer or 0');
196
        $value_id = $this->validateInteger($value_id, 0, 'the value column index must be a positive integer or 0');
197
        $filter = function ($row) use ($offset_id) {
198
            return isset($row[$offset_id]);
199
        };
200
        $select = function ($row) use ($offset_id, $value_id) {
201
            return [$row[$offset_id], isset($row[$value_id]) ? $row[$value_id] : null];
202
        };
203
204
        $iterator = new MapIterator(new CallbackFilterIterator($this->iterator, $filter), $select);
205
        foreach ($iterator as $row) {
206
            yield $row[0] => $row[1];
207
        }
208
    }
209
210
    /**
211
     * Fetch the next row from a result set
212
     *
213
     * The rows are presented as associated arrays
214
     *
215
     * @param int|array $offset_or_keys the name for each key member OR the row Index to be
0 ignored issues
show
Bug introduced by
There is no parameter named $offset_or_keys. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
216
     *                                  used as the associated named keys
217
     *
218
     * @throws InvalidArgumentException If the submitted keys are invalid
219
     *
220
     * @return Iterator
221
     */
222
    public function fetchAssoc($index = 0)
223
    {
224
        $keys = $this->getAssocKeys($index);
225
        $keys_count = count($keys);
226
        $combine_array = function (array $row) use ($keys, $keys_count) {
227
            if ($keys_count != count($row)) {
228
                $row = array_slice(array_pad($row, $keys_count, null), 0, $keys_count);
229
            }
230
231
            return array_combine($keys, $row);
232
        };
233
234
        return new MapIterator($this->iterator, $combine_array);
235
    }
236
237
    /**
238
     * Selects the array to be used as key for the fetchAssoc method
239
     *
240
     * @param int|array $index the assoc key OR the row Index to be used as the key index
241
     *
242
     * @return array
243
     */
244
    protected function getAssocKeys($index)
245
    {
246
        if (is_array($index)) {
247
            return $this->validateKeys($index);
248
        }
249
250
        $index = $this->validateInteger($index, 0, 'the row index must be a positive integer, 0 or a non empty array');
251
        $keys = (new static($this->csv, (new Query())->setOffset($index)->setLimit(1)))->fetchOne();
252
253
        return $this->validateKeys($keys);
254
    }
255
256
    /**
257
     * Validates the array to be used by the fetchAssoc method
258
     *
259
     * @param array $keys
260
     *
261
     * @throws InvalidArgumentException If the submitted array fails the assertion
262
     *
263
     * @return array
264
     */
265
    protected function validateKeys(array $keys)
266
    {
267
        $is_valid_keys = function ($value) {
268
            return is_scalar($value) || (is_object($value) && method_exists($value, '__toString'));
269
        };
270
271
        if (empty($keys) || $keys !== array_unique(array_filter($keys, $is_valid_keys))) {
272
            throw new InvalidArgumentException('Use a flat array with unique string values');
273
        }
274
275
        return $keys;
276
    }
277
}
278