Completed
Pull Request — master (#245)
by ignace nyamagana
02:10
created

RFC4180Field::addFormatterTo()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 2
nop 2
dl 0
loc 21
rs 9.0534
c 0
b 0
f 0
ccs 8
cts 8
cp 1
crap 4
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.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
declare(strict_types=1);
14
15
namespace League\Csv;
16
17
use InvalidArgumentException;
18
use php_user_filter;
19
20
/**
21
 * A stream filter to conform the CSV field to RFC4180
22
 *
23
 * @see https://tools.ietf.org/html/rfc4180#section-2
24
 *
25
 * @package League.csv
26
 * @since   9.0.0
27
 * @author  Ignace Nyamagana Butera <[email protected]>
28
 */
29
class RFC4180Field extends php_user_filter
30
{
31
    const FILTERNAME = 'convert.league.csv.rfc4180';
32
33
    /**
34
     * the filter name used to instantiate the class with
35
     *
36
     * @var string
37
     */
38
    public $filtername;
39
40
    /**
41
     * Contents of the params parameter passed to stream_filter_append
42
     * or stream_filter_prepend functions
43
     *
44
     * @var mixed
45
     */
46
    public $params;
47
48
    /**
49
     * The value being search for
50
     *
51
     * @var string
52
     */
53
    protected $search;
54
55
    /**
56
     * The replacement value that replace found $search values
57
     *
58
     * @var string
59
     */
60
    protected $replace;
61
62
    /**
63
     * Characters that triggers enclosure with PHP fputcsv
64
     *
65
     * @var string
66
     */
67
    protected static $force_enclosure = "\n\r\t ";
68 6
69
    /**
70 6
     * Static method to add the stream filter to a {@link AbstractCsv} object
71
     *
72 6
     * @param AbstractCsv $csv
73 6
     * @param string      $whitespace_replace
74 6
     *
75 6
     * @return AbstractCsv
76
     */
77
    public static function addTo(AbstractCsv $csv, string $whitespace_replace = ''): AbstractCsv
78
    {
79
        self::register();
80
81
        $params = [
82 6
            'enclosure' => $csv->getEnclosure(),
83
            'escape' => $csv->getEscape(),
84 6
            'mode' => $csv->getStreamFilterMode(),
85 2
        ];
86
87 6
        if ($csv instanceof Writer && '' != $whitespace_replace) {
88
            self::addFormatterTo($csv, $whitespace_replace);
89
            $params['whitespace_replace'] = $whitespace_replace;
90
        }
91
92
        return $csv->addStreamFilter(self::FILTERNAME, $params);
93
    }
94 4
95
    /**
96 4
     * Add a formatter to the {@link Writer} object to format the record
97
     * field to avoid enclosure around a field with an empty space
98
     *
99
     * @param Writer $csv
100
     * @param string $whitespace_replace
101
     *
102 18
     * @return Writer
103
     */
104 18
    public static function addFormatterTo(Writer $csv, string $whitespace_replace): Writer
105 10
    {
106
        $force_enclosure = self::$force_enclosure.$csv->getDelimiter().$csv->getEscape().$csv->getEnclosure();
0 ignored issues
show
Unused Code introduced by
$force_enclosure is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
107
        if ('' == $whitespace_replace || strlen($whitespace_replace) != strcspn($whitespace_replace, self::$force_enclosure)) {
108 6
            throw new InvalidArgumentException('The sequence contains a character that enforces enclosure or is a CSV control character or is the empty string.');
109 6
        }
110 6
111 4
        $mapper = function ($value) use ($whitespace_replace) {
112
            if (is_string($value)) {
113
                return str_replace(' ', $whitespace_replace, $value);
114 6
            }
115
116
            return $value;
117
        };
118
119
        $formatter = function (array $record) use ($mapper): array {
120
            return array_map($mapper, $record);
121
        };
122
123 16
        return $csv->addFormatter($formatter);
124
    }
125 16
126
    /**
127 16
     * Static method to register the class as a stream filter
128 16
     */
129 16
    public static function register()
130
    {
131
        if (!in_array(self::FILTERNAME, stream_get_filters())) {
132
            stream_filter_register(self::FILTERNAME, __CLASS__);
133
        }
134
    }
135 6
136
    /**
137 6
     * Static method to return the stream filter filtername
138 6
     *
139 6
     * @return string
140 6
     */
141
    public static function getFiltername(): string
142
    {
143 6
        return self::FILTERNAME;
144
    }
145
146
    /**
147
     * @inheritdoc
148
     */
149
    public function filter($in, $out, &$consumed, $closing)
150
    {
151
        while ($bucket = stream_bucket_make_writeable($in)) {
152
            $bucket->data = str_replace($this->search, $this->replace, $bucket->data);
153
            $consumed += $bucket->datalen;
154
            stream_bucket_append($out, $bucket);
155
        }
156
157
        return PSFS_PASS_ON;
158
    }
159
160
    /**
161
     * @inheritdoc
162
     */
163
    public function onCreate()
164
    {
165
        if (!$this->isValidParams($this->params)) {
166
            return false;
167
        }
168
169
        $this->search = $this->params['escape'].$this->params['enclosure'];
170
        $this->replace = $this->params['enclosure'].$this->params['enclosure'];
171
        if (STREAM_FILTER_WRITE != $this->params['mode']) {
172
            return true;
173
        }
174
175
        $this->search = $this->search;
176
        $this->replace = $this->search.$this->params['enclosure'];
177
        if ($this->isValidSequence($this->params)) {
178
            $this->search = [$this->search, $this->params['whitespace_replace']];
0 ignored issues
show
Documentation Bug introduced by
It seems like array($this->search, $th...['whitespace_replace']) of type array<integer,?,{"0":"string","1":"?"}> is incompatible with the declared type string of property $search.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
179
            $this->replace = [$this->replace, ' '];
0 ignored issues
show
Documentation Bug introduced by
It seems like array($this->replace, ' ') of type array<integer,string,{"0":"string","1":"string"}> is incompatible with the declared type string of property $replace.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
180
        }
181
182
        return true;
183
    }
184
185
    /**
186
     * Validate params property
187
     *
188
     * @param array $params
189
     *
190
     * @return bool
191
     */
192
    protected function isValidParams(array $params): bool
193
    {
194
        static $mode_list = [STREAM_FILTER_READ => 1, STREAM_FILTER_WRITE => 1];
195
196
        return isset($params['enclosure'], $params['escape'], $params['mode'], $mode_list[$params['mode']])
197
            && 1 == strlen($params['enclosure'])
198
            && 1 == strlen($params['escape']);
199
    }
200
201
    /**
202
     * Is Valid White space replaced sequence
203
     *
204
     * @param array $params
205
     *
206
     * @return bool
207
     */
208
    protected function isValidSequence(array $params)
209
    {
210
        return isset($params['whitespace_replace'])
211
            && strlen($params['whitespace_replace']) == strcspn($params['whitespace_replace'], self::$force_enclosure);
212
    }
213
}
214