Completed
Pull Request — develop_3.0 (#427)
by Adrien
02:28
created

WriterAbstract::openWriter()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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