Completed
Push — master ( ecd0f8...3ad52b )
by ignace nyamagana
15:08
created

Writer::insertAll()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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