Passed
Pull Request — master (#22)
by Théo
02:53
created

Box::getPhar()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
c 0
b 0
f 0
rs 10
cc 1
eloc 1
nc 1
nop 0
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
23
/**
24
 * Box is a utility class to generate a PHAR.
25
 */
26
final class Box
27
{
28
    /**
29
     * @var Compactor[]
30
     */
31
    private $compactors = [];
32
33
    /**
34
     * @var string The path to the PHAR file
35
     */
36
    private $file;
37
38
    /**
39
     * @var Phar The PHAR instance
40
     */
41
    private $phar;
42
43
    /**
44
     * @var scalar[] The placeholders with their values
45
     */
46
    private $placeholders = [];
47
48
    private function __construct(Phar $phar, string $file)
49
    {
50
        $this->phar = $phar;
51
        $this->file = $file;
52
    }
53
54
    /**
55
     * Creates a new PHAR and Box instance.
56
     *
57
     * @param string $file  The PHAR file name
58
     * @param int    $flags Flags to pass to the Phar parent class RecursiveDirectoryIterator
59
     * @param string $alias Alias with which the Phar archive should be referred to in calls to stream functionality
60
     *
61
     * @return Box
62
     *
63
     * @see RecursiveDirectoryIterator
64
     */
65
    public static function create(string $file, int $flags = null, string $alias = null): self
66
    {
67
        return new self(new Phar($file, (int) $flags, $alias), $file);
68
    }
69
70
    /**
71
     * @param Compactor[] $compactors
72
     */
73
    public function registerCompactors(array $compactors): void
74
    {
75
        Assertion::allIsInstanceOf($compactors, Compactor::class);
76
77
        $this->compactors = $compactors;
78
    }
79
80
    /**
81
     * @param scalar[] $placeholders
82
     */
83
    public function registerPlaceholders(array $placeholders): void
84
    {
85
        $message = 'Expected value "%s" to be a scalar or stringable object.';
86
87
        foreach ($placeholders as $i => $placeholder) {
88
            if (is_object($placeholder)) {
0 ignored issues
show
introduced by
The condition is_object($placeholder) can never be true.
Loading history...
89
                Assertion::methodExists('__toString', $placeholder, $message);
90
91
                $placeholders[$i] = (string) $placeholder;
92
93
                break;
94
            }
95
96
            Assertion::scalar($placeholder, $message);
97
        }
98
99
        $this->placeholders = $placeholders;
100
    }
101
102
    public function registerStub(string $file): void
103
    {
104
        Assertion::file($file);
105
        Assertion::readable($file);
106
107
        $contents = $this->replacePlaceholders(
108
            file_get_contents($file)
109
        );
110
111
        $this->phar->setStub($contents);
112
    }
113
114
    /**
115
     * Adds the a file to the PHAR. The contents will first be compacted and have its placeholders
116
     * replaced.
117
     *
118
     * @param string $file  The file name or path
119
     * @param string $local The local file name or path
120
     */
121
    public function addFile($file, $local = null): void
122
    {
123
        if (null === $local) {
124
            $local = $file;
125
        }
126
127
        Assertion::file($file);
128
        Assertion::readable($file);
129
130
        $contents = file_get_contents($file);
131
132
        $this->addFromString($local, $contents);
133
    }
134
135
    /**
136
     * Adds the contents from a file to the PHAR. The contents will first be compacted and have its placeholders
137
     * replaced.
138
     *
139
     * @param string $local    The local name or path
140
     * @param string $contents The contents
141
     */
142
    public function addFromString(string $local, string $contents): void
143
    {
144
        $this->phar->addFromString(
145
            $local,
146
            $this->compactContents(
147
                $local,
148
                $this->replacePlaceholders($contents)
149
            )
150
        );
151
    }
152
153
    public function getPhar(): Phar
154
    {
155
        return $this->phar;
156
    }
157
158
    /**
159
     * Signs the PHAR using a private key file.
160
     *
161
     * @param string $file     the private key file name
162
     * @param string $password the private key password
163
     */
164
    public function signUsingFile(string $file, string $password = null): void
165
    {
166
        Assertion::file($file);
167
        Assertion::readable($file);
168
169
        $this->sign(file_get_contents($file), $password);
170
    }
171
172
    /**
173
     * Signs the PHAR using a private key.
174
     *
175
     * @param string $key      The private key
176
     * @param string $password The private key password
177
     */
178
    public function sign(string $key, ?string $password): void
179
    {
180
        OpenSslExceptionFactory::reset();
181
182
        $pubKey = $this->file.'.pubkey';
183
184
        Assertion::extensionLoaded('openssl');
185
186
        if (false === ($resource = openssl_pkey_get_private($key, $password))) {
187
            throw OpenSslExceptionFactory::createForLastError();
188
        }
189
190
        if (false === openssl_pkey_export($resource, $private)) {
191
            throw OpenSslExceptionFactory::createForLastError();
192
        }
193
194
        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...
195
            throw OpenSslExceptionFactory::createForLastError();
196
        }
197
198
        $this->phar->setSignatureAlgorithm(Phar::OPENSSL, $private);
199
200
        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...
201
            throw FileExceptionFactory::createForLastError();
202
        }
203
    }
204
205
    /**
206
     * Replaces the placeholders with their values.
207
     *
208
     * @param string $contents the contents
209
     *
210
     * @return string the replaced contents
211
     */
212
    private function replacePlaceholders(string $contents): string
213
    {
214
        return str_replace(
215
            array_keys($this->placeholders),
216
            array_values($this->placeholders),
217
            $contents
218
        );
219
    }
220
221
    private function compactContents(string $file, string $contents): string
222
    {
223
        return array_reduce(
224
            $this->compactors,
225
            function (string $contents, Compactor $compactor) use ($file): string {
226
                return $compactor->compact($file, $contents);
227
            },
228
            $contents
229
        );
230
    }
231
}
232