Passed
Push — master ( e9611b...a3681d )
by f
13:07
created

TarByPhar   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 389
Duplicated Lines 0 %

Test Coverage

Coverage 61.76%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 156
c 4
b 0
f 0
dl 0
loc 389
rs 3.44
ccs 84
cts 136
cp 0.6176
wmc 62

19 Methods

Rating   Name   Duplication   Size   Complexity  
F createArchive() 0 96 23
A getFileStream() 0 3 1
A extractArchive() 0 8 2
A open() 0 3 1
A getFileData() 0 19 3
A getDescription() 0 3 1
A getFileNames() 0 10 2
A getInstallationInstruction() 0 3 1
A addFileFromString() 0 4 1
A deleteFiles() 0 13 3
A isInstalled() 0 3 1
A getArchiveInformation() 0 18 3
B checkFormatSupport() 0 28 9
A extractFiles() 0 7 2
A addFiles() 0 19 4
A getSupportedFormats() 0 7 1
A isFileExists() 0 7 2
A getFileContent() 0 3 1
A __construct() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like TarByPhar often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TarByPhar, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace wapmorgan\UnifiedArchive\Drivers;
3
4
use Exception;
5
use FilesystemIterator;
6
use Phar;
7
use PharData;
8
use PharFileInfo;
9
use RecursiveIteratorIterator;
10
use Vtiful\Kernel\Format;
11
use wapmorgan\UnifiedArchive\ArchiveEntry;
12
use wapmorgan\UnifiedArchive\ArchiveInformation;
13
use wapmorgan\UnifiedArchive\Drivers\BasicDriver;
14
use wapmorgan\UnifiedArchive\Exceptions\ArchiveCreationException;
15
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
16
use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException;
17
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
18
use wapmorgan\UnifiedArchive\Formats;
19
20
class TarByPhar extends BasicDriver
21
{
22
    const TYPE = self::TYPE_EXTENSION;
23
    public static $disabled = false;
24
25
    /**
26
     * @var false|string
27
     */
28
    protected $archiveFileName;
29
30
    /**
31
     * @var PharData
32
     */
33
    protected $tar;
34
35
    /**
36
     * @var float
37
     */
38
    protected $compressRatio;
39 1
40
    protected $pureFilesNumber;
41
42 1
    /**
43
     * @var int Flags for iterator
44
     */
45
    const PHAR_FLAGS = FilesystemIterator::UNIX_PATHS;
46
47
    public static function isInstalled()
48
    {
49
        return extension_loaded('phar');
50
    }
51
52
    /**
53 3
     * @inheritDoc
54
     */
55 3
    public static function getInstallationInstruction()
56
    {
57 3
        return 'install `phar` extension and optionally php-extensions (zlib, bz2)';
58
    }
59 1
60 2
    /**
61 1
     * @inheritDoc
62 1
     */
63 1
    public static function getDescription()
64
    {
65
        return 'adapter for ext-phar';
66
    }
67
68
    /**
69
     * @return array
70
     */
71
    public static function getSupportedFormats()
72
    {
73
        return [
74
            Formats::TAR,
75
            Formats::TAR_GZIP,
76
            Formats::TAR_BZIP,
77
            Formats::ZIP,
78
        ];
79
    }
80
81
    /**
82
     * @param $format
83
     * @return array
84
     */
85
    public static function checkFormatSupport($format)
86
    {
87
        if (static::$disabled || !static::isInstalled()) {
88 10
            return [];
89
        }
90 10
91 10
        $abilities = [
92 10
            BasicDriver::OPEN,
93
            BasicDriver::EXTRACT_CONTENT,
94
            BasicDriver::STREAM_CONTENT,
95
            BasicDriver::APPEND,
96
            BasicDriver::DELETE,
97 10
            BasicDriver::CREATE,
98
        ];
99 10
100 10
        switch ($format) {
101
            case Formats::TAR:
102
            case Formats::ZIP:
103
                return $abilities;
104
105 10
            case Formats::TAR_GZIP:
106
                return extension_loaded('zlib')
107 10
                    ? $abilities
108 10
                    : [];
109
            case Formats::TAR_BZIP:
110
                return extension_loaded('bz2')
111
                    ? $abilities
112
                    : [];
113 10
        }
114 10
    }
115 10
116 10
    /**
117
     * @inheritDoc
118 10
     */
119
    public function __construct($archiveFileName, $format, $password = null)
120
    {
121
        $this->archiveFileName = realpath($archiveFileName);
122
        $this->open();
123
    }
124
125
    /**
126
     *
127
     */
128
    protected function open()
129
    {
130
        $this->tar = new PharData($this->archiveFileName, self::PHAR_FLAGS);
131
    }
132
133
    /**
134
     * @inheritDoc
135
     */
136
    public function getArchiveInformation()
137
    {
138
        $information = new ArchiveInformation();
139
        $stream_path_length = strlen('phar://'.$this->archiveFileName.'/');
140
        $information->compressedFilesSize = filesize($this->archiveFileName);
0 ignored issues
show
Bug introduced by
It seems like $this->archiveFileName can also be of type boolean; however, parameter $filename of filesize() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

140
        $information->compressedFilesSize = filesize(/** @scrutinizer ignore-type */ $this->archiveFileName);
Loading history...
141
        /**
142
         * @var string $i
143
         * @var PharFileInfo $file
144
         */
145
        foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) {
146
            $information->files[] = substr($file->getPathname(), $stream_path_length);
147
            $information->uncompressedFilesSize += $file->getSize();
148
        }
149
        $this->compressRatio = $information->compressedFilesSize > 0
150
            ? $information->uncompressedFilesSize / $information->compressedFilesSize
151
            : 0;
152 3
        $this->pureFilesNumber = count($information->files);
153
        return $information;
154
    }
