sebastianbergmann/phpunit · error · PHPUnit\Util\InvalidDirectoryException

"%s" is not a directory

Error message

"%s" is not a directory

What it means

ExcludeList::addDirectory() is a static API to register a directory whose files PHPUnit's error/deprecation reporting should ignore (commonly used to silence vendor deprecations). It validates its argument with is_dir() and throws InvalidDirectoryException for anything that is not an existing directory. This prevents typo'd or stale paths from silently making the exclusion useless.

Source

Thrown at src/Util/ExcludeList.php:153

        Tokenizer::class => 1,
    ];

    /**
     * @var list<string>
     */
    private static array $directories = [];
    private static bool $initialized  = false;
    private readonly bool $enabled;

    /**
     * @param non-empty-string $directory
     *
     * @throws InvalidDirectoryException
     */
    public static function addDirectory(string $directory): void
    {
        if (!is_dir($directory)) {
            throw new InvalidDirectoryException($directory);
        }

        $directory = realpath($directory);

        assert($directory !== false);

        self::$directories[] = $directory;
    }

    public function __construct(?bool $enabled = null)
    {
        if ($enabled === null) {
            $enabled = !defined('PHPUNIT_TESTSUITE');
        }

        $this->enabled = $enabled;
    }

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Pass an absolute path anchored to the bootstrap: ExcludeList::addDirectory(__DIR__ . '/../vendor')
  2. Check is_dir($dir) before calling when the path comes from configuration or user input
  3. Fix or remove stale symlinks and typos in the configured directory
  4. When the directory is optional, wrap the call in a guard so its absence does not break the whole run

Example fix

// before
\PHPUnit\Util\ExcludeList::addDirectory('src/generated');
// InvalidDirectoryException: "src/generated" is not a directory (cwd differs)

// after
$dir = realpath(__DIR__ . '/src/generated');
if ($dir !== false) {
    \PHPUnit\Util\ExcludeList::addDirectory($dir);
}
Defensive patterns

Strategy: validation

Validate before calling

use PHPUnit\Util\ExcludeList;

$dir = __DIR__ . '/../generated';
if (is_dir($dir)) {
    ExcludeList::addDirectory($dir);
}

Type guard

static function isExistingDirectory(string $path): bool
{
    return is_dir($path) && !is_link($path) || (is_link($path) && is_dir(readlink($path) ?: ''));
}

Try / catch

use PHPUnit\Util\InvalidDirectoryException;

try {
    ExcludeList::addDirectory($configuredDir);
} catch (InvalidDirectoryException $e) {
    // log and continue: an unused exclusion should not break the suite
    error_log('Skipping exclusion: ' . $e->getMessage());
}

Prevention

When it happens

Trigger: Calling ExcludeList::addDirectory($dir) from a bootstrap with $dir that is actually a file, a symlink pointing to a nonexistent target, or a relative path that does not resolve from the current working directory when PHPUnit runs.

Common situations: Bootstraps that build paths relative to getcwd() instead of __DIR__; projects moved or renamed after the bootstrap was written; vendor replaced by a symlink in deployment images; phar-based tooling where relative paths change meaning.

Related errors


AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23). Data as JSON: /api/errors/df009a7d46ed2018. Report an issue: GitHub.