TarByPhar::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 3
dl 0
loc 4
ccs 1
cts 1
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 wapmorgan\UnifiedArchive\Abilities;
11
use wapmorgan\UnifiedArchive\ArchiveEntry;
12
use wapmorgan\UnifiedArchive\ArchiveInformation;
13
use wapmorgan\UnifiedArchive\Drivers\Basic\BasicExtensionDriver;
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 BasicExtensionDriver
21
{
22
    const EXTENSION_NAME = 'phar';
23
24
    public static $disabled = false;
25
26
    /**
27
     * @var PharData
28
     */
29
    protected $tar;
30
31
    /**
32
     * @var float
33
     */
34
    protected $compressRatio;
35
36
    protected $pureFilesNumber;
37
38
    /**
39 1
     * @var int Flags for iterator
40
     */
41
    const PHAR_FLAGS = FilesystemIterator::UNIX_PATHS;
42 1
43
    /**
44
     * @inheritDoc
45
     */
46
    public static function getInstallationInstruction()
47
    {
48
        return 'install `phar` extension and optionally php-extensions (zlib, bz2)';
49
    }
50
51
    /**
52
     * @inheritDoc
53 3
     */
54
    public static function getDescription()
55 3
    {
56
        return 'adapter for ext-phar';
57 3
    }
58
59 1
    /**
60 2
     * @return array
61 1
     */
62 1
    public static function getFormats()
63 1
    {
64
        return [
65
            Formats::TAR,
66
            Formats::TAR_GZIP,
67
            Formats::TAR_BZIP,
68
            Formats::ZIP,
69
        ];
70
    }
71
72
    /**
73
     * @param $format
74
     * @return array
75
     */
76
    public static function getFormatAbilities($format)
77
    {
78
        if (static::$disabled || !static::isInstalled()) {
79
            return [];
80
        }
81
82
        $abilities = [
83
            Abilities::OPEN,
84
            Abilities::EXTRACT_CONTENT,
85
            Abilities::STREAM_CONTENT,
86
            Abilities::APPEND,
87
            Abilities::DELETE,
88 10
            Abilities::CREATE,
89
        ];
90 10
91 10
        switch ($format) {
92 10
            case Formats::TAR:
93
            case Formats::ZIP:
94
                return $abilities;
95
96
            case Formats::TAR_GZIP:
97 10
                return extension_loaded('zlib')
98
                    ? $abilities
99 10
                    : [];
100 10
            case Formats::TAR_BZIP:
101
                return extension_loaded('bz2')
102
                    ? $abilities
103
                    : [];
104
        }
105 10
    }
106
107 10
    /**
108 10
     * @inheritDoc
109
     */
110
    public function __construct($archiveFileName, $format, $password = null)
111
    {
112
        parent::__construct($archiveFileName, $format);
113 10
        $this->open();
114 10
    }
115 10
116 10
    /**
117
     *
118 10
     */
119
    protected function open()
120
    {
121
        $this->tar = new PharData($this->fileName, self::PHAR_FLAGS);
122
    }
123
124
    /**
125
     * @inheritDoc
126
     */
127
    public function getArchiveInformation()
128
    {
129
        $information = new ArchiveInformation();
130
        $stream_path_length = strlen('phar://'.$this->fileName.'/');
131
        $information->compressedFilesSize = filesize($this->fileName);
132
        /**
133
         * @var string $i
134
         * @var PharFileInfo $file
135
         */
136
        foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) {
137
            $information->files[] = substr($file->getPathname(), $stream_path_length);
138
            $information->uncompressedFilesSize += $file->getSize();
139
        }
140
        $this->compressRatio = $information->compressedFilesSize > 0
141
            ? $information->uncompressedFilesSize / $information->compressedFilesSize
142
            : 0;
143
        $this->pureFilesNumber = count($information->files);
144
        return $information;
145
    }
146
147
    /**
148
     * @inheritDoc
149
     */
150
    public function getFileNames()
151
    {
152 3
        $files = [];
153
154
        $stream_path_length = strlen('phar://'.$this->fileName.'/');
155 3
        foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) {
156 3
            $files[] = substr($file->getPathname(), $stream_path_length);
157 3
        }
158
159
        return $files;
160
    }
161
162
    /**
163 4
     * @inheritDoc
164
     */
165 4
    public function isFileExists($fileName)
166
    {
167
        try {
168
            $this->tar->offsetGet($fileName);
169
            return true;
170
        } catch (Exception $e) {
171 3
            return false;
172
        }
173 3
    }
174
175
    /**
176
     * @inheritDoc
177
     */
178
    public function getFileData($fileName)
179
    {
180
        /** @var \PharFileInfo $entry_info */
181
        $entry_info = $this->tar->offsetGet($fileName);
182
        return new ArchiveEntry(
183
            $fileName,
184
            (
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

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

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