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

WriterAbstract::addRow()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 10
cts 10
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 12
nc 5
nop 1
crap 4
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\Writer\Common\Creator\EntityFactory;
11
use Box\Spout\Writer\Common\Entity\Cell;
12
use Box\Spout\Common\Manager\OptionsManagerInterface;
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 2
        return $this;
102
    }
103
104
    /**
105
     * @inheritdoc
106
     */
107 96
    public function openToFile($outputFilePath)
108
    {
109 96
        $this->outputFilePath = $outputFilePath;
110
111 96
        $this->filePointer = $this->globalFunctionsHelper->fopen($this->outputFilePath, 'wb+');
112 96
        $this->throwIfFilePointerIsNotAvailable();
113
114 93
        $this->openWriter();
115 93
        $this->isWriterOpened = true;
116
117 93
        return $this;
118
    }
119
120
    /**
121
     * @inheritdoc
122
     */
123
    public function openToBrowser($outputFileName)
124
    {
125
        $this->outputFilePath = $this->globalFunctionsHelper->basename($outputFileName);
126
127
        $this->filePointer = $this->globalFunctionsHelper->fopen('php://output', 'w');
128
        $this->throwIfFilePointerIsNotAvailable();
129
130
        // Clear any previous output (otherwise the generated file will be corrupted)
131
        // @see https://github.com/box/spout/issues/241
132
        $this->globalFunctionsHelper->ob_end_clean();
133
134
        // Set headers
135
        $this->globalFunctionsHelper->header('Content-Type: ' . static::$headerContentType);
136
        $this->globalFunctionsHelper->header('Content-Disposition: attachment; filename="' . $this->outputFilePath . '"');
137
138
        /*
139
         * When forcing the download of a file over SSL,IE8 and lower browsers fail
140
         * if the Cache-Control and Pragma headers are not set.
141
         *
142
         * @see http://support.microsoft.com/KB/323308
143
         * @see https://github.com/liuggio/ExcelBundle/issues/45
144
         */
145
        $this->globalFunctionsHelper->header('Cache-Control: max-age=0');
146
        $this->globalFunctionsHelper->header('Pragma: public');
147
148
        $this->openWriter();
149
        $this->isWriterOpened = true;
150
151
        return $this;
152
    }
153
154
    /**
155
     * Checks if the pointer to the file/stream to write to is available.
156
     * Will throw an exception if not available.
157
     *
158
     * @throws \Box\Spout\Common\Exception\IOException If the pointer is not available
159
     * @return void
160
     */
161 96
    protected function throwIfFilePointerIsNotAvailable()
162
    {
163 96
        if (!$this->filePointer) {
164 3
            throw new IOException('File pointer has not be opened');
165
        }
166 93
    }
167
168
    /**
169
     * Checks if the writer has already been opened, since some actions must be done before it gets opened.
170
     * Throws an exception if already opened.
171
     *
172
     * @param string $message Error message
173
     * @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened and must not be.
174
     * @return void
175
     */
176 52
    protected function throwIfWriterAlreadyOpened($message)
177
    {
178 52
        if ($this->isWriterOpened) {
179 5
            throw new WriterAlreadyOpenedException($message);
180
        }
181 47
    }
182
183
    /**
184
     * @inheritdoc
185
     */
186 80
    public function addRow(Row $row)
187
    {
188 80
        if ($this->isWriterOpened) {
189 70
            if (!$row->isEmpty()) {
190
                try {
191 69
                    $this->applyDefaultRowStyle($row);
192 69
                    $this->addRowToWriter($row);
193 3
                } catch (SpoutException $e) {
194
                    // if an exception occurs while writing data,
195
                    // close the writer and remove all files created so far.
196 3
                    $this->closeAndAttemptToCleanupAllFiles();
197
                    // re-throw the exception to alert developers of the error
198 70
                    throw $e;
199
                }
200
            }
201
        } else {
202 10
            throw new WriterNotOpenedException('The writer needs to be opened before adding row.');
203
        }
204 67
        return $this;
205
    }
206
207
    /**
208
     * @inheritdoc
209
     */
210
    public function withRow(\Closure $callback)
211
    {
212
        return $this->addRow($callback(EntityFactory::createRow([])));
213
    }
214
215
    /**
216
     * @inheritdoc
217
     */
218 62
    public function addRows(array $dataRows)
219
    {
220 62
        foreach ($dataRows as $dataRow) {
221
222 62
            if(!$dataRow instanceof Row) {
223 3
                $this->closeAndAttemptToCleanupAllFiles();
224 3
                throw new InvalidArgumentException();
225
            }
226
227 60
            $this->addRow($dataRow);
228
        }
229 51
        return $this;
230
    }
231
232
    /**
233
     * @param array $dataRow
234
     * @param Style|null $style
235
     * @return Row
236
     */
237
    protected function createRowFromArray(array $dataRow, Style $style = null)
238
    {
239
        $row = EntityFactory::createRow(array_map(function ($value) {
240
            if ($value instanceof Cell) {
241
                return $value;
242
            }
243
            return new Cell($value);
244
        }, $dataRow));
245
246
        if ($style !== null) {
247
            $row->setStyle($style);
248
        }
249
250
        return $row;
251
    }
252
253
    /**
254
     * @TODO: Move this into styleMerger
255
     *
256
     * @param Row $row
257
     * @return $this
258
     */
259 69
    private function applyDefaultRowStyle(Row $row)
260
    {
261 69
        $defaultRowStyle = $this->optionsManager->getOption(Options::DEFAULT_ROW_STYLE);
262 69
        if (null === $defaultRowStyle) {
263 8
            return $this;
264
        }
265 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...
266 61
        $row->setStyle($mergedStyle);
267 61
    }
268
269
    /**
270
     * Closes the writer. This will close the streamer as well, preventing new data
271
     * to be written to the file.
272
     *
273
     * @api
274
     * @return void
275
     */
276 79
    public function close()
277
    {
278 79
        if (!$this->isWriterOpened) {
279 4
            return;
280
        }
281
282 78
        $this->closeWriter();
283
284 78
        if (is_resource($this->filePointer)) {
285 78
            $this->globalFunctionsHelper->fclose($this->filePointer);
286
        }
287
288 78
        $this->isWriterOpened = false;
289 78
    }
290
291
    /**
292
     * Closes the writer and attempts to cleanup all files that were
293
     * created during the writing process (temp files & final file).
294
     *
295
     * @return void
296
     */
297 6
    private function closeAndAttemptToCleanupAllFiles()
298
    {
299
        // close the writer, which should remove all temp files
300 6
        $this->close();
301
302
        // remove output file if it was created
303 6
        if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) {
304 5
            $outputFolderPath = dirname($this->outputFilePath);
305 5
            $fileSystemHelper = $this->helperFactory->createFileSystemHelper($outputFolderPath);
306 5
            $fileSystemHelper->deleteFile($this->outputFilePath);
307
        }
308 6
    }
309
}
310