Skip to content
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
2 changes: 1 addition & 1 deletion docs/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ return require $file; // happy path: no stat(), just cached opcode
A missing compiled script is a build invariant violation (corrupt or incomplete build); PHP 8 makes a
failed `require` a catchable `Error` that surfaces naturally. `CompiledInjector::getInstance()` keeps its
`file_exists()` pre-check (it reports unbound interfaces as `Unbound`); its redundant
`realpath($this->scriptDir)` — already canonicalised in the constructor — was removed.
`realpath($this->scriptDir)` — already validated in the constructor — was removed.

## Benchmarking correctly

Expand Down
9 changes: 7 additions & 2 deletions src/CompiledInjector.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,13 @@ final class CompiledInjector implements ScriptInjectorInterface
public function __construct(#[ScriptDir]
string $scriptDir,)
{
// realpath() cannot resolve stream URIs such as phar://; keep those as given
$realPath = realpath($scriptDir);
if ($realPath === false || ! is_dir($realPath) || ! is_readable($realPath)) {
if ($realPath === false) {
$realPath = $scriptDir;
}

if (! is_dir($realPath) || ! is_readable($realPath)) {
$message = sprintf('Script directory "%s" is not readable. See https://ray-di.github.io/Ray.Compiler/error/ScriptDirNotReadable', $scriptDir);

throw new ScriptDirNotReadable($message);
Expand Down Expand Up @@ -112,7 +117,7 @@ public function getInstance($interface, $name = Name::ANY)

/** @psalm-suppress UnsupportedPropertyReferenceUsage */
$singletons = &$this->singletons;
$scriptDir = $this->scriptDir; // already realpath()d in the constructor
$scriptDir = $this->scriptDir; // validated in the constructor

// $scriptDir, $singletons, and $dependencyIndex can be used in the included file
/** @var mixed $instance */
Expand Down
31 changes: 31 additions & 0 deletions tests/CompiledInjectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@
use Ray\Compiler\Exception\ScriptDirNotReadable;
use Ray\Di\Exception\Unbound;

use function escapeshellarg;
use function exec;
use function implode;
use function mkdir;
use function serialize;
use function spl_object_hash;
use function sprintf;
use function unserialize;

use const PHP_BINARY;

/** @psalm-import-type ScriptDir from CompiledInjector */
class CompiledInjectorTest extends TestCase
{
Expand Down Expand Up @@ -111,4 +117,29 @@ public function testThrowsScriptDirNotReadableException(): void
$this->expectExceptionMessage($scriptDir);
new CompiledInjector($scriptDir);
}

/**
* realpath() returns false for phar:// paths, but compiled scripts are
* relocatable (see ScriptDirRelocationTest) and phars can carry them.
*/
public function testScriptDirInsidePhar(): void
{
$scriptDir = __DIR__ . '/tmp/' . __FUNCTION__;
@mkdir($scriptDir);
(new Compiler())->compile(new FakeModule(), $scriptDir);
$pharFile = $scriptDir . '.phar'; // outside $scriptDir so the build never globs its own output
// The test process runs with phar.readonly=1; build in a child that does not
exec(sprintf(
'%s -d phar.readonly=0 %s %s %s 2>&1',
escapeshellarg(PHP_BINARY),
escapeshellarg(__DIR__ . '/script/build_phar.php'),
escapeshellarg($scriptDir),
escapeshellarg($pharFile),
), $output, $exitCode);
$this->assertSame(0, $exitCode, implode("\n", $output));

$injector = new CompiledInjector('phar://' . $pharFile . '/di');
$instance = $injector->getInstance(FakeCarInterface::class);
$this->assertInstanceOf(FakeCarInterface::class, $instance);
}
}
20 changes: 20 additions & 0 deletions tests/script/build_phar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/**
* Build a phar whose /di directory holds the given compiled scripts.
*
* Run with -d phar.readonly=0; the test suite itself runs read-only.
*
* argv: [1] compiled script directory (flat), [2] phar file to create
*/

declare(strict_types=1);

@unlink($argv[2]); // Phar appends to an existing archive; start clean
$phar = new Phar($argv[2]);
$phar->startBuffering();
foreach (array_filter((array) glob($argv[1] . '/*')) as $file) {
$phar->addFile($file, 'di/' . basename($file));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

$phar->stopBuffering();
Loading