Completed
Pull Request — master (#89)
by Peter
02:19
created

RenderIndexFileStream   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 242
Duplicated Lines 9.09 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 91.67%

Importance

Changes 0
Metric Value
dl 22
loc 242
c 0
b 0
f 0
wmc 23
lcom 1
cbo 5
ccs 77
cts 84
cp 0.9167
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A getFilename() 0 4 1
A open() 22 22 3
A close() 0 28 3
A push() 0 18 3
A addSubStreamFileToIndex() 0 19 4
A getIndexPartFilename() 0 10 1
A count() 0 4 1
A moveParts() 0 12 3
A removeOldParts() 0 13 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * GpsLab component.
4
 *
5
 * @author    Peter Gribanov <[email protected]>
6
 * @copyright Copyright (c) 2011, Peter Gribanov
7
 * @license   http://opensource.org/licenses/MIT
8
 */
9
10
namespace GpsLab\Component\Sitemap\Stream;
11
12
use GpsLab\Component\Sitemap\Render\SitemapIndexRender;
13
use GpsLab\Component\Sitemap\Stream\Exception\FileAccessException;
14
use GpsLab\Component\Sitemap\Stream\Exception\OverflowException;
15
use GpsLab\Component\Sitemap\Stream\Exception\StreamStateException;
16
use GpsLab\Component\Sitemap\Stream\State\StreamState;
17
use GpsLab\Component\Sitemap\Url\Url;
18
19
class RenderIndexFileStream implements FileStream
20
{
21
    /**
22
     * @var SitemapIndexRender
23
     */
24
    private $render;
25
26
    /**
27
     * @var FileStream
28
     */
29
    private $substream;
30
31
    /**
32
     * @var StreamState
33
     */
34
    private $state;
35
36
    /**
37
     * @var resource|null
38
     */
39
    private $handle;
40
41
    /**
42
     * @var string
43
     */
44
    private $host = '';
45
46
    /**
47
     * @var string
48
     */
49
    private $filename = '';
50
51
    /**
52
     * @var string
53
     */
54
    private $tmp_filename = '';
55
56
    /**
57
     * @var int
58
     */
59
    private $index = 0;
60
61
    /**
62
     * @var int
63
     */
64
    private $counter = 0;
65
66
    /**
67
     * @var bool
68
     */
69
    private $empty_index = true;
70
71
    /**
72
     * @param SitemapIndexRender $render
73
     * @param FileStream         $substream
74
     * @param string             $host
75
     * @param string             $filename
76
     */
77 12
    public function __construct(SitemapIndexRender $render, FileStream $substream, $host, $filename)
78
    {
79 12
        $this->render = $render;
80 12
        $this->substream = $substream;
81 12
        $this->host = rtrim($host, '/');
82 12
        $this->filename = $filename;
83 12
        $this->state = new StreamState();
84 12
    }
85
86
    /**
87
     * @return string
88
     */
89 1
    public function getFilename()
90
    {
91 1
        return $this->filename;
92
    }
93
94
    /**
95
     * @return void
96
     */
97 9 View Code Duplication
    public function open()
98
    {
99 9
        $this->state->open();
100 9
        $this->substream->open();
101
102 9
        $tmp_filename = tempnam(sys_get_temp_dir(), 'sitemap_index');
103
104 9
        if ($tmp_filename === false) {
105
            throw FileAccessException::failedCreateUnique(sys_get_temp_dir(), 'sitemap_index');
106
        }
107
108 9
        $handle = @fopen($tmp_filename, 'wb');
109
110 9
        if ($handle === false) {
111
            throw FileAccessException::notWritable($tmp_filename);
112
        }
113
114 9
        $this->tmp_filename = $tmp_filename;
115 9
        $this->handle = $handle;
116
117 9
        fwrite($this->handle, $this->render->start());
118 9
    }
119
120
    /**
121
     * @return void
122
     */
123 12
    public function close()
124
    {
125 12
        $this->state->close();
126 9
        $this->substream->close();
127
128
        // not add empty sitemap part to index
129 9
        if (!$this->empty_index) {
130 5
            $this->addSubStreamFileToIndex();
131
        }
132
133 8
        fwrite($this->handle, $this->render->end());
134 8
        fclose($this->handle);
135
136 8
        $this->moveParts();
137
138
        // move the sitemap index file from the temporary directory to the target
139 8
        if (!rename($this->tmp_filename, $this->filename)) {
140
            unlink($this->tmp_filename);
141
142
            throw FileAccessException::failedOverwrite($this->tmp_filename, $this->filename);
143
        }
144
145 8
        $this->removeOldParts();
146
147 8
        $this->handle = null;
148 8
        $this->tmp_filename = '';
149 8
        $this->counter = 0;
150 8
    }
151
152
    /**
153
     * @param Url $url
154
     *
155
     * @return void
156
     */
157 7
    public function push(Url $url)
158
    {
159 7
        if (!$this->state->isReady()) {
160 2
            throw StreamStateException::notReady();
161
        }
162
163
        try {
164 5
            $this->substream->push($url);
165 1
        } catch (OverflowException $e) {
166 1
            $this->substream->close();
167 1
            $this->addSubStreamFileToIndex();
168 1
            $this->substream->open();
169 1
            $this->substream->push($url);
170
        }
171
172 5
        $this->empty_index = false;
173 5
        ++$this->counter;
174 5
    }
175
176
    /**
177
     * @return void
178
     */
179 5
    private function addSubStreamFileToIndex()
180
    {
181 5
        $filename = $this->substream->getFilename();
182 5
        $indexed_filename = $this->getIndexPartFilename($filename, ++$this->index);
183
184 5
        if (!file_exists($filename) || ($time = filemtime($filename)) === false) {
185 1
            throw FileAccessException::notReadable($filename);
186
        }
187
188 4
        $last_mod = (new \DateTimeImmutable())->setTimestamp($time);
189
190
        // rename sitemap file to sitemap part
191 4
        $new_filename = sys_get_temp_dir().'/'.$indexed_filename;
192 4
        if (!rename($filename, $new_filename)) {
193
            throw FileAccessException::failedOverwrite($filename, $new_filename);
194
        }
195
196 4
        fwrite($this->handle, $this->render->sitemap($this->host.'/'.$indexed_filename, $last_mod));
0 ignored issues
show
Security Bug introduced by
It seems like $last_mod defined by (new \DateTimeImmutable())->setTimestamp($time) on line 188 can also be of type false; however, GpsLab\Component\Sitemap...pIndexRender::sitemap() does only seem to accept null|object<DateTimeImmutable>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
197 4
    }
198
199
    /**
200
     * @param string $path
201
     * @param int    $index
202
     *
203
     * @return string
204
     */
205 9
    private function getIndexPartFilename($path, $index)
206
    {
207
        // use explode() for correct add index
208
        // sitemap.xml -> sitemap1.xml
209
        // sitemap.xml.gz -> sitemap1.xml.gz
210
211 9
        list($path, $extension) = explode('.', basename($path), 2) + ['', ''];
212
213 9
        return sprintf('%s%s.%s', $path, $index, $extension);
214
    }
215
216
    /**
217
     * @return int
218
     */
219 4
    public function count()
220
    {
221 4
        return $this->counter;
222
    }
223
224
    /**
225
     * Move parts of the sitemap from the temporary directory to the target.
226
     *
227
     * @return void
228
     */
229 8
    private function moveParts()
230
    {
231 8
        $filename = $this->substream->getFilename();
232 8
        for ($i = 1; $i <= $this->index; ++$i) {
233 4
            $indexed_filename = $this->getIndexPartFilename($filename, $i);
234 4
            $source = sys_get_temp_dir().'/'.$indexed_filename;
235 4
            $target = dirname($this->filename).'/'.$indexed_filename;
236 4
            if (!rename($source, $target)) {
237
                throw FileAccessException::failedOverwrite($source, $target);
238
            }
239
        }
240 8
    }
241
242
    /**
243
     * Remove old parts of the sitemap from the target directory.
244
     *
245
     * @return void
246
     */
247 8
    private function removeOldParts()
248
    {
249 8
        $filename = $this->substream->getFilename();
250 8
        for ($i = $this->index + 1; true; ++$i) {
251 8
            $indexed_filename = $this->getIndexPartFilename($filename, $i);
252 8
            $target = dirname($this->filename).'/'.$indexed_filename;
253 8
            if (file_exists($target)) {
254
                unlink($target);
255
            } else {
256 8
                break;
257
            }
258
        }
259 8
    }
260
}
261