Skip to content

Internal error "Failed opening required '<?php ...'" with PHPStan 2.2.10 on PHP 8.3+ #356

Description

@michielgerritsen

Summary

With PHPStan 2.2.10 (released 2026-08-30) every generated Factory, Proxy, Extension and
ExtensionInterface class causes an internal error:

Internal error: Failed opening required '<?php
namespace Demo\Model;

/**
 * Factory class for @see \Demo\Model\Thing101
 */
class Thing101Factory
{
    /**
     * Create class instance with specified parameters
     *
     * @param array $data
     * @return Thing101
     */
    public function create(array $data = array()) {}
}
' (include_path='.:/usr/local/lib/php') while analysing file /app/src/File101.php

require_once() receives the generated source code in place of the file path.

The same run passes with PHPStan 2.2.9.

Cause

bitExpert\PHPStan\Magento\Autoload\Cache\FileCacheStorage does not keep the
PHPStan\Cache\CacheStorage contract:

  • save($key, $variableKey, $data) writes $data (the source code) to a file.
  • load($key, $variableKey) returns the file path, not the data that was saved.

FactoryAutoloader::autoload() depends on that asymmetry
(src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php:52-57):

$pathToLocalClass = $this->cache->load($class, '');
if ($pathToLocalClass === null) {
    $this->cache->save($class, '', $this->getFileContents($class));
    $pathToLocalClass = $this->cache->load($class, '');
}

require_once($pathToLocalClass);

PHPStan\Cache\Cache now has a shared-memory arena in front of the storage. Cache::save()
publishes $data to the arena, and Cache::load() returns the arena value before it asks the
storage. Thus the second load() gives back the source code, and require_once() gets it.

PHPStan 2.2.9 has the same arena code, but the arena stayed inactive: the turbo extension was
loaded in worker processes only. PHPStan 2.2.10 restarts the main process with the turbo
extension (TurboProcessRestarter) and forks the workers, thus the arena becomes active and the
mismatch shows.

Only PHP 8.3, 8.4 and 8.5 are affected, because PHPStan distributes turbo binaries for those
versions only. PHP 8.1 and 8.2 stay on the old path and pass.

The same pattern is in:

  • Autoload/FactoryAutoloader.php:52
  • Autoload/ProxyAutoloader.php:51
  • Autoload/ExtensionAutoloader.php:68
  • Autoload/ExtensionInterfaceAutoloader.php:67

How to reproduce

The reproduction needs Linux, PHP 8.3 or later, pcntl/posix and more than one analysis
process. The Docker command below covers all of it.

cat > composer.json <<'JSON'
{
    "require-dev": {
        "phpstan/phpstan": "2.2.10",
        "bitexpert/phpstan-magento": "^0.43",
        "phpstan/extension-installer": "^1.4"
    },
    "config": { "allow-plugins": { "phpstan/extension-installer": true } }
}
JSON

cat > phpstan.neon <<'NEON'
parameters:
    level: 2
    paths:
        - src
    parallel:
        jobSize: 5
        minimumNumberOfJobsPerProcess: 1
        maximumNumberOfProcesses: 4
NEON

mkdir src
for i in $(seq 1 200); do
    printf '<?php declare(strict_types=1);\nnamespace Demo;\nclass File%s { public function run(\\Demo\\Model\\Thing%sFactory $f): void { $f->create(); } }\n' "$i" "$i" > "src/File$i.php"
done

docker run --rm --platform linux/amd64 -v "$PWD":/app -w /app php:8.4-cli bash -c '
    apt-get update -qq && apt-get install -y -qq unzip git
    docker-php-ext-install -j4 pcntl posix
    curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
    composer update --no-interaction
    vendor/bin/phpstan diagnose | grep -A3 "Turbo extension"
    vendor/bin/phpstan analyse --no-progress
'

Result:

Turbo extension: enabled (version 2659ad4)
Turbo platform: linux-gnu-x86_64 (os: Linux, machine: x86_64, libc: gnu, php: 8.4, zts: no, debug: no)
Turbo worker binary: /app/vendor/phpstan/phpstan/turbo-ext/linux-gnu-x86_64/phpstan_turbo-8.4.so (loaded via process restart)

 [ERROR] Found 50 errors

Test matrix

Setup Result
phpstan 2.2.10 50 internal errors (PHPStan stops at its internal-error limit)
phpstan 2.2.9 No errors
phpstan 2.2.10, PHPSTAN_ARENA=0 No errors
phpstan 2.2.10, PHPSTAN_TURBO=0 No errors
phpstan 2.2.10, PHP 8.1 No errors (Turbo extension: not loaded)

vendor/bin/phpstan diagnose shows the difference:

  • 2.2.9: Turbo extension: enabled in worker processes (the main process runs without it) / Mechanism: spawn (react/child-process)
  • 2.2.10: Turbo extension: enabled (loaded via process restart) / Mechanism: fork (pcntl_fork)

Environment

  • bitexpert/phpstan-magento v0.43.0
  • phpstan/phpstan 2.2.10 (2.2.9 is good)
  • PHP 8.4 (also 8.3 and 8.5), Linux x86_64
  • Found in CI on Magento 2.4.9 images with PHP 8.4

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions