Completed
Push — master ( bc7d7f...7a9596 )
by Théo
02:46
created

Box::buildFromIterator()   C

Complexity

Conditions 10
Paths 18

Size

Total Lines 51
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 51
c 0
b 0
f 0
rs 6
cc 10
eloc 32
nc 18
nop 2

How to fix   Long Method    Complexity   

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 FilesystemIterator;
18
use KevinGH\Box\Compactor\Compactor;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, KevinGH\Box\Compactor. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
19
use KevinGH\Box\Compactor\CompactorInterface;
0 ignored issues
show
Bug introduced by
The type KevinGH\Box\Compactor\CompactorInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
20
use KevinGH\Box\Exception\FileException;
21
use KevinGH\Box\Exception\InvalidArgumentException;
22
use KevinGH\Box\Exception\OpenSslException;
23
use KevinGH\Box\Exception\UnexpectedValueException;
24
use Phar;
25
use Phine\Path\Path;
0 ignored issues
show
Bug introduced by
The type Phine\Path\Path was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
use RecursiveDirectoryIterator;
27
use RecursiveIteratorIterator;
28
use RegexIterator;
29
use SplFileInfo;
30
use SplObjectStorage;
31
use Traversable;
32
33
/**
34
 * Provides additional, complimentary functionality to the Phar class.
35
 *
36
 * @author Kevin Herrera <[email protected]>
37
 */
38
class Box
39
{
40
    /**
41
     * The source code compactors.
42
     *
43
     * @var SplObjectStorage
44
     */
45
    private $compactors;
46
47
    /**
48
     * The path to the Phar file.
49
     *
50
     * @var string
51
     */
52
    private $file;
53
54
    /**
55
     * The Phar instance.
56
     *
57
     * @var Phar
58
     */
59
    private $phar;
60
61
    /**
62
     * The placeholder values.
63
     *
64
     * @var array
65
     */
66
    private $values = [];
67
68
    /**
69
     * Sets the Phar instance.
70
     *
71
     * @param Phar   $phar the instance
72
     * @param string $file the path to the Phar file
73
     */
74
    public function __construct(Phar $phar, $file)
75
    {
76
        $this->compactors = new SplObjectStorage();
77
        $this->file = $file;
78
        $this->phar = $phar;
79
    }
80
81
    /**
82
     * Adds a file contents compactor.
83
     *
84
     * @param Compactor $compactor the compactor
85
     */
86
    public function addCompactor(Compactor $compactor): void
87
    {
88
        $this->compactors->attach($compactor);
89
    }
90
91
    /**
92
     * Adds a file to the Phar, after compacting it and replacing its
93
     * placeholders.
94
     *
95
     * @param string $file  the file name
96
     * @param string $local the local file name
97
     *
98
     * @throws Exception\Exception
99
     * @throws FileException       if the file could not be used
100
     */
101
    public function addFile($file, $local = null): void
102
    {
103
        if (null === $local) {
104
            $local = $file;
105
        }
106
107
        if (false === is_file($file)) {
108
            throw FileException::create(
109
                'The file "%s" does not exist or is not a file.',
110
                $file
111
            );
112
        }
113
114
        if (false === ($contents = @file_get_contents($file))) {
115
            throw FileException::lastError();
116
        }
117
118
        $this->addFromString($local, $contents);
119
    }
120
121
    /**
122
     * Adds the contents from a file to the Phar, after compacting it and
123
     * replacing its placeholders.
124
     *
125
     * @param string $local    the local name
126
     * @param string $contents the contents
127
     */
128
    public function addFromString($local, $contents): void
129
    {
130
        $this->phar->addFromString(
131
            $local,
132
            $this->replaceValues($this->compactContents($local, $contents))
133
        );
134
    }
135
136
    /**
137
     * Similar to Phar::buildFromDirectory(), except the files will be
138
     * compacted and their placeholders replaced.
139
     *
140
     * @param string $dir   the directory
141
     * @param string $regex the regular expression filter
142
     */
143
    public function buildFromDirectory($dir, $regex = null): void
144
    {
145
        $iterator = new RecursiveIteratorIterator(
146
            new RecursiveDirectoryIterator(
147
                $dir,
148
                FilesystemIterator::KEY_AS_PATHNAME
149
                | FilesystemIterator::CURRENT_AS_FILEINFO
150
                | FilesystemIterator::SKIP_DOTS
151
            )
152
        );
153
154
        if ($regex) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $regex of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
155
            $iterator = new RegexIterator($iterator, $regex);
156
        }
157
158
        $this->buildFromIterator($iterator, $dir);
159
    }
