ZipExtensionAdapter::doExtractMembers()   C
last analyzed

Complexity

Conditions 12
Paths 22

Size

Total Lines 56
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 29
nc 22
nop 4
dl 0
loc 56
rs 6.9666
c 0
b 0
f 0

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 Compressy.
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 Gocobachi\Compressy\Adapter;
13
14
use Gocobachi\Compressy\Adapter\Resource\ResourceInterface;
15
use Gocobachi\Compressy\Adapter\Resource\ZipArchiveResource;
16
use Gocobachi\Compressy\Adapter\VersionProbe\ZipExtensionVersionProbe;
17
use Gocobachi\Compressy\Archive\Archive;
18
use Gocobachi\Compressy\Archive\Member;
19
use Gocobachi\Compressy\Exception\NotSupportedException;
20
use Gocobachi\Compressy\Exception\RuntimeException;
21
use Gocobachi\Compressy\Exception\InvalidArgumentException;
22
use Gocobachi\Compressy\Resource\Resource as ZippyResource;
23
use Gocobachi\Compressy\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 = [
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) {
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
232
        $res = \ZipArchive::ER_OPEN;
0 ignored issues
show
Unused Code introduced by
The assignment to $res is dead and can be removed.
Loading history...
233
        $zip = new \ZipArchive();
234
235
        try {
236
            $res = $zip->open($path, $mode);
237
        } catch (\Exception $e) {
238
            throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
239
        }
240
241
        if ($res !== true) {
242
            if (!isset($this->errorCodesMapping[$res])) {
243
                throw new RuntimeException('Unknown error when opening the zip file');
244
            }
245
246
            throw new RuntimeException($this->errorCodesMapping[$res]);
247
        }
248
249
        return new ZipArchiveResource($zip);
250
    }
251
252
    private function addEntries(ResourceInterface $zipResource, array $files, $recursive)
253
    {
254
        $stack = new \SplStack();
255
256
        $error = null;
257
        $cwd = getcwd();
258
        $collection = $this->manager->handle($cwd, $files);
259
260
        $this->chdir($collection->getContext());
261
262
        $adapter = $this;
263
264
        try {
265
            $collection->forAll(function($i, ZippyResource $resource) use ($zipResource, $stack, $recursive, $adapter) {
266
                $adapter->checkReadability($zipResource->getResource(), $resource->getTarget());
267
                if (is_dir($resource->getTarget())) {
268
                    if ($recursive) {
269
                        $stack->push($resource->getTarget() . ((substr($resource->getTarget(), -1) === DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR));
270
                    } else {
271
                        $adapter->addEmptyDir($zipResource->getResource(), $resource->getTarget());
272
                    }
273
                } else {
274
                    $adapter->addFileToZip($zipResource->getResource(), $resource->getTarget());
275
                }
276
277
                return true;
278
            });
279
280
            // recursively add dirs
281
            while (!$stack->isEmpty()) {
282
                $dir = $stack->pop();
283
                // removes . and ..
284
                $files = array_diff(scandir($dir), array(".", ".."));
285
                if (count($files) > 0) {
286
                    foreach ($files as $file) {
287
                        $file = $dir . $file;
288
                        $this->checkReadability($zipResource->getResource(), $file);
289
                        if (is_dir($file)) {
290
                            $stack->push($file . DIRECTORY_SEPARATOR);
291
                        } else {
292
                            $this->addFileToZip($zipResource->getResource(), $file);
293
                        }
294
                    }
295
                } else {
296
                    $this->addEmptyDir($zipResource->getResource(), $dir);
297
                }
298
            }
299
            $this->flush($zipResource->getResource());
300
301
            $this->manager->cleanup($collection);
302
        } catch (\Exception $e) {
303
            $error = $e;
304
        }
305
306
        $this->chdir($cwd);
307
308
        if ($error) {
309
            throw $error;
310
        }
311
    }
312
313
    /**
314
     *
315
     * @param \ZipArchive $zip
316
     * @param string      $file
317
     */
318
    private function checkReadability(\ZipArchive $zip, $file)
319
    {
320
        if (!is_readable($file)) {
321
            $zip->unchangeAll();
322
            $zip->close();
323
324
            throw new InvalidArgumentException(sprintf('could not read %s', $file));
325
        }
326
    }
327
328
    /**
329
     *
330
     * @param \ZipArchive $zip
331
     * @param string      $file
332
     */
333
    private function addFileToZip(\ZipArchive $zip, $file)
334
    {
335
        if (!$zip->addFile($file)) {
336
            $zip->unchangeAll();
337
            $zip->close();
338
339
            throw new RuntimeException(sprintf('unable to add %s to the zip file', $file));
340
        }
341
    }
342
343
    /**
344
     *
345
     * @param \ZipArchive $zip
346
     * @param string      $dir
347
     */
348
    private function addEmptyDir(\ZipArchive $zip, $dir)
349
    {
350
        if (!$zip->addEmptyDir($dir)) {
351
            $zip->unchangeAll();
352
            $zip->close();
353
354
            throw new RuntimeException(sprintf('unable to add %s to the zip file', $dir));
355
        }
356
    }
357
358
    /**
359
     * Flushes changes to the archive
360
     *
361
     * @param \ZipArchive $zip
362
     */
363
    private function flush(\ZipArchive $zip) // flush changes by reopening the file
364
    {
365
        $path = $zip->filename;
366
        $zip->close();
367
        $zip->open($path, \ZipArchive::CHECKCONS);
368
    }
369
}
370