Issues (224)

src/Phar/PharMeta.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the box project.
7
 *
8
 * (c) Kevin Herrera <[email protected]>
9
 *     Théo Fidry <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace KevinGH\Box\Phar;
16
17
use JetBrains\PhpStorm\ArrayShape;
18
use Phar;
19
use PharData;
20
use PharFileInfo;
21
use RecursiveDirectoryIterator;
22
use SplFileInfo;
23
use Symfony\Component\Filesystem\Path;
24
use UnexpectedValueException;
25
use function ksort;
26
use function Safe\json_decode;
27
use function Safe\json_encode;
28
use function Safe\realpath;
29
use function sprintf;
30
use function var_export;
31
use const SORT_LOCALE_STRING;
32
33
/**
34
 * Represents the PHAR metadata (partially). The goal is to capture enough information to interpret a PHAR
35
 * without instantiating a Phar or PharData instance.
36
 *
37
 * @private
38
 */
39
final readonly class PharMeta
0 ignored issues
show
A parse error occurred: Syntax error, unexpected T_READONLY, expecting T_CLASS on line 39 at column 6
Loading history...
40
{
41
    /**
42
     * @param non-empty-string|null                                                          $stub
43
     * @param non-empty-string|null                                                          $version
44
     * @param non-empty-string|null                                                          $normalizedMetadata
45
     * @param non-empty-string|null                                                          $pubKeyContent
46
     * @param array<string, array{'compression': CompressionAlgorithm, compressedSize: int}> $filesMeta
47
     */
48
    public function __construct(
49
        public CompressionAlgorithm $compression,
50
        #[ArrayShape(['hash' => 'string', 'hash_type' => 'string'])]
51
        public ?array $signature,
52
        public ?string $stub,
53
        public ?string $version,
54
        public ?string $normalizedMetadata,
55
        public int $timestamp,
56
        public ?string $pubKeyContent,
57
        public array $filesMeta,
58
    ) {
59
    }
60
61
    public static function fromPhar(Phar|PharData $phar, ?string $pubKeyContent): self
62
    {
63
        $compression = $phar->isCompressed();
64
        $signature = $phar->getSignature();
65
        $stub = $phar->getStub();
66
        $version = $phar->getVersion();
67
        $metadata = $phar->getMetadata();
68
        $timestamp = $phar->getMTime();
69
70
        return new self(
71
            false === $compression ? CompressionAlgorithm::NONE : CompressionAlgorithm::from($compression),
72
            false === $signature ? null : $signature,
73
            '' === $stub ? null : $stub,
74
            '' === $version ? null : $version,
75
            // TODO: check $unserializeOptions here
76
            null === $metadata ? null : var_export($metadata, true),
77
            $timestamp,
78
            $pubKeyContent,
79
            self::collectFilesMeta($phar),
80
        );
81
    }
82
83
    public static function fromJson(string $json): self
84
    {
85
        $decodedJson = json_decode($json, true);
86
87
        $filesMeta = $decodedJson['filesMeta'];
88
89
        foreach ($filesMeta as &$fileMeta) {
90
            $fileMeta['compression'] = CompressionAlgorithm::from($fileMeta['compression']);
91
        }
92
93
        return new self(
94
            CompressionAlgorithm::from($decodedJson['compression']),
95
            $decodedJson['signature'],
96
            $decodedJson['stub'],
97
            $decodedJson['version'],
98
            $decodedJson['normalizedMetadata'],
99
            $decodedJson['timestamp'],
100
            $decodedJson['pubKeyContent'],
101
            $filesMeta,
102
        );
103
    }
104
105
    public function toJson(): string
106
    {
107
        return json_encode([
108
            'compression' => $this->compression,
109
            'signature' => $this->signature,
110
            'stub' => $this->stub,
111
            'version' => $this->version,
112
            'normalizedMetadata' => $this->normalizedMetadata,
113
            'timestamp' => $this->timestamp,
114
            'pubKeyContent' => $this->pubKeyContent,
115
            'filesMeta' => $this->filesMeta,
116
        ]);
117
    }
118
119
    /**
120
     * @return array<string, array{'compression': CompressionAlgorithm, compressedSize: int}>
121
     */
122
    private static function collectFilesMeta(Phar|PharData $phar): array
123
    {
124
        $filesMeta = [];
125
126
        $root = self::getPharRoot($phar);
127
128
        self::traverseSource(
129
            $root,
130
            $phar,
131
            $filesMeta,
132
        );
133
134
        ksort($filesMeta, SORT_LOCALE_STRING);
135
136
        return $filesMeta;
137
    }
138
139
    /**
140
     * @param iterable<string, SplFileInfo|PharFileInfo> $source
141
     *
142
     * @return array<string, array{'compression': CompressionAlgorithm, compressedSize: int}>
143
     */
144
    private static function traverseSource(
145
        string $root,
146
        iterable $source,
147
        array &$filesMeta,
148
    ): void {
149
        foreach ($source as $path => $pharFileInfo) {
150
            if (!($pharFileInfo instanceof PharFileInfo)) {
151
                $pharFileInfo = new PharFileInfo($path);
152
            }
153
154
            if ($pharFileInfo->isDir()) {
155
                self::traverseSource(
156
                    $root,
157
                    new RecursiveDirectoryIterator($pharFileInfo->getPathname()),
158
                    $filesMeta,
159
                );
160
161
                continue;
162
            }
163
164
            $relativePath = Path::makeRelative($path, $root);
165
166
            $filesMeta[$relativePath] = [
167
                'compression' => self::getCompressionAlgorithm($pharFileInfo),
168
                'compressedSize' => $pharFileInfo->getCompressedSize(),
169
            ];
170
        }
171
    }
172
173
    private static function getPharRoot(Phar|PharData $phar): string
174
    {
175
        return 'phar://'.Path::normalize(realpath($phar->getPath()));
176
    }
177
178
    private static function getCompressionAlgorithm(PharFileInfo $pharFileInfo): CompressionAlgorithm
179
    {
180
        if (false === $pharFileInfo->isCompressed()) {
181
            return CompressionAlgorithm::NONE;
182
        }
183
184
        foreach (CompressionAlgorithm::cases() as $compressionAlgorithm) {
185
            if (CompressionAlgorithm::NONE !== $compressionAlgorithm
186
                && $pharFileInfo->isCompressed($compressionAlgorithm->value)
187
            ) {
188
                return $compressionAlgorithm;
189
            }
190
        }
191
192
        throw new UnexpectedValueException(
193
            sprintf(
194
                'Unknown compression algorithm for the file "%s',
195
                $pharFileInfo->getPath(),
196
            ),
197
        );
198
    }
199
}
200