155 3
156 3
    /**
157 3
     * @inheritDoc
158
     */
159
    public function getFileNames()
160
    {
161
        $files = [];
162
163 4
        $stream_path_length = strlen('phar://'.$this->archiveFileName.'/');
164
        foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) {
165 4
            $files[] = substr($file->getPathname(), $stream_path_length);
166
        }
167
168
        return $files;
169
    }
170
171 3
    /**
172
     * @inheritDoc
173 3
     */
174
    public function isFileExists($fileName)
175
    {
176
        try {
177
            $this->tar->offsetGet($fileName);
178
            return true;
179
        } catch (Exception $e) {
180
            return false;
181
        }
182
    }
183
184
    /**
185
     * @inheritDoc
186
     */
187
    public function getFileData($fileName)
188
    {
189
        /** @var \PharFileInfo $entry_info */
190
        $entry_info = $this->tar->offsetGet($fileName);
191
        return new ArchiveEntry(
192
            $fileName,
193
            (
0 ignored issues
show
Bug introduced by
It seems like $this->compressRatio > 1...getCompressedSize() : 0 can also be of type double; however, parameter $compressedSize of wapmorgan\UnifiedArchive...iveEntry::__construct() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

193
            /** @scrutinizer ignore-type */ (
Loading history...
194
//                $entry_info->getCompressedSize() > $entry_info->getSize()
195
                $this->compressRatio > 1
196
                    ? floor($entry_info->getSize() / $this->compressRatio)
197
                    : (
198
                            $entry_info->getCompressedSize() > 0
199
                                ? $entry_info->getCompressedSize()
200
                                : 0
201
                    )
202
            ), //$entry_info->getCompressedSize(),
203
            $entry_info->getSize(),
204 1
            $entry_info->getMTime(),
205
            $entry_info->isCompressed());
206 1
    }
207
208 1
    /**
209 1
     * @inheritDoc
210 1
     */
211
    public function getFileContent($fileName)
212
    {
213 1
        return $this->tar->offsetGet($fileName)->getContent();
214 1
    }
215
216 1
    /**
217
     * @inheritDoc
218
     */
219
    public function getFileStream($fileName)
220
    {
221
        return fopen('phar://'.$this->archiveFileName . '/' . $fileName, 'rb');
222 1
    }
223
224 1
    /**
225
     * @inheritDoc
226 1
     */
227 1
    public function extractFiles($outputFolder, array $files)
228
    {
229
        $result = $this->tar->extractTo($outputFolder, $files, true);
230 1
        if ($result === false) {
231 1
            throw new ArchiveExtractionException('Error when extracting from '.$this->archiveFileName);
232
        }
233
        return count($files);
234
    }
235
236
    /**
237 1
     * @inheritDoc
238
     */
239 1
    public function extractArchive($outputFolder)
240 1
    {
241
        $result = $this->tar->extractTo($outputFolder, null, true);
242
        if ($result === false) {
243
            throw new ArchiveExtractionException('Error when extracting from '.$this->archiveFileName);
244
        }
245
246
        return $this->pureFilesNumber;
247 1
    }
248
249 1
    /**
250
     * @inheritDoc
251
     */
252
    public function deleteFiles(array $files)
253
    {
254
        $deleted = 0;
255
256 1
        foreach ($files as $i => $file) {
257
            if ($this->tar->delete($file))
258 1
                $deleted++;
259
        }
260
261
        $this->tar = null;
262
        $this->open();
263
264
        return $deleted;
265 1
    }
266
267 1
    /**
268
     * @inheritDoc
269
     */
270
    public function addFiles(array $files)
271
    {
272
        $added = 0;
273
        try {
274
            foreach ($files as $localName => $filename) {
275
                if (is_null($filename)) {
276
                    $this->tar->addEmptyDir($localName);
277
                } else {
278
                    $this->tar->addFile($filename, $localName);
279
                    $added++;
280 1
                }
281
            }
282 1
        } catch (Exception $e) {
283
            throw new ArchiveModificationException('Could not add file "'.$filename.'": '.$e->getMessage(), $e->getCode());
284
        }
285
        $this->tar = null;
286 1
        // reopen to refresh files list properly
287
        $this->open();
288
        return $added;
289
    }
290 1
291 1
    /**
292
     * @param array $files
293
     * @param string $archiveFileName
294 1
     * @param int $archiveFormat
295
     * @param int $compressionLevel
296 1
     * @param null $password
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $password is correct as it would always require null to be passed?
Loading history...
297 1
     * @param $fileProgressCallable
298
     * @return int
299
     * @throws ArchiveCreationException
300 1
     * @throws UnsupportedOperationException
301 1
     */
302
    public static function createArchive(
303
        array $files,
304
        $archiveFileName,
305
        $archiveFormat,
306 1
        $compressionLevel = self::COMPRESSION_AVERAGE,
307
        $password = null,
308
        $fileProgressCallable = null
309 1
    )
310
    {
311
        if ($password !== null) {
0 ignored issues
show
introduced by
The condition $password !== null is always false.
Loading history...
312
            throw new UnsupportedOperationException('One-file format ('.__CLASS__.') could not encrypt an archive');
313
        }
314
315 1
        if ($fileProgressCallable !== null && !is_callable($fileProgressCallable)) {
316 1
            throw new ArchiveCreationException('File progress callable is not callable');
317 1
        }
318
319
        if (preg_match('~^(.+)\.(tar\.(gz|bz2))$~i', $archiveFileName, $match)) {
320
            $ext = $match[2];
321 1
            $basename = $match[1];
322 1
        } else {
323 1
            $ext = pathinfo($archiveFileName, PATHINFO_EXTENSION);
324 1
            $basename = dirname($archiveFileName).'/'.basename($archiveFileName, '.'.$ext);
0 ignored issues
show
Bug introduced by
Are you sure $ext of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

324
            $basename = dirname($archiveFileName).'/'.basename($archiveFileName, '.'./** @scrutinizer ignore-type */ $ext);
Loading history...
325 1
        }
326
327
        $compression = null;
328
        switch ($ext) {
329 1
            case 'tar.gz':
330 1
            case 'tgz':
331
                $compression = Phar::GZ;
332
                break;
333
            case 'tar.bz2':
334
            case 'tbz2':
335
                $compression = Phar::BZ2;
336
                break;
337
        }
338
339 1
        $destination_file = $basename.'.tar';
340
        // if compression used and there is .tar archive with that's name,
341
        // use temp file
342 1
        if ($compression !== null && file_exists($basename.'.tar')) {
343
            $temp_basename = tempnam(sys_get_temp_dir(), 'tar-archive');
344
            unlink($temp_basename);
345
            $destination_file = $temp_basename.'.tar';
346 1
        }
347
348
        $tar = new PharData(
349 1
            $destination_file,
350
            0, null, Phar::TAR
351
        );
352
353
        try {
354 1
            $current_file = 0;
355
            $total_files = count($files);
356
357
            foreach ($files as $localName => $filename) {
358 1
                if (is_null($filename)) {
359
                    if (!in_array($localName, ['/', ''], true)) {
360
                        if ($tar->addEmptyDir($localName) === false) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of $tar->addEmptyDir($localName) targeting Phar::addEmptyDir() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
361
                            throw new ArchiveCreationException('Error when adding directory '.$localName.' to archive');
362
                        }
363
                    }
364
                } else {
365
                    if ($tar->addFile($filename, $localName) === false) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of $tar->addFile($filename, $localName) targeting Phar::addFile() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
366
                        throw new ArchiveCreationException('Error when adding file '.$localName.' to archive');
367
                    }
368
                }
369
                if ($fileProgressCallable !== null) {
370
                    call_user_func_array($fileProgressCallable, [$current_file++, $total_files, $filename, $localName]);
371
                }
372
            }
373
        } catch (Exception $e) {
374
            throw new ArchiveCreationException('Error when creating archive: '.$e->getMessage(), $e->getCode(), $e);
375
        }
376
377
        switch ($compression) {
378
            case Phar::GZ:
379
                $tar->compress(Phar::GZ, $ext);
380
                break;
381
            case Phar::BZ2:
382
                $tar->compress(Phar::BZ2, $ext);
383
                break;
384
        }
385
        $tar = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $tar is dead and can be removed.
Loading history...
386
387
        // if compression used and original .tar file exist, clean it
388
        if ($compression !== null && file_exists($destination_file)) {
389
            unlink($destination_file);
390
        }
391
392
        // it temp file was used, rename it to destination archive name
393
        if (isset($temp_basename)) {
394
            rename($temp_basename.'.'.$ext, $archiveFileName);
395
        }
396
397
        return count($files);
398
    }
399
400
    /**
401
     * @param string $inArchiveName
402
     * @param string $content
403
     * @return bool
404
     */
405
    public function addFileFromString($inArchiveName, $content)
406
    {
407
        $this->tar->addFromString($inArchiveName, $content);
408
        return true;
409
    }
410
}
411