Skip to content

Commit

Permalink
Fail gracefully when the extension required for the compression algor…
Browse files Browse the repository at this point in the history
…ithm is not loaded (#205)

- Add a warning in the documentation and the logs of the `compile` command regarding the extension required to execute a PHAR once compressed
- Handle the compression in `Box` instead of using the leaked `Box#phar`
- Make `Box` countable
- A few minor refactoring bits

Closes #186
  • Loading branch information
theofidry authored May 4, 2018
1 parent 813742c commit 26b3f48
Show file tree
Hide file tree
Showing 9 changed files with 382 additions and 70 deletions.
13 changes: 12 additions & 1 deletion doc/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ The configuration file is a JSON object saved to a file. Note that all settings
"datetime_format": "?",
"directories": "?",
"directories-bin": "?",
"dump-autoload": "?",
"files": "?",
"files-bin": "?",
"finder": "?",
Expand Down Expand Up @@ -140,6 +141,10 @@ If a `composer.json` is found without a `composer.lock`, the it will be taken as
requirements regardless of whether there is no `composer.lock` because there is no dependencies or because it has been
removed.

**Warning**: this check is still done within the PHAR. As a result, if [the required extension to open the PHAR][compression]
due to the compression algorithm is not loaded, a hard failure will still appear: the requirement checker _cannot_ be
executed before that.


## Including files

Expand Down Expand Up @@ -510,10 +515,14 @@ The compression (`string`|`null`) setting is the compression algorithm to use wh
affects the individual files within the PHAR and not the PHAR as a whole ([`Phar::compressFiles()`][phar.compress]). The
following is a list of the signature algorithms available:

- `BZ2`
- `GZ` (the most efficient most of the time)
- `BZ2`
- `NONE` (default)

**Warning**: be aware that if compressed, the PHAR will required the appropriate extension ([`zlib`][zlib-extension] for
`GZ` and [`bz2`][bz2-extension] for `BZ2`) to execute the PHAR. Without it, PHP will _not_ be able to open the PHAR at
all.


## Signing algorithm (`algorithm`)

Expand Down Expand Up @@ -578,6 +587,8 @@ The metadata (`any`) setting can be any value. This value will be stored as meta
[composer-bin]: https://getcomposer.org/doc/04-schema.md#bin
[composer-classmap-authoritative]: https://getcomposer.org/doc/articles/autoloader-optimization.md#optimization-level-2-a-authoritative-class-maps
[composer-no-dev-option]: https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload-
[zlib-extension]: https://secure.php.net/manual/en/book.zlib.php
[bz2-extension]: https://secure.php.net/manual/en/book.bzip2.php


//TODO: rework the rest
Expand Down
61 changes: 60 additions & 1 deletion src/Box.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,23 @@
namespace KevinGH\Box;

use Assert\Assertion;
use BadMethodCallException;
use Closure;
use Countable;
use KevinGH\Box\Compactor\PhpScoper;
use KevinGH\Box\Composer\ComposerOrchestrator;
use KevinGH\Box\PhpScoper\NullScoper;
use KevinGH\Box\PhpScoper\Scoper;
use Phar;
use RecursiveDirectoryIterator;
use RuntimeException;
use SplFileInfo;
use function Amp\ParallelFunctions\parallelMap;
use function Amp\Promise\wait;
use function array_flip;
use function array_map;
use function chdir;
use function extension_loaded;
use function file_exists;
use function getcwd;
use function KevinGH\Box\FileSystem\dump_file;
Expand All @@ -35,13 +40,14 @@
use function KevinGH\Box\FileSystem\make_tmp_dir;
use function KevinGH\Box\FileSystem\mkdir;
use function KevinGH\Box\FileSystem\remove;
use function sprintf;

/**
* Box is a utility class to generate a PHAR.
*
* @private
*/
final class Box
final class Box implements Countable
{
public const DEBUG_DIR = '.box_dump';

Expand Down Expand Up @@ -154,6 +160,49 @@ public function endBuffering(bool $dumpAutoload): void
$this->phar->stopBuffering();
}

/**
* @return null|string The required extension to execute the PHAR now that it is compressed
*/
public function compress(int $compressionAlgorithm): ?string
{
Assertion::false($this->buffering, 'Cannot compress files while buffering.');
Assertion::inArray($compressionAlgorithm, get_phar_compression_algorithms());

$extensionRequired = get_phar_compression_algorithm_extension($compressionAlgorithm);

if (null !== $extensionRequired && false === extension_loaded($extensionRequired)) {
throw new RuntimeException(
sprintf(
'Cannot compress the PHAR with the compression algorithm "%s": the extension "%s" is required but appear to not '
.'be loaded',
array_flip(get_phar_compression_algorithms())[$compressionAlgorithm],
$extensionRequired
)
);
}

try {
if (Phar::NONE === $compressionAlgorithm) {
$this->getPhar()->decompressFiles();
} else {
$this->phar->compressFiles($compressionAlgorithm);
}
} catch (BadMethodCallException $exception) {
$exceptionMessage = 'unable to create temporary file' !== $exception->getMessage()
? 'Could not compress the PHAR: '.$exception->getMessage()
: sprintf(
'Could not compress the PHAR: the compression requires too many file descriptors to be opened (%s). Check '
.'your system limits or install the posix extension to allow Box to automatically configure it during the compression',
$this->phar->count()
)
;

throw new RuntimeException($exceptionMessage, $exception->getCode(), $exception);
}

return $extensionRequired;
}

/**
* @param Compactor[] $compactors
*/
Expand Down Expand Up @@ -399,4 +448,14 @@ function (string $contents, Compactor $compactor) use ($file): string {
$contents
);
}

/**
* {@inheritdoc}
*/
public function count(): int
{
Assertion::false($this->buffering, 'Cannot count the number of files in the PHAR when buffering');

return $this->phar->count();
}
}
4 changes: 2 additions & 2 deletions src/Composer/ComposerOrchestrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
use Composer\Factory;
use Composer\IO\NullIO;
use Humbug\PhpScoper\Autoload\ScoperAutoloadGenerator;
use RuntimeException;
use Throwable;
use function KevinGH\Box\FileSystem\dump_file;
use function KevinGH\Box\FileSystem\file_contents;
use function preg_replace;
use RuntimeException;
use Throwable;

/**
* @private
Expand Down
2 changes: 1 addition & 1 deletion src/Console/Command/Build.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use function trigger_error;
use const E_USER_DEPRECATED;
use function trigger_error;

/**
* @deprecated
Expand Down
Loading

0 comments on commit 26b3f48

Please sign in to comment.