Failed Conditions
Pull Request — develop_3.0 (#434)
by Hura
04:53 queued 02:03
created

WriterAbstract::openToFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 7
cts 7
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 7
nc 1
nop 1
crap 1
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 107
    public function __construct(
58
        OptionsManagerInterface $optionsManager,
59
        StyleMerger $styleMerger,
60
        GlobalFunctionsHelper $globalFunctionsHelper,
61
        HelperFactory $helperFactory
62
    ) {
63 107
        $this->optionsManager = $optionsManager;
64 107
        $this->styleMerger = $styleMerger;
65 107
        $this->globalFunctionsHelper = $globalFunctionsHelper;
66 107
        $this->helperFactory = $helperFactory;
67 107
    }
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 96
    public function openToFile($outputFilePath)
109
    {
110 96
        $this->outputFilePath = $outputFilePath;
111
112 96
        $this->filePointer = $this->globalFunctionsHelper->fopen($this->outputFilePath, 'wb+');
113 96
        $this->throwIfFilePointerIsNotAvailable();
114
115 93
        $this->openWriter();
116 93
        $this->isWriterOpened = true;
117
118 93
        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 96
    protected function throwIfFilePointerIsNotAvailable()
163
    {
164 96
        if (!$this->filePointer) {
165 3
            throw new IOException('File pointer has not be opened');
166
        }
167 93
    }
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 52
    protected function throwIfWriterAlreadyOpened($message)
178
    {
179 52
        if ($this->isWriterOpened) {
180 5
            throw new WriterAlreadyOpenedException($message);
181
        }
182 47
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187 80
    public function addRow(Row $row)
188
    {
189 80
        if ($this->isWriterOpened) {
190 70
            if (!$row->isEmpty()) {
191
                try {
192 69
                    $this->applyDefaultRowStyle($row);
193 69
                    $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
                    // re-throw the exception to alert developers of the error
199 70
                    throw $e;
200
                }
201
            }
202
        } else {
203 10
            throw new WriterNotOpenedException('The writer needs to be opened before adding row.');
204
        }
205
206 67
        return $this;
207
    }
208
209
    /**
210
     * {@inheritdoc}
211
     */
212
    public function withRow(\Closure $callback)
213
    {
214
        return $this->addRow($callback(EntityFactory::createRow([])));
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     */
220 62
    public function addRows(array $dataRows)
221
    {
222 62
        foreach ($dataRows as $dataRow) {
223 62
            if (!$dataRow instanceof Row) {
224 3
                $this->closeAndAttemptToCleanupAllFiles();
225 3
                throw new InvalidArgumentException();
226
            }
227
228 60
            $this->addRow($dataRow);
229
        }
230
231 51
        return $this;
232
    }
233
234
    /**
235
     * @param array $dataRow
236
     * @param Style|null $style
237
     * @return Row
238
     */
239
    protected function createRowFromArray(array $dataRow, Style $style = null)
240
    {
241
        $row = EntityFactory::createRow(array_map(function ($value) {
242
            if ($value instanceof Cell) {
243
                return $value;
244
            }
245
246
            return new Cell($value);
247
        }, $dataRow));
248
249
        if ($style !== null) {
250
            $row->setStyle($style);
251
        }
252
253
        return $row;
254
    }
255
256
    /**
257
     * @TODO: Move this into styleMerger
258
     *
259
     * @param Row $row
260
     * @return $this
261
     */
262 69
    private function applyDefaultRowStyle(Row $row)
263
    {
264 69
        $defaultRowStyle = $this->optionsManager->getOption(Options::DEFAULT_ROW_STYLE);
265 69
        if ($defaultRowStyle === null) {
266 8
            return $this;
267
        }
268 61
        $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...
269 61
        $row->setStyle($mergedStyle);
270 61
    }
271
272
    /**
273
     * Closes the writer. This will close the streamer as well, preventing new data
274
     * to be written to the file.
275
     *
276
     * @api
277
     * @return void
278
     */
279 79
    public function close()
280
    {
281 79
        if (!$this->isWriterOpened) {
282 4
            return;
283
        }
284
285 78
        $this->closeWriter();
286
287 78
        if (is_resource($this->filePointer)) {
288 78
            $this->globalFunctionsHelper->fclose($this->filePointer);
289
        }
290
291 78
        $this->isWriterOpened = false;
292 78
    }
293
294
    /**
295
     * Closes the writer and attempts to cleanup all files that were
296
     * created during the writing process (temp files & final file).
297
     *
298
     * @return void
299
     */
300 6
    private function closeAndAttemptToCleanupAllFiles()
301
    {
302
        // close the writer, which should remove all temp files
303 6
        $this->close();
304
305
        // remove output file if it was created
306 6
        if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) {
307 5
            $outputFolderPath = dirname($this->outputFilePath);
308 5
            $fileSystemHelper = $this->helperFactory->createFileSystemHelper($outputFolderPath);
309 5
            $fileSystemHelper->deleteFile($this->outputFilePath);
310
        }
311 6
    }
312
}
313