Completed
Push — master ( aa23c0...364b30 )
by ignace nyamagana
05:47 queued 02:02
created

Writer::addValidator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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