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

WriterAbstract::applyDefaultRowStyle()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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