Passed
Push — master ( b1d4f6...7824cd )
by f
10:25
created

TarByPhar   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 376
Duplicated Lines 0 %

Test Coverage

Coverage 61.76%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 153
c 4
b 0
f 0
dl 0
loc 376
ccs 84
cts 136
cp 0.6176
rs 3.52
wmc 61

18 Methods

Rating   Name   Duplication   Size   Complexity  
F createArchive() 0 96 23
A addFileFromString() 0 4 1
A getFileStream() 0 3 1
A extractArchive() 0 8 2
A open() 0 3 1
A getFileData() 0 10 2
A getDescription() 0 3 1
A getFileNames() 0 10 2
A getInstallationInstruction() 0 5 2
A deleteFiles() 0 13 3
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
    public static $disabled = false;
23
24
    /**
25
     * @var false|string
26
     */
27
    protected $archiveFileName;
28
29
    /**
30
     * @var PharData
31
     */
32
    protected $tar;
33
34
    /**
35
     * @var float
36
     */
37
    protected $compressRatio;
38
39 1
    protected $pureFilesNumber;
40
41
    /**
42 1
     * @var int Flags for iterator
43
     */
44
    const PHAR_FLAGS = FilesystemIterator::UNIX_PATHS;
45
46
    /**
47
     * @return array
48
     */
49
    public static function getSupportedFormats()
50
    {
51
        return [
52
            Formats::TAR,
53 3
            Formats::TAR_GZIP,
54
            Formats::TAR_BZIP,
55 3
            Formats::ZIP,
56
        ];
57 3
    }
58
59 1
    /**
60 2
     * @param $format
61 1
     * @return array
62 1
     */
63 1
    public static function checkFormatSupport($format)
64
    {
65
        if (static::$disabled || !class_exists('\PharData')) {
66
            return [];
67
        }
68
69
        $abilities = [
70
            BasicDriver::OPEN,
71
            BasicDriver::EXTRACT_CONTENT,
72
            BasicDriver::STREAM_CONTENT,
73
            BasicDriver::APPEND,
74
            BasicDriver::DELETE,
75
            BasicDriver::CREATE,
76
        ];
77
78
        switch ($format) {
79
            case Formats::TAR:
80
            case Formats::ZIP:
81
                return $abilities;
82
83
            case Formats::TAR_GZIP:
84
                return extension_loaded('zlib')
85
                    ? $abilities
86
                    : [];
87
            case Formats::TAR_BZIP:
88 10
                return extension_loaded('bz2')
89
                    ? $abilities
90 10
                    : [];
91 10
        }
92 10
    }
93
94
    /**
95
     * @inheritDoc
96
     */
97 10
    public static function getDescription()
98
    {
99 10
        return 'adapter for ext-phar';
100 10
    }
101
102
    /**
103
     * @inheritDoc
104
     */
105 10
    public static function getInstallationInstruction()
106
    {
107 10
        return !extension_loaded('phar')
108 10
            ? 'install `phar` extension and optionally php-extensions (zlib, bzip2)'
109
            : null;
110
    }
111
112
    /**
113 10
     * @inheritDoc
114 10
     */
115 10
    public function __construct($archiveFileName, $format, $password = null)
116 10
    {
117
        $this->archiveFileName = realpath($archiveFileName);
118 10
        $this->open();
119
    }
120
121
    /**
122
     *
123
     */
124
    protected function open()
125
    {
126
        $this->tar = new PharData($this->archiveFileName, self::PHAR_FLAGS);
127
    }
128
129
    /**
130
     * @inheritDoc
131
     */
132
    public function getArchiveInformation()
133
    {
134
        $information = new ArchiveInformation();
135
        $stream_path_length = strlen('phar://'.$this->archiveFileName.'/');
136
        $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

136
        $information->compressedFilesSize = filesize(/** @scrutinizer ignore-type */ $this->archiveFileName);
Loading history...
137
        /**
138
         * @var string $i
139
         * @var PharFileInfo $file
140
         */
141
        foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) {
142
            $information->files[] = substr($file->getPathname(), $stream_path_length);
143
            $information->uncompressedFilesSize += $file->getSize();
144
        }
145
        $this->compressRatio = $information->compressedFilesSize > 0
146
            ? $information->uncompressedFilesSize / $information->compressedFilesSize
147
            : 0;