160
161
    /**
162
     * Similar to Phar::buildFromIterator(), except the files will be compacted
163
     * and their placeholders replaced.
164
     *
165
     * @param Traversable $iterator the iterator
166
     * @param string      $base     the base directory path
167
     *
168
     * @throws Exception\Exception
169
     * @throws UnexpectedValueException if the iterator value is unexpected
170
     */
171
    public function buildFromIterator(Traversable $iterator, $base = null): void
172
    {
173
        if ($base) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $base of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
174
            $base = canonicalize($base.DIRECTORY_SEPARATOR);
175
        }
176
177
        foreach ($iterator as $key => $value) {
178
            if (is_string($value)) {
179
                if (false === is_string($key)) {
180
                    throw UnexpectedValueException::create(
181
                        'The key returned by the iterator (%s) is not a string.',
182
                        gettype($key)
183
                    );
184
                }
185
186
                $key = canonicalize($key);
187
                $value = canonicalize($value);
188
189
                if (is_dir($value)) {
190
                    $this->phar->addEmptyDir($key);
191
                } else {
192
                    $this->addFile($value, $key);
193
                }
194
            } elseif ($value instanceof SplFileInfo) {
195
                if (null === $base) {
196
                    throw InvalidArgumentException::create(
197
                        'The $base argument is required for SplFileInfo values.'
198
                    );
199
                }
200
201
                /** @var $value SplFileInfo */
202
                $real = $value->getRealPath();
203
204
                if (0 !== strpos($real, $base)) {
205
                    throw UnexpectedValueException::create(
206
                        'The file "%s" is not in the base directory.',
207
                        $real
208
                    );
209
                }
210
211
                $local = str_replace($base, '', $real);
212
213
                if ($value->isDir()) {
214
                    $this->phar->addEmptyDir($local);
215
                } else {
216
                    $this->addFile($real, $local);
217
                }
218
            } else {
219
                throw UnexpectedValueException::create(
220
                    'The iterator value "%s" was not expected.',
221
                    gettype($value)
222
                );
223
            }
224
        }
225
    }
226
227
    /**
228
     * Compacts the file contents using the supported compactors.
229
     *
230
     * @param string $file     the file name
231
     * @param string $contents the file contents
232
     *
233
     * @return string the compacted contents
234
     */
235
    public function compactContents($file, $contents)
236
    {
237
        foreach ($this->compactors as $compactor) {
238
            /** @var $compactor CompactorInterface */
239
            if ($compactor->supports($file)) {
240
                $contents = $compactor->compact($contents);
241
            }
242
        }
243
244
        return $contents;
245
    }
246
247
    /**
248
     * Creates a new Phar and Box instance.
249
     *
250
     * @param string $file  the file name
251
     * @param int    $flags the RecursiveDirectoryIterator flags
252
     * @param string $alias the Phar alias
253
     *
254
     * @return Box the Box instance
255
     */
256
    public static function create($file, $flags = null, $alias = null)
257
    {
258
        return new self(new Phar($file, (int) $flags, $alias), $file);
259
    }
260
261
    /**
262
     * Returns the Phar instance.
263
     *
264
     * @return Phar the instance
265
     */
266
    public function getPhar()
267
    {
268
        return $this->phar;
269
    }
270
271
    /**
272
     * Returns the signature of the phar.
273
     *
274
     * This method does not use the extension to extract the phar's signature.
275
     *
276
     * @param string $path the phar file path
277
     *
278
     * @return array the signature
279
     */
280
    public static function getSignature($path)
281
    {
282
        return Signature::create($path)->get();
283
    }
284
285
    /**
286
     * Replaces the placeholders with their values.
287
     *
288
     * @param string $contents the contents
289
     *
290
     * @return string the replaced contents
291
     */
292
    public function replaceValues($contents)
293
    {
294
        return str_replace(
295
            array_keys($this->values),
296
            array_values($this->values),
297
            $contents
298
        );
299
    }
300
301
    /**
302
     * Sets the bootstrap loader stub using a file.
303
     *
304
     * @param string $file    the file path
305
     * @param bool   $replace Replace placeholders?
306
     *
307
     * @throws Exception\Exception
308
     * @throws FileException       if the stub file could not be used
309
     */
310 View Code Duplication
    public function setStubUsingFile($file, $replace = false): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
311
    {
312
        if (false === is_file($file)) {
313
            throw FileException::create(
314
                'The file "%s" does not exist or is not a file.',
315
                $file
316
            );
317
        }
318
319
        if (false === ($contents = @file_get_contents($file))) {
320
            throw FileException::lastError();
321
        }
322
323
        if ($replace) {
324
            $contents = $this->replaceValues($contents);
325
        }
326
327
        $this->phar->setStub($contents);
328
    }
329
330
    /**
331
     * Sets the placeholder values.
332
     *
333
     * @param array $values the values
334
     *
335
     * @throws Exception\Exception
336
     * @throws InvalidArgumentException if a non-scalar value is used
337
     */
338
    public function setValues(array $values): void
339
    {
340
        foreach ($values as $value) {
341
            if (false === is_scalar($value)) {
342
                throw InvalidArgumentException::create(
343
                    'Non-scalar values (such as %s) are not supported.',
344
                    gettype($value)
345
                );
346
            }
347
        }
348
349
        $this->values = $values;
350
    }
351
352
    /**
353
     * Signs the Phar using a private key.
354
     *
355
     * @param string $key      the private key
356
     * @param string $password the private key password
357
     *
358
     * @throws Exception\Exception
359
     * @throws OpenSslException    if the "openssl" extension could not be used
360
     *                             or has generated an error
361
     */
362
    public function sign($key, $password = null): void
363
    {
364
        OpenSslException::reset();
365
366
        if (false === extension_loaded('openssl')) {
367
            // @codeCoverageIgnoreStart
368
            throw OpenSslException::create(
369
                'The "openssl" extension is not available.'
370
            );
371
            // @codeCoverageIgnoreEnd
372
        }
373
374
        if (false === ($resource = openssl_pkey_get_private($key, $password))) {
375
            // @codeCoverageIgnoreStart
376
            throw OpenSslException::lastError();
377
            // @codeCoverageIgnoreEnd
378
        }
379
380
        if (false === openssl_pkey_export($resource, $private)) {
381
            // @codeCoverageIgnoreStart
382
            throw OpenSslException::lastError();
383
            // @codeCoverageIgnoreEnd
384
        }
385
386
        if (false === ($details = openssl_pkey_get_details($resource))) {
387
            // @codeCoverageIgnoreStart
388
            throw OpenSslException::lastError();
389
            // @codeCoverageIgnoreEnd
390
        }
391
392
        $this->phar->setSignatureAlgorithm(Phar::OPENSSL, $private);
393
394
        if (false === @file_put_contents($this->file.'.pubkey', $details['key'])) {
395
            throw FileException::lastError();
396
        }
397
    }
398
399
    /**
400
     * Signs the Phar using a private key file.
401
     *
402
     * @param string $file     the private key file name
403
     * @param string $password the private key password
404
     *
405
     * @throws Exception\Exception
406
     * @throws FileException       if the private key file could not be read
407
     */
408 View Code Duplication
    public function signUsingFile($file, $password = null): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
409
    {
410
        if (false === is_file($file)) {
411
            throw FileException::create(
412
                'The file "%s" does not exist or is not a file.',
413
                $file
414
            );
415
        }
416
417
        if (false === ($key = @file_get_contents($file))) {
418
            throw FileException::lastError();
419
        }
420
421
        $this->sign($key, $password);
422
    }
423
}
424