Completed
Push — master ( 01074d...8c30ae )
by Théo
02:13
created

Box::addFiles()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 2
nop 2
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 SplFileInfo;
23
use Symfony\Component\Filesystem\Filesystem;
24
25
/**
26
 * Box is a utility class to generate a PHAR.
27
 */
28
final class Box
29
{
30
    /**
31
     * @var Compactor[]
32
     */
33
    private $compactors = [];
34
35
    /**
36
     * @var string The path to the PHAR file
37
     */
38
    private $file;
39
40
    /**
41
     * @var Phar The PHAR instance
42
     */
43
    private $phar;
44
45
    /**
46
     * @var scalar[] The placeholders with their values
47
     */
48
    private $placeholders = [];
49
50
    /**
51
     * @var RetrieveRelativeBasePath
52
     */
53
    private $retrieveRelativeBasePath;
54
55
    /**
56
     * @var MapFile
57
     */
58
    private $mapFile;
59
60
    private function __construct(Phar $phar, string $file)
61
    {
62
        $this->phar = $phar;
63
        $this->file = $file;
64
65
        $this->retrieveRelativeBasePath = function (string $path) { return $path; };
0 ignored issues
show
Documentation Bug introduced by
It seems like function(...) { /* ... */ } of type callable is incompatible with the declared type KevinGH\Box\RetrieveRelativeBasePath of property $retrieveRelativeBasePath.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
66
        $this->mapFile = function (): void { };
67
    }
68
69
    /**
70
     * Creates a new PHAR and Box instance.
71
     *
72
     * @param string $file  The PHAR file name
73
     * @param int    $flags Flags to pass to the Phar parent class RecursiveDirectoryIterator
74
     * @param string $alias Alias with which the Phar archive should be referred to in calls to stream functionality
75
     *
76
     * @return Box
77
     *
78
     * @see RecursiveDirectoryIterator
79
     */
80
    public static function create(string $file, int $flags = null, string $alias = null): self
81
    {
82
        // Ensure the parent directory of the PHAR file exists as `new \Phar()` does not create it and would fail
83
        // otherwise.
84
        (new Filesystem())->mkdir(dirname($file));
85
86
        return new self(new Phar($file, (int) $flags, $alias), $file);
87
    }
88
89
    /**
90
     * @param Compactor[] $compactors
91
     */
92
    public function registerCompactors(array $compactors): void
93
    {
94
        Assertion::allIsInstanceOf($compactors, Compactor::class);
95
96
        $this->compactors = $compactors;
97
    }
98
99
    /**
100
     * @param scalar[] $placeholders
101
     */
102
    public function registerPlaceholders(array $placeholders): void
103
    {
104
        $message = 'Expected value "%s" to be a scalar or stringable object.';
105
106
        foreach ($placeholders as $i => $placeholder) {
107
            if (is_object($placeholder)) {
108
                Assertion::methodExists('__toString', $placeholder, $message);
109
110
                $placeholders[$i] = (string) $placeholder;
111
112
                break;
113
            }
114
115
            Assertion::scalar($placeholder, $message);
116
        }
117
118
        $this->placeholders = $placeholders;
119
    }
120
121
    public function registerFileMapping(RetrieveRelativeBasePath $retrieveRelativeBasePath, MapFile $fileMapper): void
122
    {
123
        $this->retrieveRelativeBasePath = $retrieveRelativeBasePath;
124
        $this->mapFile = $fileMapper;
125
    }
126
127
    public function registerStub(string $file): void
128
    {
129
        Assertion::file($file);
130
        Assertion::readable($file);
131
132
        $contents = $this->replacePlaceholders(
133
            file_get_contents($file)
134
        );
135
136
        $this->phar->setStub($contents);
137
    }
138
139
    /**
140
     * @param SplFileInfo[]|string[] $files
141
     * @param bool                   $binary
142
     */
143
    public function addFiles(array $files, bool $binary): void
144
    {
145
        foreach ($files as $file) {
146
            $this->addFile((string) $file, null, $binary);
147
        }
148
    }
149
150
    /**
151
     * Adds the a file to the PHAR. The contents will first be compacted and have its placeholders
152
     * replaced.
153
     *
154
     * @param string      $file
155
     * @param null|string $contents If null the content of the file will be used
156
     * @param bool        $binary   When true means the file content shouldn't be processed
157
     *
158
     * @return string File local path
159
     */
160
    public function addFile(string $file, string $contents = null, bool $binary = false): string
161
    {
162
        Assertion::file($file);
163
        Assertion::readable($file);
164
165
        $contents = null === $contents ? file_get_contents($file) : $contents;
166
167
        $relativePath = ($this->retrieveRelativeBasePath)($file);
168
        $local = ($this->mapFile)($relativePath);
169
170
        if (null === $local) {
171
            $local = $relativePath;
172
        }
173
174
        if ($binary) {
175
            $this->phar->addFile($file, $local);
176
        } else {
177
            $processedContents = $this->compactContents(
178
                $local,
179
                $this->replacePlaceholders($contents)
180
            );
181
182
            $this->phar->addFromString($local, $processedContents);
183
        }
184
185
        return $local;
186
    }
187
188
    public function getPhar(): Phar
189
    {
190
        return $this->phar;
191
    }
192
193
    /**
194
     * Signs the PHAR using a private key file.
195
     *
196
     * @param string $file     the private key file name
197
     * @param string $password the private key password
198
     */
199
    public function signUsingFile(string $file, string $password = null): void
200
    {
201
        Assertion::file($file);
202
        Assertion::readable($file);
203
204
        $this->sign(file_get_contents($file), $password);
205
    }
206
207
    /**
208
     * Signs the PHAR using a private key.
209
     *
210
     * @param string $key      The private key
211
     * @param string $password The private key password
212
     */
213
    public function sign(string $key, ?string $password): void
214
    {
215
        OpenSslExceptionFactory::reset();
216
217
        $pubKey = $this->file.'.pubkey';
218
219
        Assertion::extensionLoaded('openssl');
220
221
        if (false === ($resource = openssl_pkey_get_private($key, $password))) {
222
            throw OpenSslExceptionFactory::createForLastError();
223
        }
224
225
        if (false === openssl_pkey_export($resource, $private)) {
226
            throw OpenSslExceptionFactory::createForLastError();
227
        }
228
229
        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...
230
            throw OpenSslExceptionFactory::createForLastError();
231
        }
232
233
        $this->phar->setSignatureAlgorithm(Phar::OPENSSL, $private);
234
235
        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...
236
            throw FileExceptionFactory::createForLastError();
237
        }
238
    }
239
240
    /**
241
     * Replaces the placeholders with their values.
242
     *
243
     * @param string $contents the contents
244
     *
245
     * @return string the replaced contents
246
     */
247
    private function replacePlaceholders(string $contents): string
248
    {
249
        return str_replace(
250
            array_keys($this->placeholders),
251
            array_values($this->placeholders),
252
            $contents
253
        );
254
    }
255
256
    private function compactContents(string $file, string $contents): string
257
    {
258
        return array_reduce(
259
            $this->compactors,
260
            function (string $contents, Compactor $compactor) use ($file): string {
261
                return $compactor->compact($file, $contents);
262
            },
263
            $contents
264
        );
265
    }
266
}
267