Test Setup Failed
Pull Request — develop_3.0 (#434)
by Hura
04:56
created

WriterAbstract::addRow()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 5
cts 5
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\Entity\Cell;
11
use Box\Spout\Common\Manager\OptionsManagerInterface;
12
use Box\Spout\Writer\Common\Entity\Options;
13
use Box\Spout\Writer\Common\Entity\Row;
14
use Box\Spout\Writer\Common\Entity\Style\Style;
15
use Box\Spout\Writer\Common\Manager\Style\StyleMerger;
16
use Box\Spout\Writer\Exception\WriterAlreadyOpenedException;
17
use Box\Spout\Writer\Exception\WriterNotOpenedException;
18
19
/**
20
 * Class WriterAbstract
21
 *
22
 * @abstract
23
 */
24
abstract class WriterAbstract implements WriterInterface
25
{
26
    /** @var string Path to the output file */
27
    protected $outputFilePath;
28
29
    /** @var resource Pointer to the file/stream we will write to */
30
    protected $filePointer;
31
32
    /** @var bool Indicates whether the writer has been opened or not */
33
    protected $isWriterOpened = false;
34
35
    /** @var GlobalFunctionsHelper Helper to work with global functions */
36
    protected $globalFunctionsHelper;
37
38
    /** @var HelperFactory $helperFactory */
39
    protected $helperFactory;
40
41
    /** @var OptionsManagerInterface Writer options manager */
42
    protected $optionsManager;
43
44
    /** @var StyleMerger Helps merge styles together */
45
    protected $styleMerger;
46
47
    /** @var string Content-Type value for the header - to be defined by child class */
48
    protected static $headerContentType;
49
50
    /**
51
     * @param OptionsManagerInterface $optionsManager
52
     * @param StyleMerger $styleMerger
53
     * @param GlobalFunctionsHelper $globalFunctionsHelper
54
     * @param HelperFactory $helperFactory
55
     */
56
    public function __construct(
57 112
        OptionsManagerInterface $optionsManager,
58
        StyleMerger $styleMerger,
59
        GlobalFunctionsHelper $globalFunctionsHelper,
60
        HelperFactory $helperFactory
61
    ) {
62
        $this->optionsManager = $optionsManager;
63 112
        $this->styleMerger = $styleMerger;
64 112
        $this->globalFunctionsHelper = $globalFunctionsHelper;
65 112
        $this->helperFactory = $helperFactory;
66 112
67
        $this->resetRowStyleToDefault();
0 ignored issues
show
Bug introduced by
The method resetRowStyleToDefault() does not seem to exist on object<Box\Spout\Writer\WriterAbstract>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
68 112
    }
69 112
70
    /**
71
     * Opens the streamer and makes it ready to accept data.
72
     *
73
     * @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened
74
     * @return void
75
     */
76
    abstract protected function openWriter();
77
78
    /**
79
     * Adds a row to the currently opened writer.
80
     *
81
     * @param Row $row The row containing cells and styles
82
     * @return void
83
     */
84
    abstract protected function addRowToWriter(Row $row);
85
86
    /**
87
     * Closes the streamer, preventing any additional writing.
88
     *
89
     * @return void
90
     */
91
    abstract protected function closeWriter();
92
93
    /**
94
     * Sets the default styles for all rows added with "addRow"
95
     *
96
     * @param Style $defaultStyle
97
     * @return WriterAbstract
98
     */
99
    public function setDefaultRowStyle($defaultStyle)
100
    {
101
        $this->optionsManager->setOption(Options::DEFAULT_ROW_STYLE, $defaultStyle);
102
        return $this;
103
    }
104 2
105
    /**
106 2
     * @inheritdoc
107 2
     */
108
    public function openToFile($outputFilePath)
109 2
    {
110
        $this->outputFilePath = $outputFilePath;
111
112
        $this->filePointer = $this->globalFunctionsHelper->fopen($this->outputFilePath, 'wb+');
113
        $this->throwIfFilePointerIsNotAvailable();
114
115
        $this->openWriter();
116
        $this->isWriterOpened = true;
117
118
        return $this;
119
    }
120
121 101
    /**
122
     * @inheritdoc
123 101
     */
124
    public function openToBrowser($outputFileName)
125 101
    {
126 101
        $this->outputFilePath = $this->globalFunctionsHelper->basename($outputFileName);
127
128 98
        $this->filePointer = $this->globalFunctionsHelper->fopen('php://output', 'w');
129 98
        $this->throwIfFilePointerIsNotAvailable();
130
131 98
        // 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
    protected function throwIfFilePointerIsNotAvailable()
163
    {
164
        if (!$this->filePointer) {
165
            throw new IOException('File pointer has not be opened');
166
        }
167
    }
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
    protected function throwIfWriterAlreadyOpened($message)
178
    {
179
        if ($this->isWriterOpened) {
180
            throw new WriterAlreadyOpenedException($message);
181
        }
182
    }
183 101
184
    /**
185 101
     * @inheritdoc
186 3
     */
187
    public function addRow(Row $row)
188 98
    {
189
        if ($this->isWriterOpened) {
190
            if (!$row->isEmpty()) {
191
                try {
192
                    $this->applyDefaultRowStyle($row);
193
                    $this->addRowToWriter($row);
194
                } catch (SpoutException $e) {
195
                    // if an exception occurs while writing data,
196
                    // close the writer and remove all files created so far.
197
                    $this->closeAndAttemptToCleanupAllFiles();
198 53
                    // re-throw the exception to alert developers of the error
199
                    throw $e;
200 53
                }
201 5
            }
202
        } else {
203 48
            throw new WriterNotOpenedException('The writer needs to be opened before adding row.');
204
        }
205
        return $this;
206
    }
207
208
    /**
209
     * @inheritdoc
210
     */
211
    public function withRow(\Closure $callback)
212
    {
213
        return $this->addRow($callback(new Row()));
0 ignored issues
show
Bug introduced by
The call to Row::__construct() misses a required argument $rowManager.

This check looks for function calls that miss required arguments.

Loading history...
214
    }
215
216
    /**
217 82
     * @inheritdoc
218
     */
219 82
    public function addRows(array $dataRows)
220
    {
221 72
        foreach ($dataRows as $dataRow) {
222
223 72
            if(!$dataRow instanceof Row) {
224 5
                $this->closeAndAttemptToCleanupAllFiles();
225
                throw new InvalidArgumentException();
226
            }
227 5
228
            $this->addRow($dataRow);
229
        }
230 72
        return $this;
231
    }
232
233
    /**
234 10
     * @param array $dataRow
235
     * @param Style|null $style
236
     * @return Row
237 69
     */
238
    protected function createRowFromArray(array $dataRow, Style $style = null)
239
    {
240
        $row = (new Row())->setCells(array_map(function ($value) {
0 ignored issues
show
Bug introduced by
The call to Row::__construct() misses a required argument $rowManager.

This check looks for function calls that miss required arguments.

Loading history...
241
            if ($value instanceof Cell) {
242
                return $value;
243
            }
244
            return new Cell($value);
245
        }, $dataRow));
246
247
        if ($style !== null) {
248
            $row->setStyle($style);
249
        }
250
251
        return $row;
252 19
    }
253
254 19
    /**
255 6
     * @TODO: Move this into styleMerger
256
     *
257
     * @param Row $row
258 13
     * @return $this
259 13
     */
260 9
    private function applyDefaultRowStyle(Row $row)
261
    {
262 9
        $defaultRowStyle = $this->optionsManager->getOption(Options::DEFAULT_ROW_STYLE);
263
        if (null === $defaultRowStyle) {
264
            return $this;
265
        }
266
        $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...
267
        $row->setStyle($mergedStyle);
268
    }
269
270
    /**
271
     * Closes the writer. This will close the streamer as well, preventing new data
272
     * to be written to the file.
273
     *
274
     * @api
275
     * @return void
276
     */
277
    public function close()
278
    {
279
        if (!$this->isWriterOpened) {
280 61
            return;
281
        }
282 61
283 61
        $this->closeWriter();
284 61
285 1
        if (is_resource($this->filePointer)) {
286
            $this->globalFunctionsHelper->fclose($this->filePointer);
287
        }
288 60
289 60
        $this->isWriterOpened = false;
290
    }
291
292
    /**
293 52
     * Closes the writer and attempts to cleanup all files that were
294
     * created during the writing process (temp files & final file).
295
     *
296
     * @return void
297
     */
298
    private function closeAndAttemptToCleanupAllFiles()
299
    {
300
        // close the writer, which should remove all temp files
301
        $this->close();
302
303
        // remove output file if it was created
304
        if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) {
305
            $outputFolderPath = dirname($this->outputFilePath);
306
            $fileSystemHelper = $this->helperFactory->createFileSystemHelper($outputFolderPath);
307
            $fileSystemHelper->deleteFile($this->outputFilePath);
308 16
        }
309
    }
310
}
311