Skip to content

Commit

Permalink
Merge pull request #25 from asgrim/check-if-given-php-binary-is-valid
Browse files Browse the repository at this point in the history
Check if a given PHP Binary is actually valid
  • Loading branch information
asgrim authored Aug 13, 2024
2 parents 1e149cb + df28f0c commit d96c349
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 1 deletion.
36 changes: 36 additions & 0 deletions src/Platform/TargetPhp/Exception/InvalidPhpBinaryPath.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Php\Pie\Platform\TargetPhp\Exception;

use RuntimeException;

use function sprintf;

class InvalidPhpBinaryPath extends RuntimeException
{
public static function fromNonExistentPhpBinary(string $phpBinaryPath): self
{
return new self(sprintf(
'The php binary at "%s" does not exist',
$phpBinaryPath,
));
}

public static function fromNonExecutablePhpBinary(string $phpBinaryPath): self
{
return new self(sprintf(
'The php binary at "%s" is not executable',
$phpBinaryPath,
));
}

public static function fromInvalidPhpBinary(string $phpBinaryPath): self
{
return new self(sprintf(
'The php binary at "%s" does not appear to be a PHP binary',
$phpBinaryPath,
));
}
}
31 changes: 30 additions & 1 deletion src/Platform/TargetPhp/PhpBinaryPath.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Php\Pie\Platform\TargetPhp;

use Composer\Semver\VersionParser;
use Composer\Util\Platform;
use Php\Pie\Platform\Architecture;
use Php\Pie\Platform\OperatingSystem;
use Psl\Json;
Expand All @@ -19,6 +20,7 @@
use function dirname;
use function file_exists;
use function is_dir;
use function is_executable;
use function preg_match;
use function sprintf;
use function trim;
Expand All @@ -40,7 +42,28 @@ private function __construct(
public readonly string $phpBinaryPath,
private readonly string|null $phpConfigPath,
) {
// @todo https://github.com/php/pie/issues/12 - we could verify that the given $phpBinaryPath really is a PHP install
}

/** @param non-empty-string $phpBinaryPath */
private static function assertValidLookingPhpBinary(string $phpBinaryPath): void
{
if (! file_exists($phpBinaryPath)) {
throw Exception\InvalidPhpBinaryPath::fromNonExistentPhpBinary($phpBinaryPath);
}

if (! Platform::isWindows() && ! is_executable($phpBinaryPath)) {
throw Exception\InvalidPhpBinaryPath::fromNonExecutablePhpBinary($phpBinaryPath);
}

// This is somewhat of a rudimentary check that the target PHP really is a PHP instance; not sure why you
// WOULDN'T want to use a real PHP, but this should stop obvious hiccups at least (rather than for security)
$testOutput = trim((new Process([$phpBinaryPath, '-r', 'echo "PHP";']))
->mustRun()
->getOutput());

if ($testOutput !== 'PHP') {
throw Exception\InvalidPhpBinaryPath::fromInvalidPhpBinary($phpBinaryPath);
}
}

/** @return non-empty-string */
Expand Down Expand Up @@ -223,12 +246,16 @@ public static function fromPhpConfigExecutable(string $phpConfig): self
->getOutput());
Assert::stringNotEmpty($phpExecutable, 'Could not find path to PHP executable.');

self::assertValidLookingPhpBinary($phpExecutable);

return new self($phpExecutable, $phpConfig);
}

/** @param non-empty-string $phpBinary */
public static function fromPhpBinaryPath(string $phpBinary): self
{
self::assertValidLookingPhpBinary($phpBinary);

return new self($phpBinary, null);
}

Expand All @@ -237,6 +264,8 @@ public static function fromCurrentProcess(): self
$phpExecutable = trim((string) (new PhpExecutableFinder())->find());
Assert::stringNotEmpty($phpExecutable, 'Could not find path to PHP executable.');

self::assertValidLookingPhpBinary($phpExecutable);

return new self($phpExecutable, null);
}
}
3 changes: 3 additions & 0 deletions test/assets/fake-php.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env bash

echo "Hah! I am not really PHP.";
37 changes: 37 additions & 0 deletions test/unit/Platform/TargetPhp/PhpBinaryPathTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace Php\PieUnitTest\Platform\TargetPhp;

use Composer\Util\Platform;
use Php\Pie\Platform\Architecture;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\TargetPhp\Exception\InvalidPhpBinaryPath;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -35,6 +37,41 @@
#[CoversClass(PhpBinaryPath::class)]
final class PhpBinaryPathTest extends TestCase
{
private const FAKE_PHP_EXECUTABLE = __DIR__ . '/../../../assets/fake-php.sh';

public function testNonExistentPhpBinaryIsRejected(): void
{
$this->expectException(InvalidPhpBinaryPath::class);
$this->expectExceptionMessage('does not exist');
PhpBinaryPath::fromPhpBinaryPath(__DIR__ . '/path/to/a/non/existent/php/binary');
}

public function testNonExecutablePhpBinaryIsRejected(): void
{
if (Platform::isWindows()) {
/**
* According to the {@link https://www.php.net/manual/en/function.is-executable.php}:
*
* for BC reasons, files with a .bat or .cmd extension are also considered executable
*
* However, that does not seem to be the case; calling {@see is_executable} always seems to return false,
* even with a `.bat` file.
*/
self::markTestSkipped('is_executable always returns false on Windows it seems...');
}

$this->expectException(InvalidPhpBinaryPath::class);
$this->expectExceptionMessage('is not executable');
PhpBinaryPath::fromPhpBinaryPath(__FILE__);
}

public function testInvalidPhpBinaryIsRejected(): void
{
$this->expectException(InvalidPhpBinaryPath::class);
$this->expectExceptionMessage('does not appear to be a PHP binary');
PhpBinaryPath::fromPhpBinaryPath(self::FAKE_PHP_EXECUTABLE);
}

public function testVersionFromCurrentProcess(): void
{
$phpBinary = PhpBinaryPath::fromCurrentProcess();
Expand Down

0 comments on commit d96c349

Please sign in to comment.