1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace CSanquer\ColibriCsv; |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Csv Writer |
7
|
|
|
* |
8
|
|
|
* @author Charles SANQUER - <[email protected]> |
9
|
|
|
*/ |
10
|
|
|
class CsvWriter extends AbstractCsv |
11
|
|
|
{ |
12
|
|
|
/** |
13
|
|
|
* |
14
|
|
|
* Default Excel Writing configuration |
15
|
|
|
* |
16
|
|
|
* available options : |
17
|
|
|
* - delimiter : (default = ';') |
18
|
|
|
* - enclosure : (default = '"') |
19
|
|
|
* - encoding : (default = 'CP1252') |
20
|
|
|
* - eol : (default = "\r\n") |
21
|
|
|
* - escape : (default = "\\") |
22
|
|
|
* - first_row_header : (default = false) use the PHP keys as CSV headers and write the first row with them |
23
|
|
|
* - bom : (default = false) add UTF8 BOM marker |
24
|
|
|
* - translit : (default = 'translit') iconv translit option possible values : 'translit', 'ignore', null |
25
|
|
|
* - trim : (default = false) trim each values on each line |
26
|
|
|
* |
27
|
|
|
* N.B. : Be careful, the option 'trim' decrease significantly the performances |
28
|
|
|
* |
29
|
|
|
* @param array $options Dialect Options to describe CSV file parameters |
30
|
|
|
*/ |
31
|
19 |
|
public function __construct($options = []) |
32
|
|
|
{ |
33
|
19 |
|
parent::__construct($options); |
34
|
19 |
|
$this->mode = self::MODE_WRITING; |
35
|
19 |
|
$this->fileHandlerMode = 'wb'; |
36
|
19 |
|
} |
37
|
|
|
|
38
|
2 |
|
protected function getCompatibleFileHanderModes() |
39
|
|
|
{ |
40
|
2 |
|
return ['wb', 'r+b', 'w+b', 'a+b', 'x+b', 'c+b']; |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* open a csv file to write |
45
|
|
|
* |
46
|
|
|
* @param string|resource $file filename or stream resource, default = null |
47
|
|
|
* @return AbstractCsv |
48
|
|
|
*/ |
49
|
12 |
|
public function open($file = null) |
50
|
|
|
{ |
51
|
12 |
|
parent::open($file); |
52
|
12 |
|
$this->writeBom(); |
53
|
|
|
|
54
|
12 |
|
return $this; |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* get HTTP headers for streaming CSV file |
59
|
|
|
* |
60
|
|
|
* @param string $filename |
61
|
|
|
* @return array |
62
|
|
|
*/ |
63
|
1 |
|
public function getHttpHeaders($filename) |
64
|
|
|
{ |
65
|
|
|
return [ |
66
|
1 |
|
'Content-Type' => 'application/csv', |
67
|
1 |
|
'Content-Disposition' => 'attachment;filename="'.$filename.'"', |
68
|
1 |
|
]; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* echo HTTP headers for streaming CSV file |
73
|
|
|
* |
74
|
|
|
* @param string $filename |
75
|
|
|
* |
76
|
|
|
* @codeCoverageIgnore this cannot be tested correctly by PHPUnit because it send HTTP headers |
77
|
|
|
*/ |
78
|
|
|
public function createHttpHeaders($filename) |
79
|
|
|
{ |
80
|
|
|
$headers = $this->getHttpHeaders($filename); |
81
|
|
|
foreach ($headers as $key => $value) { |
82
|
|
|
header($key.': '.$value); |
83
|
|
|
} |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* |
88
|
|
|
* @param resource|null $fileHandler |
89
|
|
|
* @param array $values |
90
|
|
|
* |
91
|
|
|
* @return CsvWriter |
92
|
|
|
* |
93
|
|
|
* @throws \InvalidArgumentException |
94
|
|
|
*/ |
95
|
13 |
|
protected function write($fileHandler, $values) |
96
|
|
|
{ |
97
|
13 |
|
if ($this->isFileOpened()) { |
98
|
13 |
|
$delimiter = $this->dialect->getDelimiter(); |
99
|
13 |
|
$enclosure = $this->dialect->getEnclosure(); |
100
|
13 |
|
$eol = $this->dialect->getLineEndings(); |
101
|
13 |
|
$escape = $this->dialect->getEscape(); |
102
|
13 |
|
$trim = $this->dialect->getTrim(); |
103
|
13 |
|
$enclosingMode = $this->dialect->getEnclosingMode(); |
104
|
13 |
|
$escapeDouble = $this->dialect->getEscapeDouble(); |
105
|
13 |
|
$line = implode($this->dialect->getDelimiter(), array_map( |
106
|
13 |
|
function ($var) use ($delimiter, $enclosure, $eol, $escape, $trim, $enclosingMode, $escapeDouble) { |
107
|
|
|
// Escape enclosures and enclosed string |
108
|
13 |
|
if ($escapeDouble) { |
109
|
|
|
// double enclosure |
110
|
11 |
|
$searches = [$enclosure]; |
111
|
11 |
|
$replacements = [$enclosure.$enclosure]; |
112
|
11 |
|
} else { |
113
|
|
|
// use escape character |
114
|
2 |
|
$searches = [$enclosure]; |
115
|
2 |
|
$replacements = [$escape.$enclosure]; |
116
|
|
|
} |
117
|
13 |
|
$clean = str_replace($searches, $replacements, $trim ? trim($var) : $var); |
118
|
|
|
|
119
|
13 |
|
if ($enclosingMode === Dialect::ENCLOSING_ALL || |
120
|
|
|
( |
121
|
12 |
|
$enclosingMode === Dialect::ENCLOSING_MINIMAL && |
122
|
11 |
|
preg_match('/['.preg_quote($enclosure.$delimiter.$eol, '/').']+/', $clean) |
123
|
12 |
|
) || |
124
|
12 |
|
($enclosingMode === Dialect::ENCLOSING_NONNUMERIC && preg_match('/[^\d\.]+/', $clean)) |
125
|
13 |
|
) { |
126
|
10 |
|
$var = $enclosure.$clean.$enclosure; |
127
|
10 |
|
} else { |
128
|
12 |
|
$var = $clean; |
129
|
|
|
} |
130
|
|
|
|
131
|
13 |
|
return $var; |
132
|
13 |
|
}, |
133
|
|
|
$values |
134
|
13 |
|
)) |
135
|
|
|
// Add line ending |
136
|
13 |
|
.$this->dialect->getLineEndings(); |
137
|
|
|
|
138
|
|
|
// Write to file |
139
|
13 |
|
fwrite($fileHandler, $this->convertEncoding($line, 'UTF-8', $this->dialect->getEncoding())); |
140
|
13 |
|
} |
141
|
|
|
|
142
|
13 |
|
return $this; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* write a CSV row from a PHP array |
147
|
|
|
* |
148
|
|
|
* @param array $values |
149
|
|
|
* |
150
|
|
|
* @return CsvWriter |
151
|
|
|
*/ |
152
|
14 |
|
public function writeRow(array $values) |
153
|
|
|
{ |
154
|
14 |
|
$this->openFile($this->fileHandlerMode); |
155
|
|
|
|
156
|
13 |
|
if ($this->dialect->getFirstRowHeader() && empty($this->headers)) { |
157
|
2 |
|
$this->headers = array_keys($values); |
158
|
2 |
|
$this->write($this->getFileHandler(), $this->headers); |
159
|
2 |
|
} |
160
|
|
|
|
161
|
13 |
|
return $this->write($this->getFileHandler(), $values); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* write CSV rows from a PHP arrays |
166
|
|
|
* |
167
|
|
|
* @param array rows (multiple arrays of values) |
168
|
|
|
* |
169
|
|
|
* @return CsvWriter |
170
|
|
|
*/ |
171
|
6 |
|
public function writeRows(array $rows) |
172
|
|
|
{ |
173
|
6 |
|
foreach ($rows as $values) { |
174
|
6 |
|
$this->writeRow($values); |
175
|
6 |
|
} |
176
|
|
|
|
177
|
6 |
|
return $this; |
178
|
|
|
} |
179
|
|
|
} |
180
|
|
|
|