1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Box\Spout\Writer; |
4
|
|
|
|
5
|
|
|
use Box\Spout\Common\Exception\IOException; |
6
|
|
|
use Box\Spout\Common\Exception\InvalidArgumentException; |
7
|
|
|
use Box\Spout\Writer\Exception\WriterAlreadyOpenedException; |
8
|
|
|
use Box\Spout\Writer\Exception\WriterNotOpenedException; |
9
|
|
|
use Box\Spout\Writer\Style\StyleBuilder; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Class AbstractWriter |
13
|
|
|
* |
14
|
|
|
* @package Box\Spout\Writer |
15
|
|
|
* @abstract |
16
|
|
|
*/ |
17
|
|
|
abstract class AbstractWriter implements WriterInterface |
18
|
|
|
{ |
19
|
|
|
/** @var string Path to the output file */ |
20
|
|
|
protected $outputFilePath; |
21
|
|
|
|
22
|
|
|
/** @var resource Pointer to the file/stream we will write to */ |
23
|
|
|
protected $filePointer; |
24
|
|
|
|
25
|
|
|
/** @var bool Indicates whether the writer has been opened or not */ |
26
|
|
|
protected $isWriterOpened = false; |
27
|
|
|
|
28
|
|
|
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */ |
29
|
|
|
protected $globalFunctionsHelper; |
30
|
|
|
|
31
|
|
|
/** @var Style\Style Style to be applied to the next written row(s) */ |
32
|
|
|
protected $rowStyle; |
33
|
|
|
|
34
|
|
|
/** @var Style\Style Default row style. Each writer can have its own default style */ |
35
|
|
|
protected $defaultRowStyle; |
36
|
|
|
|
37
|
|
|
/** @var string Content-Type value for the header - to be defined by child class */ |
38
|
|
|
protected static $headerContentType; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Opens the streamer and makes it ready to accept data. |
42
|
|
|
* |
43
|
|
|
* @return void |
44
|
|
|
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened |
45
|
|
|
*/ |
46
|
|
|
abstract protected function openWriter(); |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Adds data to the currently openned writer. |
50
|
|
|
* |
51
|
|
|
* @param array $dataRow Array containing data to be streamed. |
52
|
|
|
* Example $dataRow = ['data1', 1234, null, '', 'data5']; |
53
|
|
|
* @param Style\Style $style Style to be applied to the written row |
54
|
|
|
* @return void |
55
|
|
|
*/ |
56
|
|
|
abstract protected function addRowToWriter(array $dataRow, $style); |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Closes the streamer, preventing any additional writing. |
60
|
|
|
* |
61
|
|
|
* @return void |
62
|
|
|
*/ |
63
|
|
|
abstract protected function closeWriter(); |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* |
67
|
|
|
*/ |
68
|
282 |
|
public function __construct() |
69
|
|
|
{ |
70
|
282 |
|
$this->defaultRowStyle = $this->getDefaultRowStyle(); |
71
|
282 |
|
$this->resetRowStyleToDefault(); |
72
|
282 |
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Sets the default styles for all rows added with "addRow". |
76
|
|
|
* Overriding the default style instead of using "addRowWithStyle" improves performance by 20%. |
77
|
|
|
* @see https://github.com/box/spout/issues/272 |
78
|
|
|
* |
79
|
|
|
* @param Style\Style $defaultStyle |
80
|
|
|
* @return AbstractWriter |
81
|
|
|
*/ |
82
|
|
|
public function setDefaultRowStyle($defaultStyle) |
83
|
|
|
{ |
84
|
|
|
$this->defaultRowStyle = $defaultStyle; |
85
|
|
|
return $this; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper |
90
|
|
|
* @return AbstractWriter |
91
|
|
|
*/ |
92
|
282 |
|
public function setGlobalFunctionsHelper($globalFunctionsHelper) |
93
|
|
|
{ |
94
|
282 |
|
$this->globalFunctionsHelper = $globalFunctionsHelper; |
95
|
282 |
|
return $this; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Inits the writer and opens it to accept data. |
100
|
|
|
* By using this method, the data will be written to a file. |
101
|
|
|
* |
102
|
|
|
* @api |
103
|
|
|
* @param string $outputFilePath Path of the output file that will contain the data |
104
|
|
|
* @return AbstractWriter |
105
|
|
|
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened or if the given path is not writable |
106
|
|
|
*/ |
107
|
249 |
|
public function openToFile($outputFilePath) |
108
|
|
|
{ |
109
|
249 |
|
$this->outputFilePath = $outputFilePath; |
110
|
|
|
|
111
|
249 |
|
$this->filePointer = $this->globalFunctionsHelper->fopen($this->outputFilePath, 'wb+'); |
|
|
|
|
112
|
249 |
|
$this->throwIfFilePointerIsNotAvailable(); |
113
|
|
|
|
114
|
240 |
|
$this->openWriter(); |
115
|
240 |
|
$this->isWriterOpened = true; |
116
|
|
|
|
117
|
240 |
|
return $this; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Inits the writer and opens it to accept data. |
122
|
|
|
* By using this method, the data will be outputted directly to the browser. |
123
|
|
|
* |
124
|
|
|
* @codeCoverageIgnore |
125
|
|
|
* |
126
|
|
|
* @api |
127
|
|
|
* @param string $outputFileName Name of the output file that will contain the data. If a path is passed in, only the file name will be kept |
128
|
|
|
* @return AbstractWriter |
129
|
|
|
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened |
130
|
|
|
*/ |
131
|
|
|
public function openToBrowser($outputFileName) |
132
|
|
|
{ |
133
|
|
|
$this->outputFilePath = $this->globalFunctionsHelper->basename($outputFileName); |
134
|
|
|
|
135
|
|
|
$this->filePointer = $this->globalFunctionsHelper->fopen('php://output', 'w'); |
|
|
|
|
136
|
|
|
$this->throwIfFilePointerIsNotAvailable(); |
137
|
|
|
|
138
|
|
|
// Clear any previous output (otherwise the generated file will be corrupted) |
139
|
|
|
// @see https://github.com/box/spout/issues/241 |
140
|
|
|
$this->globalFunctionsHelper->ob_end_clean(); |
141
|
|
|
|
142
|
|
|
// Set headers |
143
|
|
|
$this->globalFunctionsHelper->header('Content-Type: ' . static::$headerContentType); |
144
|
|
|
$this->globalFunctionsHelper->header('Content-Disposition: attachment; filename="' . $this->outputFilePath . '"'); |
145
|
|
|
|
146
|
|
|
/* |
147
|
|
|
* When forcing the download of a file over SSL,IE8 and lower browsers fail |
148
|
|
|
* if the Cache-Control and Pragma headers are not set. |
149
|
|
|
* |
150
|
|
|
* @see http://support.microsoft.com/KB/323308 |
151
|
|
|
* @see https://github.com/liuggio/ExcelBundle/issues/45 |
152
|
|
|
*/ |
153
|
|
|
$this->globalFunctionsHelper->header('Cache-Control: max-age=0'); |
154
|
|
|
$this->globalFunctionsHelper->header('Pragma: public'); |
155
|
|
|
|
156
|
|
|
$this->openWriter(); |
157
|
|
|
$this->isWriterOpened = true; |
158
|
|
|
|
159
|
|
|
return $this; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Checks if the pointer to the file/stream to write to is available. |
164
|
|
|
* Will throw an exception if not available. |
165
|
|
|
* |
166
|
|
|
* @return void |
167
|
|
|
* @throws \Box\Spout\Common\Exception\IOException If the pointer is not available |
168
|
|
|
*/ |
169
|
249 |
|
protected function throwIfFilePointerIsNotAvailable() |
170
|
|
|
{ |
171
|
249 |
|
if (!$this->filePointer) { |
172
|
9 |
|
throw new IOException('File pointer has not be opened'); |
173
|
|
|
} |
174
|
240 |
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Checks if the writer has already been opened, since some actions must be done before it gets opened. |
178
|
|
|
* Throws an exception if already opened. |
179
|
|
|
* |
180
|
|
|
* @param string $message Error message |
181
|
|
|
* @return void |
182
|
|
|
* @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened and must not be. |
183
|
|
|
*/ |
184
|
120 |
|
protected function throwIfWriterAlreadyOpened($message) |
185
|
|
|
{ |
186
|
120 |
|
if ($this->isWriterOpened) { |
187
|
15 |
|
throw new WriterAlreadyOpenedException($message); |
188
|
|
|
} |
189
|
105 |
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Write given data to the output. New data will be appended to end of stream. |
193
|
|
|
* |
194
|
|
|
* @param array $dataRow Array containing data to be streamed. |
195
|
|
|
* If empty, no data is added (i.e. not even as a blank row) |
196
|
|
|
* Example: $dataRow = ['data1', 1234, null, '', 'data5', false]; |
197
|
|
|
* @api |
198
|
|
|
* @return AbstractWriter |
199
|
|
|
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer |
200
|
|
|
* @throws \Box\Spout\Common\Exception\IOException If unable to write data |
201
|
|
|
*/ |
202
|
201 |
|
public function addRow(array $dataRow) |
203
|
|
|
{ |
204
|
201 |
|
if ($this->isWriterOpened) { |
205
|
|
|
// empty $dataRow should not add an empty line |
206
|
171 |
|
if (!empty($dataRow)) { |
207
|
171 |
|
$this->addRowToWriter($dataRow, $this->rowStyle); |
208
|
165 |
|
} |
209
|
165 |
|
} else { |
210
|
30 |
|
throw new WriterNotOpenedException('The writer needs to be opened before adding row.'); |
211
|
|
|
} |
212
|
|
|
|
213
|
165 |
|
return $this; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Write given data to the output and apply the given style. |
218
|
|
|
* @see addRow |
219
|
|
|
* |
220
|
|
|
* @api |
221
|
|
|
* @param array $dataRow Array of array containing data to be streamed. |
222
|
|
|
* @param Style\Style $style Style to be applied to the row. |
223
|
|
|
* @return AbstractWriter |
224
|
|
|
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid |
225
|
|
|
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer |
226
|
|
|
* @throws \Box\Spout\Common\Exception\IOException If unable to write data |
227
|
|
|
*/ |
228
|
48 |
|
public function addRowWithStyle(array $dataRow, $style) |
229
|
|
|
{ |
230
|
48 |
|
if (!$style instanceof Style\Style) { |
231
|
18 |
|
throw new InvalidArgumentException('The "$style" argument must be a Style instance and cannot be NULL.'); |
232
|
|
|
} |
233
|
|
|
|
234
|
30 |
|
$this->setRowStyle($style); |
235
|
30 |
|
$this->addRow($dataRow); |
236
|
18 |
|
$this->resetRowStyleToDefault(); |
237
|
|
|
|
238
|
18 |
|
return $this; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* Write given data to the output. New data will be appended to end of stream. |
243
|
|
|
* |
244
|
|
|
* @api |
245
|
|
|
* @param array $dataRows Array of array containing data to be streamed. |
246
|
|
|
* If a row is empty, it won't be added (i.e. not even as a blank row) |
247
|
|
|
* Example: $dataRows = [ |
248
|
|
|
* ['data11', 12, , '', 'data13'], |
249
|
|
|
* ['data21', 'data22', null, false], |
250
|
|
|
* ]; |
251
|
|
|
* @return AbstractWriter |
252
|
|
|
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid |
253
|
|
|
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer |
254
|
|
|
* @throws \Box\Spout\Common\Exception\IOException If unable to write data |
255
|
|
|
*/ |
256
|
147 |
|
public function addRows(array $dataRows) |
257
|
|
|
{ |
258
|
147 |
|
if (!empty($dataRows)) { |
259
|
147 |
|
$firstRow = reset($dataRows); |
260
|
147 |
|
if (!is_array($firstRow)) { |
261
|
3 |
|
throw new InvalidArgumentException('The input should be an array of arrays'); |
262
|
|
|
} |
263
|
|
|
|
264
|
144 |
|
foreach ($dataRows as $dataRow) { |
265
|
144 |
|
$this->addRow($dataRow); |
266
|
129 |
|
} |
267
|
129 |
|
} |
268
|
|
|
|
269
|
129 |
|
return $this; |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* Write given data to the output and apply the given style. |
274
|
|
|
* @see addRows |
275
|
|
|
* |
276
|
|
|
* @api |
277
|
|
|
* @param array $dataRows Array of array containing data to be streamed. |
278
|
|
|
* @param Style\Style $style Style to be applied to the rows. |
279
|
|
|
* @return AbstractWriter |
280
|
|
|
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid |
281
|
|
|
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer |
282
|
|
|
* @throws \Box\Spout\Common\Exception\IOException If unable to write data |
283
|
|
|
*/ |
284
|
48 |
|
public function addRowsWithStyle(array $dataRows, $style) |
285
|
|
|
{ |
286
|
48 |
|
if (!$style instanceof Style\Style) { |
287
|
18 |
|
throw new InvalidArgumentException('The "$style" argument must be a Style instance and cannot be NULL.'); |
288
|
|
|
} |
289
|
|
|
|
290
|
30 |
|
$this->setRowStyle($style); |
291
|
30 |
|
$this->addRows($dataRows); |
292
|
30 |
|
$this->resetRowStyleToDefault(); |
293
|
|
|
|
294
|
30 |
|
return $this; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Returns the default style to be applied to rows. |
299
|
|
|
* Can be overriden by children to have a custom style. |
300
|
|
|
* |
301
|
|
|
* @return Style\Style |
302
|
|
|
*/ |
303
|
159 |
|
protected function getDefaultRowStyle() |
304
|
|
|
{ |
305
|
159 |
|
return (new StyleBuilder())->build(); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* Sets the style to be applied to the next written rows |
310
|
|
|
* until it is changed or reset. |
311
|
|
|
* |
312
|
|
|
* @param Style\Style $style |
313
|
|
|
* @return void |
314
|
|
|
*/ |
315
|
60 |
|
private function setRowStyle($style) |
316
|
|
|
{ |
317
|
|
|
// Merge given style with the default one to inherit custom properties |
318
|
60 |
|
$this->rowStyle = $style->mergeWith($this->defaultRowStyle); |
319
|
60 |
|
} |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* Resets the style to be applied to the next written rows. |
323
|
|
|
* |
324
|
|
|
* @return void |
325
|
|
|
*/ |
326
|
282 |
|
private function resetRowStyleToDefault() |
327
|
|
|
{ |
328
|
282 |
|
$this->rowStyle = $this->defaultRowStyle; |
329
|
282 |
|
} |
330
|
|
|
|
331
|
|
|
/** |
332
|
|
|
* Closes the writer. This will close the streamer as well, preventing new data |
333
|
|
|
* to be written to the file. |
334
|
|
|
* |
335
|
|
|
* @api |
336
|
|
|
* @return void |
337
|
|
|
*/ |
338
|
177 |
|
public function close() |
339
|
|
|
{ |
340
|
177 |
|
$this->closeWriter(); |
341
|
|
|
|
342
|
177 |
|
if (is_resource($this->filePointer)) { |
343
|
177 |
|
$this->globalFunctionsHelper->fclose($this->filePointer); |
344
|
177 |
|
} |
345
|
|
|
|
346
|
177 |
|
$this->isWriterOpened = false; |
347
|
177 |
|
} |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.