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

Statement::setOffset()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 2
eloc 7
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 ArrayIterator;
16
use CallbackFilterIterator;
17
use InvalidArgumentException;
18
use Iterator;
19
use League\Csv\Config\Validator;
20
use LimitIterator;
21
22
/**
23
 * A simple Statement class to fetch rows against a Csv file object
24
 *
25
 * @package League.csv
26
 * @since  4.2.1
27
 *
28
 */
29
class Statement
30
{
31
    use Validator;
32
33
    /**
34
     * Callables to filter the iterator
35
     *
36
     * @var callable[]
37
     */
38
    protected $filters = [];
39
40
    /**
41
     * Callables to sort the iterator
42
     *
43
     * @var callable[]
44
     */
45
    protected $sort_by = [];
46
47
    /**
48
     * iterator Offset
49
     *
50
     * @var int
51
     */
52
    protected $offset = 0;
53
54
    /**
55
     * iterator maximum length
56
     *
57
     * @var int
58
     */
59
    protected $limit = -1;
60
61
    /**
62
     * @inheritdoc
63
     */
64
    public function __set($property, $value)
65
    {
66
        throw new InvalidArgumentException(sprintf('%s is an undefined property', $property));
67
    }
68
69
    /**
70
     * @inheritdoc
71
     */
72
    public function __unset($property)
73
    {
74
        throw new InvalidArgumentException(sprintf('%s is an undefined property', $property));
75
    }
76
77
    /**
78
     * Returns a Record object
79
     *
80
     * @return RecordSet
81
     */
82
    public function process(AbstractCsv $csv)
83
    {
84
        $input_encoding = $csv->getInputEncoding();
85
        $use_converter = $this->useInternalConverter($csv);
86
        $header = $csv->getHeader();
87
        $header_offset = $csv->getHeaderOffset();
88
        $filters = [];
89
        if (null !== $header_offset) {
90
            $filters[] = function ($row, $index) use ($header_offset) {
91
                return $index !== $header_offset;
92
            };
93
        }
94
95
        $iterator = $this->applyBOMStripping($csv);
96
        $iterator = $this->applyIteratorFilter($iterator, $filters);
97
        $iterator = $this->applyIteratorSortBy($iterator);
98
        $iterator = new LimitIterator($iterator, $this->offset, $this->limit);
99
        $iterator = $this->convertToUtf8($iterator, $input_encoding, $use_converter);
100
101
        return new RecordSet($this->applyHeader($iterator, $header), $header);
102
    }
103
104
    /**
105
     * Tell whether to use Stream Filter or not to convert the CSV
106
     *
107
     * @param AbstractCsv $csv
108
     *
109
     * @return bool
110
     */
111
    protected function useInternalConverter(AbstractCsv $csv)
112
    {
113
        return !('UTF-8' === $csv->getInputEncoding()
114
            || ($csv->isActiveStreamFilter() && \STREAM_FILTER_READ === $csv->getStreamFilterMode()));
115
    }
116
117
    /**
118
     * Set LimitIterator Offset
119
     *
120
     * @param $offset
121
     *
122
     * @return static
123
     */
124
    public function setOffset($offset)
125
    {
126
        $offset = $this->validateInteger($offset, 0, 'the offset must be a positive integer or 0');
127
        if ($offset === $this->offset) {
128
            return $this;
129
        }
130
131
        $clone = clone $this;
132
        $clone->offset = $offset;
133
134
        return $clone;
135
    }
136
137
    /**
138
     * Set LimitIterator Count
139
     *
140
     * @param int $limit
141
     *
142
     * @return static
143
     */
144
    public function setLimit($limit)
145
    {
146
        $limit = $this->validateInteger($limit, -1, 'the limit must an integer greater or equals to -1');
147
        if ($limit === $this->limit) {
148
            return $this;
149
        }
150
151
        $clone = clone $this;
152
        $clone->limit = $limit;
153
154
        return $clone;
155
    }
156
157
    /**
158
     * Set an Iterator sorting callable function
159
     *
160
     * @param callable $callable
161
     *
162
     * @return static
163
     */
164
    public function addSortBy(callable $callable)
165
    {
166
        $clone = clone $this;
167
        $clone->sort_by[] = $callable;
168
169
        return $clone;
170
    }
171
172
    /**
173
     * Set the Iterator filter method
174
     *
175
     * @param callable $callable
176
     *
177
     * @return static
178
     */
179
    public function addFilter(callable $callable)
180
    {
181
        $clone = clone $this;
182
        $clone->filters[] = $callable;
183
184
        return $clone;
185
    }
186
187
    /**
188
     * Remove the BOM sequence from the CSV
189
     *
190
     * @param AbstractCsv $csv
191
     *
192
     * @return Iterator
193
     */
194
    protected function applyBOMStripping(AbstractCsv $csv)
195
    {
196
        $bom_length = mb_strlen($csv->getInputBOM());
197
        if (0 === $bom_length) {
198
            return $csv->getIterator();
199
        }
200
201
        $enclosure = $csv->getEnclosure();
202
        $strip_BOM = function ($row, $index) use ($bom_length, $enclosure) {
203
            if (0 != $index) {
204
                return $row;
205
            }
206
207
            $row[0] = mb_substr($row[0], $bom_length);
208
            if ($row[0][0] === $enclosure && mb_substr($row[0], -1, 1) === $enclosure) {
209
                $row[0] = mb_substr($row[0], 1, -1);
210
            }
211
212
            return $row;
213
        };
214
215
        return new MapIterator($csv->getIterator(), $strip_BOM);
216
    }
217
218
    /**
219
    * Filter the Iterator
220
    *
221
    * @param Iterator $iterator
222
    *
223
    * @return Iterator
224
    */
225
    protected function applyIteratorFilter(Iterator $iterator, array $filters)
226
    {
227
        $reducer = function ($iterator, $callable) {
228
            return new CallbackFilterIterator($iterator, $callable);
229
        };
230
231
        $filters[] = function ($row) {
232
            return is_array($row) && $row != [null];
233
        };
234
235
        return array_reduce(array_merge($filters, $this->filters), $reducer, $iterator);
236
    }
237
238
    /**
239
    * Sort the Iterator
240
    *
241
    * @param Iterator $iterator
242
    *
243
    * @return Iterator
244
    */
245
    protected function applyIteratorSortBy(Iterator $iterator)
246
    {
247
        if (!$this->sort_by) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sort_by of type callable[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
248
            return $iterator;
249
        }
250
251
        $obj = new ArrayIterator(iterator_to_array($iterator));
252
        $obj->uasort(function ($row_a, $row_b) {
253
            $res = 0;
254
            foreach ($this->sort_by as $compare) {
255
                if (0 !== ($res = call_user_func($compare, $row_a, $row_b))) {
256
                    break;
257
                }
258
            }
259
260
            return $res;
261
        });
262
263
        return $obj;
264
    }
265
266
    /**
267
     * Convert Iterator to UTF-8 if needed
268
     *
269
     * @param string   $input_encoding
270
     * @param Iterator $iterator
271
     * @param bool     $user_converter
0 ignored issues
show
Documentation introduced by
There is no parameter named $user_converter. Did you maybe mean $use_converter?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

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

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

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

Loading history...
272
     *
273
     * @return Iterator
274
     */
275
    protected function convertToUtf8(Iterator $iterator, $input_encoding, $use_converter = false)
276
    {
277
        if (!$use_converter) {
278
            return $iterator;
279
        }
280
281
        $convert_cell = function ($value) use ($input_encoding) {
282
            return mb_convert_encoding($value, 'UTF-8', $input_encoding);
283
        };
284
285
        $convert_row = function ($row) use ($convert_cell) {
286
            return array_map($convert_cell, (array) $row);
287
        };
288
289
        return new MapIterator($iterator, $convert_row);
290
    }
291
292
    /**
293
     * Apply CSV header to the return iterator
294
     *
295
     * @param Iterator $iterator
296
     * @param array    $header
297
     *
298
     * @return Iterator
299
     */
300
    protected function applyHeader(Iterator $iterator, array $header)
301
    {
302
        if (empty($header)) {
303
            return $iterator;
304
        }
305
306
        $count = count($header);
307
        $combine_array = function (array $row) use ($header, $count) {
308
            if ($count != count($row)) {
309
                $row = array_slice(array_pad($row, $count, null), 0, $count);
310
            }
311
312
            return array_combine($header, $row);
313
        };
314
315
        return new MapIterator($iterator, $combine_array);
316
    }
317
}
318