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

Controls::fetchDelimitersOccurrence()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 13
nc 2
nop 2
crap 3
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
namespace League\Csv\Config;
14
15
use CallbackFilterIterator;
16
use InvalidArgumentException;
17
use League\Csv\AbstractCsv;
18
use LimitIterator;
19
use SplFileObject;
20
21
/**
22
 *  Trait to configure CSV document properties
23
 *
24
 * @package League.csv
25
 * @since  6.0.0
26
 *
27
 */
28
trait Controls
29
{
30
    use Validator;
31
32
    use StreamFilter;
33
34
    use Header;
35
36
    /**
37
     * the field delimiter (one character only)
38
     *
39
     * @var string
40
     */
41
    protected $delimiter = ',';
42
43
    /**
44
     * the field enclosure character (one character only)
45
     *
46
     * @var string
47
     */
48
    protected $enclosure = '"';
49
50
    /**
51
     * the field escape character (one character only)
52
     *
53
     * @var string
54
     */
55
    protected $escape = '\\';
56
57
    /**
58
     * newline character
59
     *
60
     * @var string
61
     */
62
    protected $newline = "\n";
63
64
    /**
65
     * The Input file BOM character
66 3
     *
67
     * @var string|null
68 3
     */
69 3
    protected $input_bom;
70
71 3
    /**
72
     * The Output file BOM character
73 3
     * @var string
74
     */
75
    protected $output_bom = '';
76
77
    /**
78
     * Sets the field delimiter
79
     *
80
     * @param string $delimiter
81
     *
82
     * @throws InvalidArgumentException If $delimiter is not a single character
83 18
     *
84
     * @return $this
85 18
     */
86
    public function setDelimiter($delimiter)
87
    {
88
        if (!$this->isValidCsvControls($delimiter)) {
89
            throw new InvalidArgumentException('The delimiter must be a single character');
90
        }
91
        $this->delimiter = $delimiter;
92
93 3
        return $this;
94
    }
95 3
96
    /**
97
     * Tell whether the submitted string is a valid CSV Control character
98
     *
99
     * @param string $str The submitted string
100
     *
101
     * @return bool
102
     */
103
    protected function isValidCsvControls($str)
104
    {
105
        return 1 == mb_strlen($str);
106
    }
107
108
    /**
109 12
     * Returns the current field delimiter
110
     *
111 12
     * @return string
112 9
     */
113 9
    public function getDelimiter()
114 9
    {
115 9
        return $this->delimiter;
116 9
    }
117 9
118 9
    /**
119 9
     * Detect Delimiters occurences in the CSV
120 9
     *
121 9
     * Returns a associative array where each key represents
122 6
     * a valid delimiter and each value the number of occurences
123 9
     *
124
     * @param string[] $delimiters the delimiters to consider
125 9
     * @param int      $nb_rows    Detection is made using $nb_rows of the CSV
126
     *
127
     * @return array
128
     */
129
    public function fetchDelimitersOccurrence(array $delimiters, $nb_rows = 1)
130
    {
131
        $nb_rows = $this->validateInteger($nb_rows, 1, 'The number of rows to consider must be a valid positive integer');
132
        $filter_row = function ($row) {
133
            return is_array($row) && count($row) > 1;
134
        };
135
        $delimiters = array_unique(array_filter($delimiters, [$this, 'isValidCsvControls']));
136
        $csv = $this->getIterator();
0 ignored issues
show
Bug introduced by
It seems like getIterator() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
137
        $res = [];
138
        foreach ($delimiters as $delim) {
139 114
            $csv->setCsvControl($delim, $this->enclosure, $this->escape);
140
            $iterator = new CallbackFilterIterator(new LimitIterator($csv, 0, $nb_rows), $filter_row);
141 114
            $res[$delim] = count(iterator_to_array($iterator, false), COUNT_RECURSIVE);
142 18
        }
143
        arsort($res, SORT_NUMERIC);
144 99
145
        return $res;
146
    }
147
148
    /**
149
     * Sets the field enclosure
150
     *
151
     * @param string $enclosure
152
     *
153
     * @throws InvalidArgumentException If $enclosure is not a single character
154
     *
155
     * @return $this
156
     */
157
    public function setEnclosure($enclosure)
158
    {
159
        if (!$this->isValidCsvControls($enclosure)) {
160
            throw new InvalidArgumentException('The enclosure must be a single character');
161
        }
162
        $this->enclosure = $enclosure;
163 3
164
        return $this;
165 3
    }
166 3
167
    /**
168 3
     * Returns the current field enclosure
169
     *
170 3
     * @return string
171
     */
172
    public function getEnclosure()
173
    {
174
        return $this->enclosure;
175
    }
176
177
    /**
178 24
     * Sets the field escape character
179
     *
180 24
     * @param string $escape
181
     *
182
     * @throws InvalidArgumentException If $escape is not a single character
183
     *
184
     * @return $this
185
     */
186
    public function setEscape($escape)
187
    {
188
        if (!$this->isValidCsvControls($escape)) {
189
            throw new InvalidArgumentException('The escape character must be a single character');
190
        }
191
        $this->escape = $escape;
192 3
193
        return $this;
194 3
    }
195 3
196
    /**
197 3
     * Returns the current field escape character
198
     *
199 3
     * @return string
200
     */
201
    public function getEscape()
202
    {
203
        return $this->escape;
204
    }
205
206
    /**
207 3
     * Sets the newline sequence characters
208
     *
209 3
     * @param string $newline
210
     *
211
     * @return $this
212
     */
213
    public function setNewline($newline)
214
    {
215
        $this->newline = (string) $newline;
216
217
        return $this;
218
    }
219 6
220
    /**
221 6
     * Returns the current newline sequence characters
222
     *
223 6
     * @return string
224
     */
225
    public function getNewline()
226
    {
227
        return $this->newline;
228
    }
229
230
    /**
231 6
     * Outputs all data on the CSV file
232
     *
233 6
     * @param string $filename CSV downloaded name if present adds extra headers
234
     *
235
     * @return int Returns the number of characters read from the handle
236
     *             and passed through to the output.
237
     */
238
    public function output($filename = null)
239
    {
240
        if (null !== $filename) {
241
            $filename = filter_var($filename, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
242
            header('Content-Type: text/csv');
243
            header('Content-Transfer-Encoding: binary');
244
            header("Content-Disposition: attachment; filename=\"$filename\"");
245
        }
246
247
        return $this->fpassthru();
248
    }
249
250
    /**
251
     * Returns the BOM sequence in use on Output methods
252
     *
253
     * @return string
254
     */
255
    public function getOutputBOM()
256
    {
257
        return $this->output_bom;
258
    }
259
260
    /**
261
     * Returns the BOM sequence of the given CSV
262
     *
263
     * @return string
264
     */
265
    public function getInputBOM()
266
    {
267
        if (null === $this->input_bom) {
268
            $bom = [
269
                AbstractCsv::BOM_UTF32_BE, AbstractCsv::BOM_UTF32_LE,
270
                AbstractCsv::BOM_UTF16_BE, AbstractCsv::BOM_UTF16_LE, AbstractCsv::BOM_UTF8,
271
            ];
272
            $csv = $this->getIterator();
0 ignored issues
show
Bug introduced by
It seems like getIterator() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
273
            $csv->setFlags(SplFileObject::READ_CSV);
274
            $csv->rewind();
275
            $line = $csv->fgets();
276
            $res = array_filter($bom, function ($sequence) use ($line) {
277
                return strpos($line, $sequence) === 0;
278
            });
279
280
            $this->input_bom = (string) array_shift($res);
281
        }
282
283
        return $this->input_bom;
284
    }
285
    /**
286
     * Outputs all data from the CSV
287
     *
288
     * @return int Returns the number of characters read from the handle
289
     *             and passed through to the output.
290
     */
291
    protected function fpassthru()
292
    {
293
        $bom = '';
294
        $input_bom = $this->getInputBOM();
295
        if ($this->output_bom && $input_bom != $this->output_bom) {
296
            $bom = $this->output_bom;
297
        }
298
        $csv = $this->getIterator();
0 ignored issues
show
Bug introduced by
It seems like getIterator() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
299
        $csv->setFlags(SplFileObject::READ_CSV);
300
        $csv->rewind();
301
        if (!empty($bom)) {
302
            $csv->fseek(mb_strlen($input_bom));
303
        }
304
        echo $bom;
305
        $res = $csv->fpassthru();
306
307
        return $res + strlen($bom);
308
    }
309
310
    /**
311
     * Retrieves the CSV content
312
     *
313
     * @return string
314
     */
315
    public function __toString()
316
    {
317
        ob_start();
318
        $this->fpassthru();
319
320
        return ob_get_clean();
321
    }
322
323
    /**
324
     * Sets the BOM sequence to prepend the CSV on output
325
     *
326
     * @param string $str The BOM sequence
327
     *
328
     * @return $this
329
     */
330
    public function setOutputBOM($str)
331
    {
332
        if (empty($str)) {
333
            $this->output_bom = '';
334
335
            return $this;
336
        }
337
338
        $this->output_bom = (string) $str;
339
340
        return $this;
341
    }
342
}
343