Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check if a given PHP Binary is actually valid #25

Merged
merged 3 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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