Completed
Push — master ( a3add9...6ca7fe )
by ignace nyamagana
02:56
created

Writer::consolidate()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 20
ccs 12
cts 12
cp 1
rs 9.2
cc 4
eloc 12
nc 6
nop 0
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.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
declare(strict_types=1);
14
15
namespace League\Csv;
16
17
use League\Csv\Exception\InsertionException;
18
use League\Csv\Exception\OutOfRangeException;
19
use Traversable;
20
use TypeError;
21
22
/**
23
 * A class to manage records insertion into a CSV Document
24
 *
25
 * @package League.csv
26
 * @since   4.0.0
27
 * @author  Ignace Nyamagana Butera <[email protected]>
28
 *
29
 */
30
class Writer extends AbstractCsv
31
{
32
    /**
33
     * callable collection to format the record before insertion
34
     *
35
     * @var callable[]
36
     */
37
    protected $formatters = [];
38
39
    /**
40
     * callable collection to validate the record before insertion
41
     *
42
     * @var callable[]
43
     */
44
    protected $validators = [];
45
46
    /**
47
     * newline character
48
     *
49
     * @var string
50
     */
51
    protected $newline = "\n";
52
53
    /**
54
     * Insert records count for flushing
55
     *
56
     * @var int
57
     */
58
    protected $flush_counter = 0;
59
60
    /**
61
     * Buffer flush threshold
62
     *
63
     * @var int|null
64
     */
65
    protected $flush_threshold;
66
67
    /**
68
     * @inheritdoc
69
     */
70
    protected $stream_filter_mode = STREAM_FILTER_WRITE;
71
72
    /**
73
     * Returns the current newline sequence characters
74
     *
75
     * @return string
76
     */
77 2
    public function getNewline(): string
78
    {
79 2
        return $this->newline;
80
    }
81
82
    /**
83
     * Get the flush threshold
84
     *
85
     * @return int|null
86
     */
87 2
    public function getFlushThreshold()
88
    {
89 2
        return $this->flush_threshold;
90
    }
91
92
    /**
93
     * Adds multiple lines to the CSV document
94
     *
95
     * a simple wrapper method around insertOne
96
     *
97
     * @param Traversable|array $records a multidimensional array or a Traversable object
98
     *
99
     * @return int
100
     */
101 6
    public function insertAll($records): int
102
    {
103 6
        if (!is_iterable($records)) {
104 2
            throw new TypeError(sprintf('%s() expects argument passed to be iterable, %s given', __METHOD__, gettype($records)));
105
        }
106
107 4
        $bytes = 0;
108 4
        foreach ($records as $record) {
109 4
            $bytes += $this->insertOne($record);
110
        }
111
112 4
        $this->flush_counter = 0;
113 4
        $this->document->fflush();
114
115 4
        return $bytes;
116
    }
117
118
    /**
119
     * Adds a single line to a CSV document
120
     *
121
     * @param string[] $record an array
122
     *
123
     * @throws InsertionException If the record can not be inserted
124
     *
125
     * @return int
126
     */
127 16
    public function insertOne(array $record): int
128
    {
129 16
        $record = array_reduce($this->formatters, [$this, 'formatRecord'], $record);
130 16
        $this->validateRecord($record);
131 14
        $bytes = $this->document->fputcsv($record, ...$this->document->getCsvControl());
132 14
        if (!$bytes) {
133 2
            throw InsertionException::createFromStream($record);
134
        }
135
136 12
        return $bytes + $this->consolidate();
137
    }
138
139
    /**
140
     * Format the given record
141
     *
142
     * @param string[] $record
143
     * @param callable $formatter
144
     *
145
     * @return string[]
146
     */
147 2
    protected function formatRecord(array $record, callable $formatter): array
148
    {
149 2
        return $formatter($record);
150
    }
151
152
    /**
153
     * Validate a record
154
     *
155
     * @param string[] $record
156
     *
157
     * @throws InsertionException If the validation failed
158
     */
159 8
    protected function validateRecord(array $record)
160
    {
161 8
        foreach ($this->validators as $name => $validator) {
162 2
            if (true !== $validator($record)) {
163 2
                throw InsertionException::createFromValidator($name, $record);
164
            }
165
        }
166 6
    }
167
168
    /**
169
     * Apply post insertion actions
170
     *
171
     * @return int
172
     */
173 8
    protected function consolidate(): int
174
    {
175 8
        $bytes = 0;
176 8
        if ("\n" !== $this->newline) {
177 2
            $this->document->fseek(-1, SEEK_CUR);
178 2
            $bytes = $this->document->fwrite($this->newline, strlen($this->newline)) - 1;
179
        }
180
181 8
        if (null === $this->flush_threshold) {
182 6
            return $bytes;
183
        }
184
185 2
        ++$this->flush_counter;
186 2
        if (0 === $this->flush_counter % $this->flush_threshold) {
187 2
            $this->flush_counter = 0;
188 2
            $this->document->fflush();
189
        }
190
191 2
        return $bytes;
192
    }
193
194
    /**
195
     * add a formatter to the collection
196
     *
197
     * @param callable $formatter
198
     *
199
     * @return static
200
     */
201 2
    public function addFormatter(callable $formatter): self
202
    {
203 2
        $this->formatters[] = $formatter;
204
205 2
        return $this;
206
    }
207
208
    /**
209
     * add a Validator to the collection
210
     *
211
     * @param callable $validator
212
     * @param string   $validator_name the validator name
213
     *
214
     * @return static
215
     */
216 2
    public function addValidator(callable $validator, string $validator_name): self
217
    {
218 2
        $this->validators[$validator_name] = $validator;
219
220 2
        return $this;
221
    }
222
223
    /**
224
     * Sets the newline sequence characters
225
     *
226
     * @param string $newline
227
     *
228
     * @return static
229
     */
230 2
    public function setNewline(string $newline): self
231
    {
232 2
        $this->newline = $newline;
233
234 2
        return $this;
235
    }
236
237
    /**
238
     * Set the automatic flush threshold on write
239
     *
240
     * @param int|null $threshold
241
     *
242
     * @throws OutOfRangeException if the $threshold is not valid
243
     *
244
     * @return static
245
     */
246 4
    public function setFlushThreshold($threshold): self
247
    {
248 4
        if (null !== $threshold && $threshold < 1) {
249 2
            throw new OutOfRangeException(sprintf('%() expects 1 Argument to be null or a valid integer greater or equal to 1, %s given', __METHOD__, $threshold));
250
        }
251
252 4
        if ($threshold === $this->flush_threshold) {
253 2
            return $this;
254
        }
255
256 4
        $this->flush_threshold = $threshold;
257 4
        if (0 < $this->flush_counter) {
258 2
            $this->flush_counter = 0;
259 2
            $this->document->fflush();
260
        }
261
262 4
        return $this;
263
    }
264
}
265