Passed
Pull Request — develop_3.0 (#434)
by Adrien
03:29
created

WriterAbstract::addRowWithStyle()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

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 2
eloc 7
nc 2
nop 2
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\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\Style\StyleMerger;
15
use Box\Spout\Writer\Exception\WriterAlreadyOpenedException;
16
use Box\Spout\Writer\Exception\WriterNotOpenedException;
17
18
/**
19
 * Class WriterAbstract
20
 *
21
 * @abstract
22
 */
23
abstract class WriterAbstract implements WriterInterface
24
{
25
    /** @var string Path to the output file */
26
    protected $outputFilePath;
27
28
    /** @var resource Pointer to the file/stream we will write to */
29
    protected $filePointer;
30
31
    /** @var bool Indicates whether the writer has been opened or not */
32
    protected $isWriterOpened = false;
33
34
    /** @var GlobalFunctionsHelper Helper to work with global functions */
35
    protected $globalFunctionsHelper;
36
37
    /** @var HelperFactory $helperFactory */
38
    protected $helperFactory;
39
40
    /** @var OptionsManagerInterface Writer options manager */
41
    protected $optionsManager;
42
43
    /** @var StyleMerger Helps merge styles together */
44
    protected $styleMerger;
45
46
    /** @var string Content-Type value for the header - to be defined by child class */
47
    protected static $headerContentType;
48
49
    /**
50
     * @param OptionsManagerInterface $optionsManager
51
     * @param StyleMerger $styleMerger
52
     * @param GlobalFunctionsHelper $globalFunctionsHelper
53
     * @param HelperFactory $helperFactory
54
     */
55 108
    public function __construct(
56
        OptionsManagerInterface $optionsManager,
57
        StyleMerger $styleMerger,
58
        GlobalFunctionsHelper $globalFunctionsHelper,
59
        HelperFactory $helperFactory
60
    ) {
61 108
        $this->optionsManager = $optionsManager;
62 108
        $this->styleMerger = $styleMerger;
63 108
        $this->globalFunctionsHelper = $globalFunctionsHelper;
64 108
        $this->helperFactory = $helperFactory;
65 108
    }
66
67
    /**
68
     * Opens the streamer and makes it ready to accept data.
69
     *
70
     * @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened
71
     * @return void
72
     */
73
    abstract protected function openWriter();
74
75
    /**
76
     * Adds a row to the currently opened writer.
77
     *
78
     * @param Row $row The row containing cells and styles
79
     * @return void
80
     */
81
    abstract protected function addRowToWriter(Row $row);
82
83
    /**
84
     * Closes the streamer, preventing any additional writing.
85
     *
86
     * @return void
87
     */
88
    abstract protected function closeWriter();
89
90
    /**
91
     * Sets the default styles for all rows added with "addRow"
92
     *
93
     * @param Style $defaultStyle
94
     * @return WriterAbstract
95
     */
96 2
    public function setDefaultRowStyle($defaultStyle)
97
    {
98 2
        $this->optionsManager->setOption(Options::DEFAULT_ROW_STYLE, $defaultStyle);
99
100 2
        return $this;
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106 97
    public function openToFile($outputFilePath)
107
    {
108 97
        $this->outputFilePath = $outputFilePath;
109
110 97
        $this->filePointer = $this->globalFunctionsHelper->fopen($this->outputFilePath, 'wb+');
111 97
        $this->throwIfFilePointerIsNotAvailable();
112
113 94
        $this->openWriter();
114 94
        $this->isWriterOpened = true;
115
116 94
        return $this;
117
    }
118
119
    /**
120
     * @codeCoverageIgnore
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 63
    public function addRows(array $dataRows)
214
    {
215 63
        foreach ($dataRows as $dataRow) {
216 63
            if (!$dataRow instanceof Row) {
217 3
                $this->closeAndAttemptToCleanupAllFiles();
218 3
                throw new InvalidArgumentException();
219
            }
220
221 61
            $this->addRow($dataRow);
222
        }
223
224 52
        return $this;
225
    }
226
227
    /**
228
     * @TODO: Move this into styleMerger
229
     *
230
     * @param Row $row
231
     * @return $this
232
     */
233 70
    private function applyDefaultRowStyle(Row $row)
234
    {
235 70
        $defaultRowStyle = $this->optionsManager->getOption(Options::DEFAULT_ROW_STYLE);
236 70
        if ($defaultRowStyle === null) {
237 8
            return $this;
238
        }
239
240 62
        $mergedStyle = $this->styleMerger->merge($row->getStyle(), $defaultRowStyle);
241 62
        $row->setStyle($mergedStyle);
242 62
    }
243
244
    /**
245
     * Closes the writer. This will close the streamer as well, preventing new data
246
     * to be written to the file.
247
     *
248
     * @return void
249
     */
250 80
    public function close()
251
    {
252 80
        if (!$this->isWriterOpened) {
253 4
            return;
254
        }
255
256 79
        $this->closeWriter();
257
258 79
        if (is_resource($this->filePointer)) {
259 79
            $this->globalFunctionsHelper->fclose($this->filePointer);
260
        }
261
262 79
        $this->isWriterOpened = false;
263 79
    }
264
265
    /**
266
     * Closes the writer and attempts to cleanup all files that were
267
     * created during the writing process (temp files & final file).
268
     *
269
     * @return void
270
     */
271 6
    private function closeAndAttemptToCleanupAllFiles()
272
    {
273
        // close the writer, which should remove all temp files
274 6
        $this->close();
275
276
        // remove output file if it was created
277 6
        if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) {
278 5
            $outputFolderPath = dirname($this->outputFilePath);
279 5
            $fileSystemHelper = $this->helperFactory->createFileSystemHelper($outputFolderPath);
280 5
            $fileSystemHelper->deleteFile($this->outputFilePath);
281
        }
282 6
    }
283
}
284