Completed
Pull Request — master (#132)
by
unknown
06:43 queued 04:56
created

ZipExtensionAdapter::addEntries()   B

Complexity

Conditions 10
Paths 62

Size

Total Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 60
rs 7.006
c 0
b 0
f 0
cc 10
nc 62
nop 3

How to fix   Long Method    Complexity   

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
/*
4
 * This file is part of Zippy.
5
 *
6
 * (c) Alchemy <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Alchemy\Zippy\Adapter;
13
14
use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
15
use Alchemy\Zippy\Adapter\Resource\ZipArchiveResource;
16
use Alchemy\Zippy\Adapter\VersionProbe\ZipExtensionVersionProbe;
17
use Alchemy\Zippy\Archive\Archive;
18
use Alchemy\Zippy\Archive\Member;
19
use Alchemy\Zippy\Exception\NotSupportedException;
20
use Alchemy\Zippy\Exception\RuntimeException;
21
use Alchemy\Zippy\Exception\InvalidArgumentException;
22
use Alchemy\Zippy\Resource\Resource as ZippyResource;
23
use Alchemy\Zippy\Resource\ResourceManager;
24
25
/**
26
 * ZipExtensionAdapter allows you to create and extract files from archives
27
 * using PHP Zip extension
28
 *
29
 * @see http://www.php.net/manual/en/book.zip.php
30
 */
31
class ZipExtensionAdapter extends AbstractAdapter
32
{
33
    private $errorCodesMapping = array(
34
        \ZipArchive::ER_EXISTS => "File already exists",
35
        \ZipArchive::ER_INCONS => "Zip archive inconsistent",
36
        \ZipArchive::ER_INVAL  => "Invalid argument",
37
        \ZipArchive::ER_MEMORY => "Malloc failure",
38
        \ZipArchive::ER_NOENT  => "No such file",
39
        \ZipArchive::ER_NOZIP  => "Not a zip archive",
40
        \ZipArchive::ER_OPEN   => "Can't open file",
41
        \ZipArchive::ER_READ   => "Read error",
42
        \ZipArchive::ER_SEEK   => "Seek error"
43
    );
44
45
    public function __construct(ResourceManager $manager)
46
    {
47
        parent::__construct($manager);
48
        $this->probe = new ZipExtensionVersionProbe();
49
    }
50
51
    /**
52
     * @inheritdoc
53
     */
54
    protected function doListMembers(ResourceInterface $resource)
55
    {
56
        $members = array();
57
        for ($i = 0; $i < $resource->getResource()->numFiles; $i++) {
58
            $stat = $resource->getResource()->statIndex($i);
59
            $members[] = new Member(
60
                $resource,
61
                $this,
62
                $stat['name'],
63
                $stat['size'],
64
                new \DateTime('@' . $stat['mtime']),
65
                0 === strlen($resource->getResource()->getFromIndex($i, 1))
66
            );
67
        }
68
69
        return $members;
70
    }
71
72
    /**
73
     * @inheritdoc
74
     */
75
    public static function getName()
76
    {
77
        return 'zip-extension';
78
    }
79
80
    /**
81
     * @inheritdoc
82
     */
83
    protected function doExtract(ResourceInterface $resource, $to)
84
    {
85
        return $this->extractMembers($resource, null, $to);
86
    }
87
88
    /**
89
     * @inheritdoc
90
     */
91
    protected function doExtractMembers(ResourceInterface $resource, $members, $to, $overwrite = false)
92
    {
93
        if (null === $to) {
94
            // if no destination is given, will extract to zip current folder
95
            $to = dirname(realpath($resource->getResource()->filename));
96
        }
97
98
        if (!is_dir($to)) {
99
            $resource->getResource()->close();
100
            throw new InvalidArgumentException(sprintf("%s is not a directory", $to));
101
        }
102
103
        if (!is_writable($to)) {
104
            $resource->getResource()->close();
105
            throw new InvalidArgumentException(sprintf("%s is not writable", $to));
106
        }
107
108
        if (null !== $members) {
109
            $membersTemp = (array) $members;
110
            if (empty($membersTemp)) {
111
                $resource->getResource()->close();
112
113
                throw new InvalidArgumentException("no members provided");
114
            }
115
            $members = array();
116
            // allows $members to be an array of strings or array of Members
117
            foreach ($membersTemp as $member) {
118
                if ($member instanceof Member) {
119
                    $member = $member->getLocation();
120
                }
121
122
                if ($resource->getResource()->locateName($member) === false) {
123
                    $resource->getResource()->close();
124
125
                    throw new InvalidArgumentException(sprintf('%s is not in the zip file', $member));
126
                }
127
128
                if ($overwrite == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
129
                    if (file_exists($member)) {
130
                        $resource->getResource()->close();
131
132
                        throw new RuntimeException('Target file ' . $member . ' already exists.');
133
                    }
134
                }
135
136
                $members[] = $member;
137
            }
138
        }
139
140
        if (!$resource->getResource()->extractTo($to, $members)) {
141
            $resource->getResource()->close();
142
143
            throw new InvalidArgumentException(sprintf('Unable to extract archive : %s', $resource->getResource()->getStatusString()));
144
        }
145
146
        return new \SplFileInfo($to);
147
    }
148
149
    /**
150
     * @inheritdoc
151
     */
152
    protected function doRemove(ResourceInterface $resource, $files)
153
    {
154
        $files = (array) $files;
155
156
        if (empty($files)) {
157
            throw new InvalidArgumentException("no files provided");
158
        }
159
160
        // either remove all files or none in case of error
161
        foreach ($files as $file) {
162
            if ($resource->getResource()->locateName($file) === false) {
163
                $resource->getResource()->unchangeAll();
164
                $resource->getResource()->close();
165
166
                throw new InvalidArgumentException(sprintf('%s is not in the zip file', $file));
167
            }
168
            if (!$resource->getResource()->deleteName($file)) {
169
                $resource->getResource()->unchangeAll();
170
                $resource->getResource()->close();
171
172
                throw new RuntimeException(sprintf('unable to remove %s', $file));
173
            }
174
        }
175
        $this->flush($resource->getResource());
176
177
        return $files;
178
    }
179
180
    /**
181
     * @inheritdoc
182
     */
183
    protected function doAdd(ResourceInterface $resource, $files, $recursive)
184
    {
185
        $files = (array) $files;
186
        if (empty($files)) {
187
            $resource->getResource()->close();
188
            throw new InvalidArgumentException("no files provided");
189
        }
190
        $this->addEntries($resource, $files, $recursive);
191
192
        return $files;
193
    }
194
195
    /**
196
     * @inheritdoc
197
     */
198
    protected function doCreate($path, $files, $recursive)
199
    {
200
        $files = (array) $files;
201
202
        if (empty($files)) {
203
            throw new NotSupportedException("Cannot create an empty zip");
204
        }
205
206
        $resource = $this->getResource($path, \ZipArchive::CREATE);
207
        $this->addEntries($resource, $files, $recursive);
208
209
        return new Archive($resource, $this, $this->manager);
210
    }
211
212
    /**
213
     * Returns a new instance of the invoked adapter
214
     *
215
     * @return AbstractAdapter
216
     *
217
     * @throws RuntimeException In case object could not be instanciated
218
     */
219
    public static function newInstance()
220
    {
221
        return new ZipExtensionAdapter(ResourceManager::create());
222
    }
223
224
    protected function createResource($path)
225
    {
226
        return $this->getResource($path, \ZipArchive::CHECKCONS);
227
    }
228
229
    private function getResource($path, $mode)
230
    {
231
        $zip = new \ZipArchive();
232
        $res = $zip->open($path, $mode);
233
234
        if ($res !== true) {
235
            throw new RuntimeException($this->errorCodesMapping[$res]);
236
        }
237
238
        return new ZipArchiveResource($zip);
239
    }
240
241
    private function addEntries(ResourceInterface $zipResource, array $files, $recursive)
242
    {
243
        $stack = new \SplStack();
244
245
        $error = null;
246
        $cwd = getcwd();
247
        $collection = $this->manager->handle($cwd, $files);
248
249
        $this->chdir($collection->getContext());
250
251
        $adapter = $this;
252
253
        try {
254
            $collection->forAll(function($i, ZippyResource $resource) use ($zipResource, $stack, $recursive, $adapter) {
255
                $adapter->checkReadability($zipResource->getResource(), $resource->getTarget());
256
                if (is_dir($resource->getTarget())) {
257
                    if ($recursive) {
258
                        $stack->push($resource->getTarget() . ((substr($resource->getTarget(), -1) === DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR));
259
                    } else {
260
                        $adapter->addEmptyDir($zipResource->getResource(), $resource->getTarget());
261
                    }
262
                } else {
263
                    $adapter->addFileToZip($zipResource->getResource(), $resource->getTarget());
264
                }
265
266
                return true;
267
            });
268
269
            // recursively add dirs
270
            while (!$stack->isEmpty()) {
271
                $dir = $stack->pop();
272
                // removes . and ..
273
                $files = array_diff(scandir($dir), array(".", ".."));
274
                if (count($files) > 0) {
275
                    foreach ($files as $file) {
276
                        $file = $dir . $file;
277
                        $this->checkReadability($zipResource->getResource(), $file);
278
                        if (is_dir($file)) {
279
                            $stack->push($file . DIRECTORY_SEPARATOR);
280
                        } else {
281
                            $this->addFileToZip($zipResource->getResource(), $file);
282
                        }
283
                    }
284
                } else {
285
                    $this->addEmptyDir($zipResource->getResource(), $dir);
286
                }
287
            }
288
            $this->flush($zipResource->getResource());
289
290
            $this->manager->cleanup($collection);
291
        } catch (\Exception $e) {
292
            $error = $e;
293
        }
294
295
        $this->chdir($cwd);
296
297
        if ($error) {
298
            throw $error;
299
        }
300
    }
301
302
    /**
303
     * Is public for PHP 5.3 compatibility, should be private
304
     *
305
     * @param \ZipArchive $zip
306
     * @param string      $file
307
     */
308
    public function checkReadability(\ZipArchive $zip, $file)
309
    {
310
        if (!is_readable($file)) {
311
            $zip->unchangeAll();
312
            $zip->close();
313
314
            throw new InvalidArgumentException(sprintf('could not read %s', $file));
315
        }
316
    }
317
318
    /**
319
     * Is public for PHP 5.3 compatibility, should be private
320
     *
321
     * @param \ZipArchive $zip
322
     * @param string      $file
323
     */
324 View Code Duplication
    public function addFileToZip(\ZipArchive $zip, $file)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
325
    {
326
        if (!$zip->addFile($file)) {
327
            $zip->unchangeAll();
328
            $zip->close();
329
330
            throw new RuntimeException(sprintf('unable to add %s to the zip file', $file));
331
        }
332
    }
333
334
    /**
335
     * Is public for PHP 5.3 compatibility, should be private
336
     *
337
     * @param \ZipArchive $zip
338
     * @param string      $dir
339
     */
340 View Code Duplication
    public function addEmptyDir(\ZipArchive $zip, $dir)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
341
    {
342
        if (!$zip->addEmptyDir($dir)) {
343
            $zip->unchangeAll();
344
            $zip->close();
345
346
            throw new RuntimeException(sprintf('unable to add %s to the zip file', $dir));
347
        }
348
    }
349
350
    /**
351
     * Flushes changes to the archive
352
     *
353
     * @param \ZipArchive $zip
354
     */
355
    private function flush(\ZipArchive $zip) // flush changes by reopening the file
356
    {
357
        $path = $zip->filename;
358
        $zip->close();
359
        $zip->open($path, \ZipArchive::CHECKCONS);
360
    }
361
}
362