Completed
Pull Request — master (#746)
by duan
03:03
created

WriterAbstract::openToBrowser()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 57

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 57
ccs 0
cts 0
cp 0
rs 8.9381
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 6

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Box\Spout\Writer;
4
5
use Box\Spout\Common\Creator\HelperFactory;
6
use Box\Spout\Common\Entity\Row;
7
use Box\Spout\Common\Entity\Style\Style;
8
use Box\Spout\Common\Exception\InvalidArgumentException;
9
use Box\Spout\Common\Exception\IOException;
10
use Box\Spout\Common\Exception\SpoutException;
11
use Box\Spout\Common\Helper\GlobalFunctionsHelper;
12
use Box\Spout\Common\Manager\OptionsManagerInterface;
13
use Box\Spout\Writer\Common\Entity\Options;
14
use Box\Spout\Writer\Exception\WriterAlreadyOpenedException;
15
use Box\Spout\Writer\Exception\WriterNotOpenedException;
16
17
/**
18
 * Class WriterAbstract
19
 *
20
 * @abstract
21
 */
22
abstract class WriterAbstract implements WriterInterface
23
{
24
    /** @var string Path to the output file */
25
    protected $outputFilePath;
26
27
    /** @var resource Pointer to the file/stream we will write to */
28
    protected $filePointer;
29
30
    /** @var bool Indicates whether the writer has been opened or not */
31
    protected $isWriterOpened = false;
32
33
    /** @var GlobalFunctionsHelper Helper to work with global functions */
34
    protected $globalFunctionsHelper;
35
36
    /** @var HelperFactory $helperFactory */
37
    protected $helperFactory;
38
39
    /** @var OptionsManagerInterface Writer options manager */
40
    protected $optionsManager;
41
42
    /** @var string Content-Type value for the header - to be defined by child class */
43
    protected static $headerContentType;
44
45
    /**
46
     * @param OptionsManagerInterface $optionsManager
47
     * @param GlobalFunctionsHelper $globalFunctionsHelper
48
     * @param HelperFactory $helperFactory
49
     */
50 105
    public function __construct(
51
        OptionsManagerInterface $optionsManager,
52
        GlobalFunctionsHelper $globalFunctionsHelper,
53
        HelperFactory $helperFactory
54
    ) {
55 105
        $this->optionsManager = $optionsManager;
56 105
        $this->globalFunctionsHelper = $globalFunctionsHelper;
57 105
        $this->helperFactory = $helperFactory;
58 105
    }
59
60
    /**
61
     * Opens the streamer and makes it ready to accept data.
62
     *
63
     * @throws IOException If the writer cannot be opened
64
     * @return void
65
     */
66
    abstract protected function openWriter();
67
68
    /**
69
     * Adds a row to the currently opened writer.
70
     *
71
     * @param Row $row The row containing cells and styles
72
     * @throws WriterNotOpenedException If the workbook is not created yet
73
     * @throws IOException If unable to write data
74
     * @return void
75
     */
76
    abstract protected function addRowToWriter(Row $row);
77
78
    /**
79
     * Closes the streamer, preventing any additional writing.
80
     *
81
     * @return void
82
     */
83
    abstract protected function closeWriter();
84
85
    /**
86
     * {@inheritdoc}
87
     */
88 2
    public function setDefaultRowStyle(Style $defaultStyle)
89
    {
90 2
        $this->optionsManager->setOption(Options::DEFAULT_ROW_STYLE, $defaultStyle);
91
92 2
        return $this;
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98 90
    public function openToFile($outputFilePath)
99
    {
100 90
        $this->outputFilePath = $outputFilePath;
101
102 90
        $this->filePointer = $this->globalFunctionsHelper->fopen($this->outputFilePath, 'wb+');
103 90
        $this->throwIfFilePointerIsNotAvailable();
104
105 87
        $this->openWriter();
106 87
        $this->isWriterOpened = true;
107
108 87
        return $this;
109
    }
110
111
    /**
112
     * @codeCoverageIgnore
113
     * {@inheritdoc}
114
     */
115
    public function openToBrowser($outputFileName, array $headers = [])
116
    {
117
        $this->outputFilePath = $this->globalFunctionsHelper->basename($outputFileName);
118
119
        $this->filePointer = $this->globalFunctionsHelper->fopen('php://output', 'w');
120
        $this->throwIfFilePointerIsNotAvailable();
121
122
        // Clear any previous output (otherwise the generated file will be corrupted)
123
        // @see https://github.com/box/spout/issues/241
124
        $this->globalFunctionsHelper->ob_end_clean();
125
126
        /*
127
         * Set headers
128
         *
129
         * For newer browsers such as Firefox, Chrome, Opera, Safari, etc., they all support and use `filename*`
130
         * specified by the new standard, even if they do not automatically decode filename; it does not matter;
131
         * and for older versions of Internet Explorer, they are not recognized `filename*`, will automatically
132
         * ignore it and use the old `filename` (the only minor flaw is that there must be an English suffix name).
133
         * In this way, the multi-browser multi-language compatibility problem is perfectly solved, which does not
134
         * require UA judgment and is more in line with the standard.
135
         *
136
         * @see https://github.com/box/spout/issues/745
137
         * @see https://tools.ietf.org/html/rfc6266
138
         * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
139
         */
140
        $this->globalFunctionsHelper->header('Content-Type: ' . static::$headerContentType);
141
        $this->globalFunctionsHelper->header(
142
            'Content-Disposition: attachment; ' .
143
            'filename="' . rawurldecode($this->outputFilePath) . '"; ' .
144
            'filename*=UTF-8\'\'' . rawurldecode($this->outputFilePath)
145
        );
146
147
        /*
148
         * When forcing the download of a file over SSL,IE8 and lower browsers fail
149
         * if the Cache-Control and Pragma headers are not set.
150
         *
151
         * @see http://support.microsoft.com/KB/323308
152
         * @see https://github.com/liuggio/ExcelBundle/issues/45
153
         */
154
        $this->globalFunctionsHelper->header('Cache-Control: max-age=0');
155
        $this->globalFunctionsHelper->header('Pragma: public');
156
157
        /*
158
         * Set custom Headers
159
         * Sometimes need to output or cover more headers.
160
         *
161
         * @see https://github.com/box/spout/issues/745
162
         */
163
        foreach ($headers as $header){
164
            $this->globalFunctionsHelper->header($header);
165
        }
166
167
        $this->openWriter();
168
        $this->isWriterOpened = true;
169
170
        return $this;
171
    }
172
173
    /**
174
     * Checks if the pointer to the file/stream to write to is available.
175
     * Will throw an exception if not available.
176
     *
177
     * @throws IOException If the pointer is not available
178
     * @return void
179
     */
180 90
    protected function throwIfFilePointerIsNotAvailable()
181
    {
182 90
        if (!$this->filePointer) {
183 3
            throw new IOException('File pointer has not be opened');
184
        }
185 87
    }
186
187
    /**
188
     * Checks if the writer has already been opened, since some actions must be done before it gets opened.
189
     * Throws an exception if already opened.
190
     *
191
     * @param string $message Error message
192
     * @throws WriterAlreadyOpenedException If the writer was already opened and must not be.
193
     * @return void
194
     */
195 54
    protected function throwIfWriterAlreadyOpened($message)
196
    {
197 54
        if ($this->isWriterOpened) {
198 5
            throw new WriterAlreadyOpenedException($message);
199
        }
200 49
    }
201
202
    /**
203
     * {@inheritdoc}
204
     */
205 85
    public function addRow(Row $row)
206
    {
207 85
        if ($this->isWriterOpened) {
208
            try {
209 75
                $this->addRowToWriter($row);
210 4
            } catch (SpoutException $e) {
211
                // if an exception occurs while writing data,
212
                // close the writer and remove all files created so far.
213 4
                $this->closeAndAttemptToCleanupAllFiles();
214
215
                // re-throw the exception to alert developers of the error
216 75
                throw $e;
217
            }
218
        } else {
219 10
            throw new WriterNotOpenedException('The writer needs to be opened before adding row.');
220
        }
221
222 73
        return $this;
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     */
228 71
    public function addRows(array $rows)
229
    {
230 71
        foreach ($rows as $row) {
231 71
            if (!$row instanceof Row) {
232 2
                $this->closeAndAttemptToCleanupAllFiles();
233 2
                throw new InvalidArgumentException('The input should be an array of Row');
234
            }
235
236 69
            $this->addRow($row);
237
        }
238
239 63
        return $this;
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     */
245 81
    public function close()
246
    {
247 81
        if (!$this->isWriterOpened) {
248 1
            return;
249
        }
250
251 80
        $this->closeWriter();
252
253 80
        if (\is_resource($this->filePointer)) {
254 80
            $this->globalFunctionsHelper->fclose($this->filePointer);
255
        }
256
257 80
        $this->isWriterOpened = false;
258 80
    }
259
260
    /**
261
     * Closes the writer and attempts to cleanup all files that were
262
     * created during the writing process (temp files & final file).
263
     *
264
     * @return void
265
     */
266 6
    private function closeAndAttemptToCleanupAllFiles()
267
    {
268
        // close the writer, which should remove all temp files
269 6
        $this->close();
270
271
        // remove output file if it was created
272 6
        if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) {
273 5
            $outputFolderPath = \dirname($this->outputFilePath);
274 5
            $fileSystemHelper = $this->helperFactory->createFileSystemHelper($outputFolderPath);
275 5
            $fileSystemHelper->deleteFile($this->outputFilePath);
276
        }
277 6
    }
278
}
279