Failed Conditions
Pull Request — develop_3.0 (#434)
by Adrien
03:06
created

WriterAbstract::withRow()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
3
namespace Box\Spout\Writer;
4
5
use Box\Spout\Common\Creator\HelperFactory;
6
use Box\Spout\Common\Exception\InvalidArgumentException;
7
use Box\Spout\Common\Exception\IOException;
8
use Box\Spout\Common\Exception\SpoutException;
9
use Box\Spout\Common\Helper\GlobalFunctionsHelper;
10
use Box\Spout\Common\Manager\OptionsManagerInterface;
11
use Box\Spout\Writer\Common\Creator\EntityFactory;
12
use Box\Spout\Writer\Common\Entity\Cell;
13
use Box\Spout\Writer\Common\Entity\Options;
14
use Box\Spout\Writer\Common\Entity\Row;
15
use Box\Spout\Writer\Common\Entity\Style\Style;
16
use Box\Spout\Writer\Common\Manager\Style\StyleMerger;
17
use Box\Spout\Writer\Exception\WriterAlreadyOpenedException;
18
use Box\Spout\Writer\Exception\WriterNotOpenedException;
19
20
/**
21
 * Class WriterAbstract
22
 *
23
 * @abstract
24
 */
25
abstract class WriterAbstract implements WriterInterface
26
{
27
    /** @var string Path to the output file */
28
    protected $outputFilePath;
29
30
    /** @var resource Pointer to the file/stream we will write to */
31
    protected $filePointer;
32
33
    /** @var bool Indicates whether the writer has been opened or not */
34
    protected $isWriterOpened = false;
35
36
    /** @var GlobalFunctionsHelper Helper to work with global functions */
37
    protected $globalFunctionsHelper;
38
39
    /** @var HelperFactory $helperFactory */
40
    protected $helperFactory;
41
42
    /** @var OptionsManagerInterface Writer options manager */
43
    protected $optionsManager;
44
45
    /** @var StyleMerger Helps merge styles together */
46
    protected $styleMerger;
47
48
    /** @var string Content-Type value for the header - to be defined by child class */
49
    protected static $headerContentType;
50
51
    /**
52
     * @param OptionsManagerInterface $optionsManager
53
     * @param StyleMerger $styleMerger
54
     * @param GlobalFunctionsHelper $globalFunctionsHelper
55
     * @param HelperFactory $helperFactory
56
     */
57 108
    public function __construct(
58
        OptionsManagerInterface $optionsManager,
59
        StyleMerger $styleMerger,
60
        GlobalFunctionsHelper $globalFunctionsHelper,
61
        HelperFactory $helperFactory
62
    ) {
63 108
        $this->optionsManager = $optionsManager;
64 108
        $this->styleMerger = $styleMerger;
65 108
        $this->globalFunctionsHelper = $globalFunctionsHelper;
66 108
        $this->helperFactory = $helperFactory;
67 108
    }
68
69
    /**
70
     * Opens the streamer and makes it ready to accept data.
71
     *
72
     * @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened
73
     * @return void
74
     */
75
    abstract protected function openWriter();
76
77
    /**
78
     * Adds a row to the currently opened writer.
79
     *
80
     * @param Row $row The row containing cells and styles
81
     * @return void
82
     */
83
    abstract protected function addRowToWriter(Row $row);
84
85
    /**
86
     * Closes the streamer, preventing any additional writing.
87
     *
88
     * @return void
89
     */
90
    abstract protected function closeWriter();
91
92
    /**
93
     * Sets the default styles for all rows added with "addRow"
94
     *
95
     * @param Style $defaultStyle
96
     * @return WriterAbstract
97
     */
98 2
    public function setDefaultRowStyle($defaultStyle)
99
    {
100 2
        $this->optionsManager->setOption(Options::DEFAULT_ROW_STYLE, $defaultStyle);
101
102 2
        return $this;
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108 97
    public function openToFile($outputFilePath)
109
    {
110 97
        $this->outputFilePath = $outputFilePath;
111
112 97
        $this->filePointer = $this->globalFunctionsHelper->fopen($this->outputFilePath, 'wb+');
113 97
        $this->throwIfFilePointerIsNotAvailable();
114
115 94
        $this->openWriter();
116 94
        $this->isWriterOpened = true;
117
118 94
        return $this;
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124
    public function openToBrowser($outputFileName)
125
    {
126
        $this->outputFilePath = $this->globalFunctionsHelper->basename($outputFileName);
127
128
        $this->filePointer = $this->globalFunctionsHelper->fopen('php://output', 'w');
129
        $this->throwIfFilePointerIsNotAvailable();
130
131
        // Clear any previous output (otherwise the generated file will be corrupted)
132
        // @see https://github.com/box/spout/issues/241
133
        $this->globalFunctionsHelper->ob_end_clean();
134
135
        // Set headers
136
        $this->globalFunctionsHelper->header('Content-Type: ' . static::$headerContentType);
137
        $this->globalFunctionsHelper->header('Content-Disposition: attachment; filename="' . $this->outputFilePath . '"');
138
139
        /*
140
         * When forcing the download of a file over SSL,IE8 and lower browsers fail
141
         * if the Cache-Control and Pragma headers are not set.
142
         *
143
         * @see http://support.microsoft.com/KB/323308
144
         * @see https://github.com/liuggio/ExcelBundle/issues/45
145
         */
146
        $this->globalFunctionsHelper->header('Cache-Control: max-age=0');
147
        $this->globalFunctionsHelper->header('Pragma: public');
148
149
        $this->openWriter();
150
        $this->isWriterOpened = true;
151
152
        return $this;
153
    }
154
155
    /**
156
     * Checks if the pointer to the file/stream to write to is available.
157
     * Will throw an exception if not available.
158
     *
159
     * @throws \Box\Spout\Common\Exception\IOException If the pointer is not available
160
     * @return void
161
     */
162 97
    protected function throwIfFilePointerIsNotAvailable()
163
    {
164 97
        if (!$this->filePointer) {
165 3
            throw new IOException('File pointer has not be opened');
166
        }
167 94
    }
168
169
    /**
170
     * Checks if the writer has already been opened, since some actions must be done before it gets opened.
171
     * Throws an exception if already opened.
172
     *
173
     * @param string $message Error message
174
     * @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened and must not be.
175
     * @return void
176
     */
177 53
    protected function throwIfWriterAlreadyOpened($message)
178
    {
179 53
        if ($this->isWriterOpened) {
180 5
            throw new WriterAlreadyOpenedException($message);
181
        }
182 48
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187 81
    public function addRow(Row $row)
188
    {
189 81
        if ($this->isWriterOpened) {
190 71
            if ($row->hasCells()) {
191
                try {
192 70
                    $this->applyDefaultRowStyle($row);
193 70
                    $this->addRowToWriter($row);
194 3
                } catch (SpoutException $e) {
195
                    // if an exception occurs while writing data,
196
                    // close the writer and remove all files created so far.
197 3
                    $this->closeAndAttemptToCleanupAllFiles();
198
199
                    // re-throw the exception to alert developers of the error
200 71
                    throw $e;
201
                }
202
            }
203
        } else {
204 10
            throw new WriterNotOpenedException('The writer needs to be opened before adding row.');
205
        }
206
207 68
        return $this;
208
    }
209
210
    /**
211
     * {@inheritdoc}
212
     */
213
    public function withRow(\Closure $callback)
214
    {
215
        return $this->addRow($callback(EntityFactory::createRow([])));
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221 63
    public function addRows(array $dataRows)
222
    {
223 63
        foreach ($dataRows as $dataRow) {
224 63
            if (!$dataRow instanceof Row) {
225 3
                $this->closeAndAttemptToCleanupAllFiles();
226 3
                throw new InvalidArgumentException();
227
            }
228
229 61
            $this->addRow($dataRow);
230
        }
231
232 52
        return $this;
233
    }
234
235
    /**
236
     * @param array $dataRow
237
     * @param Style|null $style
238
     * @return Row
239
     */
240
    protected function createRowFromArray(array $dataRow, Style $style = null)
241
    {
242
        $row = EntityFactory::createRow(array_map(function ($value) {
243
            if ($value instanceof Cell) {
244
                return $value;
245
            }
246
247
            return new Cell($value);
248
        }, $dataRow));
249
250
        if ($style !== null) {
251
            $row->setStyle($style);
252
        }
253
254
        return $row;
255
    }
256
257
    /**
258
     * @TODO: Move this into styleMerger
259
     *
260
     * @param Row $row
261
     * @return $this
262
     */
263 70
    private function applyDefaultRowStyle(Row $row)
264
    {
265 70
        $defaultRowStyle = $this->optionsManager->getOption(Options::DEFAULT_ROW_STYLE);
266 70
        if ($defaultRowStyle === null) {
267 8
            return $this;
268
        }
269
270 62
        $mergedStyle = $this->styleMerger->merge($row->getStyle(), $defaultRowStyle);
0 ignored issues
show
Bug introduced by
It seems like $row->getStyle() can be null; however, merge() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
271 62
        $row->setStyle($mergedStyle);
272 62
    }
273
274
    /**
275
     * Closes the writer. This will close the streamer as well, preventing new data
276
     * to be written to the file.
277
     *
278
     * @return void
279
     */
280 80
    public function close()
281
    {
282 80
        if (!$this->isWriterOpened) {
283 4
            return;
284
        }
285
286 79
        $this->closeWriter();
287
288 79
        if (is_resource($this->filePointer)) {
289 79
            $this->globalFunctionsHelper->fclose($this->filePointer);
290
        }
291
292 79
        $this->isWriterOpened = false;
293 79
    }
294
295
    /**
296
     * Closes the writer and attempts to cleanup all files that were
297
     * created during the writing process (temp files & final file).
298
     *
299
     * @return void
300
     */
301 6
    private function closeAndAttemptToCleanupAllFiles()
302
    {
303
        // close the writer, which should remove all temp files
304 6
        $this->close();
305
306
        // remove output file if it was created
307 6
        if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) {
308 5
            $outputFolderPath = dirname($this->outputFilePath);
309 5
            $fileSystemHelper = $this->helperFactory->createFileSystemHelper($outputFolderPath);
310 5
            $fileSystemHelper->deleteFile($this->outputFilePath);
311
        }
312 6
    }
313
}
314