Completed
Pull Request — develop_3.0 (#436)
by Adrien
02:39
created

WriterAbstract::setDefaultRowStyle()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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