Completed
Pull Request — master (#134)
by
unknown
05:12
created

ZipExtensionAdapter::doExtractMembers()   C

Complexity

Conditions 14
Paths 22

Size

Total Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 58
rs 6.2666
c 0
b 0
f 0
cc 14
nc 22
nop 4

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