Completed
Pull Request — master (#327)
by Adrien
06:29 queued 03:48
created

AbstractWriter::addRow()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.9197
c 0
b 0
f 0
ccs 11
cts 11
cp 1
cc 4
eloc 11
nc 4
nop 1
crap 4
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\Common\Exception\SpoutException;
8
use Box\Spout\Common\Helper\FileSystemHelper;
9
use Box\Spout\Writer\Exception\WriterAlreadyOpenedException;
10
use Box\Spout\Writer\Exception\WriterNotOpenedException;
11
use Box\Spout\Writer\Style\StyleBuilder;
12
13
/**
14
 * Class AbstractWriter
15
 *
16
 * @package Box\Spout\Writer
17
 * @abstract
18
 */
19
abstract class AbstractWriter implements WriterInterface
20
{
21
    /** @var string Path to the output file */
22
    protected $outputFilePath;
23
24
    /** @var resource Pointer to the file/stream we will write to */
25
    protected $filePointer;
26
27
    /** @var bool Indicates whether the writer has been opened or not */
28
    protected $isWriterOpened = false;
29
30
    /** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
31
    protected $globalFunctionsHelper;
32
33
    /** @var Style\Style Style to be applied to the next written row(s) */
34
    protected $rowStyle;
35
36
    /** @var Style\Style Default row style. Each writer can have its own default style */
37
    protected $defaultRowStyle;
38
39
    /** @var string Content-Type value for the header - to be defined by child class */
40
    protected static $headerContentType;
41
42
    /**
43
     * Opens the streamer and makes it ready to accept data.
44
     *
45
     * @return void
46
     * @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened
47
     */
48
    abstract protected function openWriter();
49
50
    /**
51
     * Adds data to the currently openned writer.
52
     *
53
     * @param  array $dataRow Array containing data to be streamed.
54
     *          Example $dataRow = ['data1', 1234, null, '', 'data5'];
55
     * @param Style\Style $style Style to be applied to the written row
56
     * @return void
57
     */
58
    abstract protected function addRowToWriter(array $dataRow, $style);
59
60
    /**
61
     * Closes the streamer, preventing any additional writing.
62
     *
63
     * @return void
64
     */
65
    abstract protected function closeWriter();
66
67
    /**
68
     *
69
     */
70 303
    public function __construct()
71
    {
72 303
        $this->defaultRowStyle = $this->getDefaultRowStyle();
73 303
        $this->resetRowStyleToDefault();
74 303
    }
75
76
    /**
77
     * Sets the default styles for all rows added with "addRow".
78
     * Overriding the default style instead of using "addRowWithStyle" improves performance by 20%.
79
     * @see https://github.com/box/spout/issues/272
80
     *
81
     * @param Style\Style $defaultStyle
82
     * @return AbstractWriter
83
     */
84 6
    public function setDefaultRowStyle($defaultStyle)
85
    {
86 6
        $this->defaultRowStyle = $defaultStyle;
87 6
        $this->resetRowStyleToDefault();
88 6
        return $this;
89
    }
90
91
    /**
92
     * @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
93
     * @return AbstractWriter
94
     */
95 303
    public function setGlobalFunctionsHelper($globalFunctionsHelper)
96
    {
97 303
        $this->globalFunctionsHelper = $globalFunctionsHelper;
98 303
        return $this;
99
    }
100
101
    /**
102
     * Inits the writer and opens it to accept data.
103
     * By using this method, the data will be written to a file.
104
     *
105
     * @api
106
     * @param  string $outputFilePath Path of the output file that will contain the data
107
     * @return AbstractWriter
108
     * @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened or if the given path is not writable
109
     */
110 270
    public function openToFile($outputFilePath)
111
    {
112 270
        $this->outputFilePath = $outputFilePath;
113
114 270
        $this->filePointer = $this->globalFunctionsHelper->fopen($this->outputFilePath, 'wb+');
1 ignored issue
show
Documentation Bug introduced by
It seems like $this->globalFunctionsHe...>outputFilePath, 'wb+') can also be of type boolean. However, the property $filePointer is declared as type resource. Maybe add an additional type check?

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 the id property of an instance of the Account 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.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
115 270
        $this->throwIfFilePointerIsNotAvailable();
116
117 261
        $this->openWriter();
118 261
        $this->isWriterOpened = true;
119
120 261
        return $this;
121
    }
122
123
    /**
124
     * Inits the writer and opens it to accept data.
125
     * By using this method, the data will be outputted directly to the browser.
126
     *
127
     * @codeCoverageIgnore
128
     *
129
     * @api
130
     * @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
131
     * @return AbstractWriter
132
     * @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened
133
     */
134
    public function openToBrowser($outputFileName)
135
    {
136
        $this->outputFilePath = $this->globalFunctionsHelper->basename($outputFileName);
137
138
        $this->filePointer = $this->globalFunctionsHelper->fopen('php://output', 'w');
1 ignored issue
show
Documentation Bug introduced by
It seems like $this->globalFunctionsHe...en('php://output', 'w') can also be of type boolean. However, the property $filePointer is declared as type resource. Maybe add an additional type check?

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 the id property of an instance of the Account 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.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
139
        $this->throwIfFilePointerIsNotAvailable();
140
141
        // Clear any previous output (otherwise the generated file will be corrupted)
142
        // @see https://github.com/box/spout/issues/241
143
        $this->globalFunctionsHelper->ob_end_clean();
144
145
        // Set headers
146
        $this->globalFunctionsHelper->header('Content-Type: ' . static::$headerContentType);
147
        $this->globalFunctionsHelper->header('Content-Disposition: attachment; filename="' . $this->outputFilePath . '"');
148
149
        /*
150
         * When forcing the download of a file over SSL,IE8 and lower browsers fail
151
         * if the Cache-Control and Pragma headers are not set.
152
         *
153
         * @see http://support.microsoft.com/KB/323308
154
         * @see https://github.com/liuggio/ExcelBundle/issues/45
155
         */
156
        $this->globalFunctionsHelper->header('Cache-Control: max-age=0');
157
        $this->globalFunctionsHelper->header('Pragma: public');
158
159
        $this->openWriter();
160
        $this->isWriterOpened = true;
161
162
        return $this;
163
    }
164
165
    /**
166
     * Checks if the pointer to the file/stream to write to is available.
167
     * Will throw an exception if not available.
168
     *
169
     * @return void
170
     * @throws \Box\Spout\Common\Exception\IOException If the pointer is not available
171
     */
172 270
    protected function throwIfFilePointerIsNotAvailable()
173
    {
174 270
        if (!$this->filePointer) {
175 9
            throw new IOException('File pointer has not be opened');
176
        }
177 261
    }
178
179
    /**
180
     * Checks if the writer has already been opened, since some actions must be done before it gets opened.
181
     * Throws an exception if already opened.
182
     *
183
     * @param string $message Error message
184
     * @return void
185
     * @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened and must not be.
186
     */
187 138
    protected function throwIfWriterAlreadyOpened($message)
188
    {
189 138
        if ($this->isWriterOpened) {
190 15
            throw new WriterAlreadyOpenedException($message);
191
        }
192 123
    }
193
194
    /**
195
     * Write given data to the output. New data will be appended to end of stream.
196
     *
197
     * @param  array $dataRow Array containing data to be streamed.
198
     *                        If empty, no data is added (i.e. not even as a blank row)
199
     *                        Example: $dataRow = ['data1', 1234, null, '', 'data5', false];
200
     * @api
201
     * @return AbstractWriter
202
     * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
203
     * @throws \Box\Spout\Common\Exception\IOException If unable to write data
204
     * @throws \Box\Spout\Common\Exception\SpoutException If anything else goes wrong while writing data
205
     */
206 222
    public function addRow(array $dataRow)
207
    {
208 222
        if ($this->isWriterOpened) {
209
            // empty $dataRow should not add an empty line
210 192
            if (!empty($dataRow)) {
211
                try {
212 192
                    $this->addRowToWriter($dataRow, $this->rowStyle);
213 192
                } catch (SpoutException $e) {
214
                    // if an exception occurs while writing data,
215
                    // close the writer and remove all files created so far.
216 12
                    $this->closeAndAttemptToCleanupAllFiles();
217
218
                    // re-throw the exception to alert developers of the error
219 12
                    throw $e;
220
                }
221 186
            }
222 186
        } else {
223 30
            throw new WriterNotOpenedException('The writer needs to be opened before adding row.');
224
        }
225
226 186
        return $this;
227
    }
228
229
    /**
230
     * Write given data to the output and apply the given style.
231
     * @see addRow
232
     *
233
     * @api
234
     * @param array $dataRow Array of array containing data to be streamed.
235
     * @param Style\Style $style Style to be applied to the row.
236
     * @return AbstractWriter
237
     * @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
238
     * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
239
     * @throws \Box\Spout\Common\Exception\IOException If unable to write data
240
     */
241 57
    public function addRowWithStyle(array $dataRow, $style)
242
    {
243 57
        if (!$style instanceof Style\Style) {
244 18
            throw new InvalidArgumentException('The "$style" argument must be a Style instance and cannot be NULL.');
245
        }
246
247 39
        $this->setRowStyle($style);
248 39
        $this->addRow($dataRow);
249 27
        $this->resetRowStyleToDefault();
250
251 27
        return $this;
252
    }
253
254
    /**
255
     * Write given data to the output. New data will be appended to end of stream.
256
     *
257
     * @api
258
     * @param  array $dataRows Array of array containing data to be streamed.
259
     *                         If a row is empty, it won't be added (i.e. not even as a blank row)
260
     *                         Example: $dataRows = [
261
     *                             ['data11', 12, , '', 'data13'],
262
     *                             ['data21', 'data22', null, false],
263
     *                         ];
264
     * @return AbstractWriter
265
     * @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
266
     * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
267
     * @throws \Box\Spout\Common\Exception\IOException If unable to write data
268
     */
269 159
    public function addRows(array $dataRows)
270
    {
271 159
        if (!empty($dataRows)) {
272 159
            $firstRow = reset($dataRows);
273 159
            if (!is_array($firstRow)) {
274 3
                throw new InvalidArgumentException('The input should be an array of arrays');
275
            }
276
277 156
            foreach ($dataRows as $dataRow) {
278 156
                $this->addRow($dataRow);
279 141
            }
280 135
        }
281
282 135
        return $this;
283
    }
284
285
    /**
286
     * Write given data to the output and apply the given style.
287
     * @see addRows
288
     *
289
     * @api
290
     * @param array $dataRows Array of array containing data to be streamed.
291
     * @param Style\Style $style Style to be applied to the rows.
292
     * @return AbstractWriter
293
     * @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
294
     * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
295
     * @throws \Box\Spout\Common\Exception\IOException If unable to write data
296
     */
297 48
    public function addRowsWithStyle(array $dataRows, $style)
298
    {
299 48
        if (!$style instanceof Style\Style) {
300 18
            throw new InvalidArgumentException('The "$style" argument must be a Style instance and cannot be NULL.');
301
        }
302
303 30
        $this->setRowStyle($style);
304 30
        $this->addRows($dataRows);
305 30
        $this->resetRowStyleToDefault();
306
307 30
        return $this;
308
    }
309
310
    /**
311
     * Returns the default style to be applied to rows.
312
     * Can be overriden by children to have a custom style.
313
     *
314
     * @return Style\Style
315
     */
316 165
    protected function getDefaultRowStyle()
317
    {
318 165
        return (new StyleBuilder())->build();
319
    }
320
321
    /**
322
     * Sets the style to be applied to the next written rows
323
     * until it is changed or reset.
324
     *
325
     * @param Style\Style $style
326
     * @return void
327
     */
328 69
    private function setRowStyle($style)
329
    {
330
        // Merge given style with the default one to inherit custom properties
331 69
        $this->rowStyle = $style->mergeWith($this->defaultRowStyle);
332 69
    }
333
334
    /**
335
     * Resets the style to be applied to the next written rows.
336
     *
337
     * @return void
338
     */
339 303
    private function resetRowStyleToDefault()
340
    {
341 303
        $this->rowStyle = $this->defaultRowStyle;
342 303
    }
343
344
    /**
345
     * Closes the writer. This will close the streamer as well, preventing new data
346
     * to be written to the file.
347
     *
348
     * @api
349
     * @return void
350
     */
351 204
    public function close()
352
    {
353 204
        $this->closeWriter();
354
355 204
        if (is_resource($this->filePointer)) {
356 204
            $this->globalFunctionsHelper->fclose($this->filePointer);
357 204
        }
358
359 204
        $this->isWriterOpened = false;
360 204
    }
361
362
    /**
363
     * Closes the writer and attempts to cleanup all files that were
364
     * created during the writing process (temp files & final file).
365
     *
366
     * @return void
367
     */
368 12
    private function closeAndAttemptToCleanupAllFiles()
369
    {
370
        // close the writer, which should remove all temp files
371 12
        $this->close();
372
373
        // remove output file if it was created
374 12
        if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) {
375 12
            $outputFolder = $this->globalFunctionsHelper->dirname($this->outputFilePath);
376 12
            $fileSystemHelper = new FileSystemHelper($outputFolder);
377 12
            $fileSystemHelper->deleteFile($this->outputFilePath);
378 12
        }
379 12
    }
380
}
381
382