Passed
Push — develop_3.0 ( fec27e...727422 )
by Adrien
02:46
created

WriterAbstract::addRowsWithStyle()   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 94
    public function __construct(
56
        OptionsManagerInterface $optionsManager,
57
        StyleMerger $styleMerger,
58
        GlobalFunctionsHelper $globalFunctionsHelper,
59
        HelperFactory $helperFactory
60
    ) {
61 94
        $this->optionsManager = $optionsManager;
62 94
        $this->styleMerger = $styleMerger;
63 94
        $this->globalFunctionsHelper = $globalFunctionsHelper;
64 94
        $this->helperFactory = $helperFactory;
65 94
    }
66
67
    /**
68
     * Opens the streamer and makes it ready to accept data.
69
     *
70
     * @throws 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
     * @throws WriterNotOpenedException If the workbook is not created yet
80
     * @throws IOException If unable to write data
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
     * {@inheritdoc}
94
     */
95 2
    public function setDefaultRowStyle(Style $defaultStyle)
96
    {
97 2
        $this->optionsManager->setOption(Options::DEFAULT_ROW_STYLE, $defaultStyle);
98
99 2
        return $this;
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105 83
    public function openToFile($outputFilePath)
106
    {
107 83
        $this->outputFilePath = $outputFilePath;
108
109 83
        $this->filePointer = $this->globalFunctionsHelper->fopen($this->outputFilePath, 'wb+');
110 83
        $this->throwIfFilePointerIsNotAvailable();
111
112 80
        $this->openWriter();
113 80
        $this->isWriterOpened = true;
114
115 80
        return $this;
116
    }
117
118
    /**
119
     * @codeCoverageIgnore
120
     * {@inheritdoc}
121
     */
122
    public function openToBrowser($outputFileName)
123
    {
124
        $this->outputFilePath = $this->globalFunctionsHelper->basename($outputFileName);
125
126
        $this->filePointer = $this->globalFunctionsHelper->fopen('php://output', 'w');
127
        $this->throwIfFilePointerIsNotAvailable();
128
129
        // Clear any previous output (otherwise the generated file will be corrupted)
130
        // @see https://github.com/box/spout/issues/241
131
        $this->globalFunctionsHelper->ob_end_clean();
132
133
        // Set headers
134
        $this->globalFunctionsHelper->header('Content-Type: ' . static::$headerContentType);
135
        $this->globalFunctionsHelper->header('Content-Disposition: attachment; filename="' . $this->outputFilePath . '"');
136
137
        /*
138
         * When forcing the download of a file over SSL,IE8 and lower browsers fail
139
         * if the Cache-Control and Pragma headers are not set.
140
         *
141
         * @see http://support.microsoft.com/KB/323308
142
         * @see https://github.com/liuggio/ExcelBundle/issues/45
143
         */
144
        $this->globalFunctionsHelper->header('Cache-Control: max-age=0');
145
        $this->globalFunctionsHelper->header('Pragma: public');
146
147
        $this->openWriter();
148
        $this->isWriterOpened = true;
149
150
        return $this;
151
    }
152
153
    /**
154
     * Checks if the pointer to the file/stream to write to is available.
155
     * Will throw an exception if not available.
156
     *
157
     * @throws IOException If the pointer is not available
158
     * @return void
159
     */
160 83
    protected function throwIfFilePointerIsNotAvailable()
161
    {
162 83
        if (!$this->filePointer) {
163 3
            throw new IOException('File pointer has not be opened');
164
        }
165 80
    }
166
167
    /**
168
     * Checks if the writer has already been opened, since some actions must be done before it gets opened.
169
     * Throws an exception if already opened.
170
     *
171
     * @param string $message Error message
172
     * @throws WriterAlreadyOpenedException If the writer was already opened and must not be.
173
     * @return void
174
     */
175 49
    protected function throwIfWriterAlreadyOpened($message)
176
    {
177 49
        if ($this->isWriterOpened) {
178 5
            throw new WriterAlreadyOpenedException($message);
179
        }
180 44
    }
181
182
    /**
183
     * {@inheritdoc}
184
     */
185 75
    public function addRow(Row $row)
186
    {
187 75
        if ($this->isWriterOpened) {
188
            // empty $dataRow should not add an empty line
189 65
            if ($row->hasCells()) {
190
                try {
191 65
                    $this->applyDefaultRowStyle($row);
192 65
                    $this->addRowToWriter($row);
193 4
                } catch (SpoutException $e) {
194
                    // if an exception occurs while writing data,
195
                    // close the writer and remove all files created so far.
196 4
                    $this->closeAndAttemptToCleanupAllFiles();
197
198
                    // re-throw the exception to alert developers of the error
199 65
                    throw $e;
200
                }
201
            }
202
        } else {
203 10
            throw new WriterNotOpenedException('The writer needs to be opened before adding row.');
204
        }
205
206 63
        return $this;
207
    }
208
209
    /**
210
     * {@inheritdoc}
211
     */
212 63
    public function addRows(array $rows)
213
    {
214 63
        foreach ($rows as $row) {
215 63
            if (!$row instanceof Row) {
216 2
                $this->closeAndAttemptToCleanupAllFiles();
217 2
                throw new InvalidArgumentException('The input should be an array of Row');
218
            }
219
220 61
            $this->addRow($row);
221
        }
222
223 55
        return $this;
224
    }
225
226
    /**
227
     * @TODO: Move this into styleMerger
228
     *
229
     * @param Row $row
230
     * @return $this
231
     */
232 65
    private function applyDefaultRowStyle(Row $row)
233
    {
234 65
        $defaultRowStyle = $this->optionsManager->getOption(Options::DEFAULT_ROW_STYLE);
235 65
        if ($defaultRowStyle === null) {
236 6
            return $this;
237
        }
238
239 59
        $mergedStyle = $this->styleMerger->merge($row->getStyle(), $defaultRowStyle);
240 59
        $row->setStyle($mergedStyle);
241 59
    }
242
243
    /**
244
     * {@inheritdoc}
245
     */
246 74
    public function close()
247
    {
248 74
        if (!$this->isWriterOpened) {
249 4
            return;
250
        }
251
252 73
        $this->closeWriter();
253
254 73
        if (is_resource($this->filePointer)) {
255 73
            $this->globalFunctionsHelper->fclose($this->filePointer);
256
        }
257
258 73
        $this->isWriterOpened = false;
259 73
    }
260
261
    /**
262
     * Closes the writer and attempts to cleanup all files that were
263
     * created during the writing process (temp files & final file).
264
     *
265
     * @return void
266
     */
267 6
    private function closeAndAttemptToCleanupAllFiles()
268
    {
269
        // close the writer, which should remove all temp files
270 6
        $this->close();
271
272
        // remove output file if it was created
273 6
        if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) {
274 5
            $outputFolderPath = dirname($this->outputFilePath);
275 5
            $fileSystemHelper = $this->helperFactory->createFileSystemHelper($outputFolderPath);
276 5
            $fileSystemHelper->deleteFile($this->outputFilePath);
277
        }
278 6
    }
279
}
280