Skip to content
Closed
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
3 changes: 3 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ jobs:
cd e2e/bug-15102c
composer install
../../bin/phpstan analyze
- script: |
cd e2e/bug-15102d
../../bin/phpstan analyze
- script: |
cd e2e/bug-14724
composer install
Expand Down
21 changes: 21 additions & 0 deletions e2e/bug-15102d/dep/E2eNestedClassLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php declare(strict_types = 1);

// The shape of Composer's ClassLoader: register() + loadClass() reading a file.
final class E2eNestedClassLoader
{

public function register(): void
{
spl_autoload_register([$this, 'loadClass']);
}

public function loadClass(string $class): void
{
if ($class !== 'E2eDepInternal\\SomeInterface') {
return;
}

require __DIR__ . '/SomeInterface.php';
}

}
10 changes: 10 additions & 0 deletions e2e/bug-15102d/dep/SomeInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);

namespace E2eDepInternal;

interface SomeInterface
{

public function handle(): string;

}
8 changes: 8 additions & 0 deletions e2e/bug-15102d/dep/autoload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php declare(strict_types = 1);

require_once __DIR__ . '/E2eNestedClassLoader.php';

$loader = new E2eNestedClassLoader();
$loader->register();

return $loader;
21 changes: 21 additions & 0 deletions e2e/bug-15102d/dep/bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php declare(strict_types = 1);

// The shape deptrac's bootstrap.php creates: an autoloader that lazily requires a nested
// Composer-style autoloader and memoizes the result in a closure static. The file-read
// trap's pseudo-include makes that require "succeed" with dummy data, so the memo ends up
// holding an int and the require is never retried.
spl_autoload_register(static function (string $class): void {
static $composerAutoloader;

if (!str_starts_with($class, 'E2eDepInternal\\')) {
return;
}

if ($composerAutoloader === null) {
$composerAutoloader = require __DIR__ . '/autoload.php';
}

if ($composerAutoloader instanceof E2eNestedClassLoader) {
$composerAutoloader->loadClass($class);
}
});
6 changes: 6 additions & 0 deletions e2e/bug-15102d/phpstan.dist.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
parameters:
level: 8
bootstrapFiles:
- dep/bootstrap.php
paths:
- test.php
15 changes: 15 additions & 0 deletions e2e/bug-15102d/test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types = 1);

namespace E2eMemoizedRequire;

use E2eDepInternal\SomeInterface;

final class Handler implements SomeInterface
{

public function handle(): string
{
return 'ok';
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use function class_exists;
use function function_exists;
use function interface_exists;
use function is_file;
use function opcache_invalidate;
use function PHPStan\autoloadFunctions;
use function PHPStan\autoloadFunctionsPrependedToComposer;
Expand Down Expand Up @@ -92,6 +93,26 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
return null;
}

// Include the located files for real first. The probe's pseudo-include succeeds with
// dummy data, so an autoloader that memoizes such a require's result - deptrac's
// bootstrap caches `$loader = require .../vendor/autoload.php` in a closure static -
// is left holding an int and never retries it. This include does what that autoloader
// can no longer do itself: an autoload-infrastructure file registers its own class
// loader, and locating the class again consults it through the file-read trap.
foreach ($locatedFiles as $locatedFile) {
if (!is_file($locatedFile)) {
continue;
}
(static function (string $file): void {
require $file;
})($locatedFile);
}

$reflection = $this->locateWithoutAutoloading($reflector, $identifier);
if ($reflection !== null) {
return $reflection;
}

foreach ($autoloadFunctions as $autoloadFunction) {
$autoloadFunction($className);

Expand Down
Loading