148
        $this->pureFilesNumber = count($information->files);
149
        return $information;
150
    }
151
152 3
    /**
153
     * @inheritDoc
154
     */
155 3
    public function getFileNames()
156 3
    {
157 3
        $files = [];
158
159
        $stream_path_length = strlen('phar://'.$this->archiveFileName.'/');
160
        foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) {
161
            $files[] = substr($file->getPathname(), $stream_path_length);
162
        }
163 4
164
        return $files;
165 4
    }
166
167
    /**
168
     * @inheritDoc
169
     */
170
    public function isFileExists($fileName)
171 3
    {
172
        try {
173 3
            $this->tar->offsetGet($fileName);
174
            return true;
175
        } catch (Exception $e) {
176
            return false;
177
        }
178
    }
179
180
    /**
181
     * @inheritDoc
182
     */
183
    public function getFileData($fileName)
184
    {
185
        /** @var \PharFileInfo $entry_info */
186
        $entry_info = $this->tar->offsetGet($fileName);
187
        return new ArchiveEntry(
188
            $fileName,
189
            ($this->compressRatio > 0 ? floor($entry_info->getSize() / $this->compressRatio) : 0), //$entry_info->getCompressedSize(),
0 ignored issues
show
Bug introduced by
It seems like $this->compressRatio > 0...his->compressRatio) : 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

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

311
            $basename = dirname($archiveFileName).'/'.basename($archiveFileName, '.'./** @scrutinizer ignore-type */ $ext);
Loading history...
312
        }
313
314
        $compression = null;
315 1
        switch ($ext) {
316 1
            case 'tar.gz':
317 1
            case 'tgz':
318
                $compression = Phar::GZ;
319
                break;
320
            case 'tar.bz2':
321 1
            case 'tbz2':
322 1
                $compression = Phar::BZ2;
323 1
                break;
324 1
        }
325 1
326
        $destination_file = $basename.'.tar';
327
        // if compression used and there is .tar archive with that's name,
328
        // use temp file
329 1
        if ($compression !== null && file_exists($basename.'.tar')) {
330 1
            $temp_basename = tempnam(sys_get_temp_dir(), 'tar-archive');
331
            unlink($temp_basename);
332
            $destination_file = $temp_basename.'.tar';
333
        }
334
335
        $tar = new PharData(
336
            $destination_file,
337
            0, null, Phar::TAR
338
        );
339 1
340
        try {
341
            $current_file = 0;
342 1
            $total_files = count($files);
343
344
            foreach ($files as $localName => $filename) {
345
                if (is_null($filename)) {
346 1
                    if (!in_array($localName, ['/', ''], true)) {
347
                        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...
348
                            throw new ArchiveCreationException('Error when adding directory '.$localName.' to archive');
349 1
                        }
350
                    }
351
                } else {
352
                    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...
353
                        throw new ArchiveCreationException('Error when adding file '.$localName.' to archive');
354 1
                    }
355
                }
356
                if ($fileProgressCallable !== null) {
357
                    call_user_func_array($fileProgressCallable, [$current_file++, $total_files, $filename, $localName]);
358 1
                }
359
            }
360
        } catch (Exception $e) {
361
            throw new ArchiveCreationException('Error when creating archive: '.$e->getMessage(), $e->getCode(), $e);
362
        }
363
364
        switch ($compression) {
365
            case Phar::GZ:
366
                $tar->compress(Phar::GZ, $ext);
367
                break;
368
            case Phar::BZ2:
369
                $tar->compress(Phar::BZ2, $ext);
370
                break;
371
        }
372
        $tar = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $tar is dead and can be removed.
Loading history...
373
374
        // if compression used and original .tar file exist, clean it
375
        if ($compression !== null && file_exists($destination_file)) {
376
            unlink($destination_file);
377
        }
378
379
        // it temp file was used, rename it to destination archive name
380
        if (isset($temp_basename)) {
381
            rename($temp_basename.'.'.$ext, $archiveFileName);
382
        }
383
384
        return count($files);
385
    }
386
387
    /**
388
     * @param string $inArchiveName
389
     * @param string $content
390
     * @return bool
391
     */
392
    public function addFileFromString($inArchiveName, $content)
393
    {
394
        $this->tar->addFromString($inArchiveName, $content);
395
        return true;
396
    }
397
}
398