Completed
Pull Request — master (#32)
by Théo
02:37
created

Box::addFiles()   B

Complexity

Conditions 3
Paths 1

Size

Total Lines 84
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 84
rs 8.7169
c 0
b 0
f 0
cc 3
eloc 46
nc 1
nop 1

How to fix   Long Method   

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
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;
16
17
use Assert\Assertion;
18
use KevinGH\Box\Exception\FileExceptionFactory;
19
use KevinGH\Box\Exception\OpenSslExceptionFactory;
20
use Phar;
21
use RecursiveDirectoryIterator;
22
use Symfony\Component\Filesystem\Filesystem;
23
use function Amp\ParallelFunctions\parallelMap;
24
use function Amp\Promise\wait;
25
26
/**
27
 * Box is a utility class to generate a PHAR.
28
 */
29
final class Box
30
{
31
    /**
32
     * @var Compactor[]
33
     */
34
    private $compactors = [];
35
36
    /**
37
     * @var string The path to the PHAR file
38
     */
39
    private $file;
40
41
    /**
42
     * @var Phar The PHAR instance
43
     */
44
    private $phar;
45
46
    /**
47
     * @internal For serialization purpose only
48
     */
49
    private $flags;
50
51
    /**
52
     * @internal For serialization purpose only
53
     */
54
    private $alias;
55
56
    /**
57
     * @var scalar[] The placeholders with their values
58
     */
59
    private $placeholders = [];
60
61
    /**
62
     * @var callable
63
     */
64
    private $retrieveBasePath;
65
66
    /**
67
     * @var callable
68
     */
69
    private $mapFile;
70
71
    private function __construct(string $file, int $flags, ?string $alias)
72
    {
73
        $this->file = $file;
74
        $this->flags = $flags;
75
        $this->alias = $alias;
76
77
        $this->phar = new Phar($file, $flags, $alias);
78
    }
79
80
    /**
81
     * Creates a new PHAR and Box instance.
82
     *
83
     * @param string $file  The PHAR file name
84
     * @param int    $flags Flags to pass to the Phar parent class RecursiveDirectoryIterator
85
     * @param string $alias Alias with which the Phar archive should be referred to in calls to stream functionality
86
     *
87
     * @return Box
88
     *
89
     * @see RecursiveDirectoryIterator
90
     */
91
    public static function create(string $file, int $flags = null, string $alias = null): self
92
    {
93
        // Ensure the parent directory of the PHAR file exists as `new \Phar()` does not create it and would fail
94
        // otherwise.
95
        (new Filesystem())->mkdir(dirname($file));
96
97
        return new self($file, (int) $flags, $alias);
98
    }
99
100
    /**
101
     * @param Compactor[] $compactors
102
     */
103
    public function registerCompactors(array $compactors): void
104
    {
105
        Assertion::allIsInstanceOf($compactors, Compactor::class);
106
107
        $this->compactors = $compactors;
108
    }
109
110
    /**
111
     * @param scalar[] $placeholders
112
     */
113
    public function registerPlaceholders(array $placeholders): void
114
    {
115
        $message = 'Expected value "%s" to be a scalar or stringable object.';
116
117
        foreach ($placeholders as $i => $placeholder) {
118
            if (is_object($placeholder)) {
0 ignored issues
show
introduced by
The condition is_object($placeholder) can never be true.
Loading history...
119
                Assertion::methodExists('__toString', $placeholder, $message);
120
121
                $placeholders[$i] = (string) $placeholder;
122
123
                break;
124
            }
125
126
            Assertion::scalar($placeholder, $message);
127
        }
128
129
        $this->placeholders = $placeholders;
130
    }
131
132
    public function registerStub(string $file): void
133
    {
134
        Assertion::file($file);
135
        Assertion::readable($file);
136
137
        $contents = $this->replacePlaceholders(
138
            file_get_contents($file)
139
        );
140
141
        $this->phar->setStub($contents);
142
    }
143
144
    public function registerFileMapping(callable $retrieveBasePath, callable $mapFile): void
145
    {
146
        $this->retrieveBasePath = $retrieveBasePath;
147
        $this->mapFile = $mapFile;
148
    }
149
150
    /**
151
     * @param iterable|SplFileInfo[] $files
152
     */
153
    public function addFiles(iterable $files): void
154
    {
155
        $retrieveBasePath = $this->retrieveBasePath;
156
        $mapFile = $this->mapFile;
157
        $compactors = $this->compactors;
158
        $placeholders = $this->placeholders;
159
160
        //Debug: the values passed to the $processFile closure seems to be working fine
161
        $x0 = \serialize($retrieveBasePath);
162
        $x1 = \serialize($mapFile);
163
        $x3 = \serialize($placeholders);
164
        $x4 = \serialize($compactors);
165
166
        $y0 = \unserialize($x0, []);
0 ignored issues
show
Unused Code introduced by
The assignment to $y0 is dead and can be removed.
Loading history...
167
        $y1 = \unserialize($x1, []);
0 ignored issues
show
Unused Code introduced by
The assignment to $y1 is dead and can be removed.
Loading history...
168
        $y3 = \unserialize($x3, []);
0 ignored issues
show
Unused Code introduced by
The assignment to $y3 is dead and can be removed.
Loading history...
169
        $y4 = \unserialize($x4, []);
0 ignored issues
show
Unused Code introduced by
The assignment to $y4 is dead and can be removed.
Loading history...
170
171
        $files = array_map(
172
            function (\SplFileInfo $fileInfo): string {
173
                return $fileInfo->getPathname();
174
            },
175
            iterator_to_array($files)
176
        );
177
178
        $processFile = function (string $filePath) use ($retrieveBasePath, $mapFile, $placeholders, $compactors) {
179
            Assertion::file($filePath);
180
            Assertion::readable($filePath);
181
182
            // Resolve paths
183
            $relativePath = $retrieveBasePath($filePath);
184
            $mappedPath = $mapFile($relativePath);
185
186
            if (null !== $mappedPath) {
187
                $relativePath = $mappedPath;
188
            }
189
190
            if (null === $relativePath) {
191
                $relativePath = $filePath;
192
            }
193
194
            // Process content
195
            $contents = file_get_contents($filePath);
196
197
            $contents = str_replace(
198
                array_keys($placeholders),
199
                array_values($placeholders),
200
                $contents
201
            );
202
203
            $contents = array_reduce(
204
                $compactors,
205
                function (string $contents, Compactor $compactor) use ($relativePath): string {
206
                    return $compactor->compact($relativePath, $contents);
207
                },
208
                $contents
209
            );
210
211
            return [$relativePath, $contents];
212
        };
213
214
        // Regular processing
215
//        $fileAndContents = array_map(
216
//            $processFile,
217
//            $files
218
//        );
219
220
        // Parallel processing
221
        $fileAndContents = wait(
222
            parallelMap(
223
                $files,
224
                $processFile
225
            )
226
        );
227
228
        $phar = $this->phar;
229
230
        array_map(
231
            function (array $tuple) use ($phar): void {
232
                list($file, $contents) = $tuple;
233
234
                $phar->addFromString($file, $contents);
235
            },
236
            $fileAndContents
237
        );
238
    }
239
240
    /**
241
     * Adds the a file to the PHAR. The contents will first be compacted and have its placeholders
242
     * replaced.
243
     *
244
     * @param string $file
245
     */
246
    public function addFile($file): void
247
    {
248
        $relativePath = $this->{retrieveBasePath}($file);
0 ignored issues
show
Bug introduced by
The constant KevinGH\Box\retrieveBasePath was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
249
        $mappedPath = $this->{mapFile}($relativePath);
0 ignored issues
show
Bug introduced by
The constant KevinGH\Box\mapFile was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
250
251
        if (null !== $mappedPath) {
252
            $local = $mappedPath;
253
        }
254
255
        Assertion::file($file);
256
        Assertion::readable($file);
257
258
        $contents = file_get_contents($file);
259
260
        $this->addFromString($local, $contents);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $local does not seem to be defined for all execution paths leading up to this point.
Loading history...
261
    }
262
263
    /**
264
     * Adds the contents from a file to the PHAR. The contents will first be compacted and have its placeholders
265
     * replaced.
266
     *
267
     * @param string $local    The local name or path
268
     * @param string $contents The contents
269
     */
270
    public function addFromString(string $local, string $contents): void
271
    {
272
        $this->phar->addFromString(
273
            $local,
274
            $this->compactContents(
275
                $local,
276
                $this->replacePlaceholders($contents)
277
            )
278
        );
279
    }
280
281
    public function getPhar(): Phar
282
    {
283
        return $this->phar;
284
    }
285
286
    /**
287
     * Signs the PHAR using a private key file.
288
     *
289
     * @param string $file     the private key file name
290
     * @param string $password the private key password
291
     */
292
    public function signUsingFile(string $file, string $password = null): void
293
    {
294
        Assertion::file($file);
295
        Assertion::readable($file);
296
297
        $this->sign(file_get_contents($file), $password);
298
    }
299
300
    /**
301
     * Signs the PHAR using a private key.
302
     *
303
     * @param string $key      The private key
304
     * @param string $password The private key password
305
     */
306
    public function sign(string $key, ?string $password): void
307
    {
308
        OpenSslExceptionFactory::reset();
309
310
        $pubKey = $this->file.'.pubkey';
311
312
        Assertion::extensionLoaded('openssl');
313
314
        if (false === ($resource = openssl_pkey_get_private($key, $password))) {
315
            throw OpenSslExceptionFactory::createForLastError();
316
        }
317
318
        if (false === openssl_pkey_export($resource, $private)) {
319
            throw OpenSslExceptionFactory::createForLastError();
320
        }
321
322
        if (false === ($details = openssl_pkey_get_details($resource))) {
0 ignored issues
show
introduced by
The condition false === $details = ope..._get_details($resource) can never be true.
Loading history...
323
            throw OpenSslExceptionFactory::createForLastError();
324
        }
325
326
        $this->phar->setSignatureAlgorithm(Phar::OPENSSL, $private);
327
328
        if (false === @file_put_contents($pubKey, $details['key'])) {
0 ignored issues
show
introduced by
The condition false === @file_put_cont...ubKey, $details['key']) can never be true.
Loading history...
329
            throw FileExceptionFactory::createForLastError();
330
        }
331
    }
332
333
    /**
334
     * Replaces the placeholders with their values.
335
     *
336
     * @param string $contents the contents
337
     *
338
     * @return string the replaced contents
339
     */
340
    private function replacePlaceholders(string $contents): string
341
    {
342
        return str_replace(
343
            array_keys($this->placeholders),
344
            array_values($this->placeholders),
345
            $contents
346
        );
347
    }
348
349
    private function compactContents(string $file, string $contents): string
350
    {
351
        return array_reduce(
352
            $this->compactors,
353
            function (string $contents, Compactor $compactor) use ($file): string {
354
                return $compactor->compact($file, $contents);
355
            },
356
            $contents
357
        );
358
    }
359
}
360