yiisoft/yii2 · error · InvalidArgumentException

Unable to open directory: $dir

Error message

Unable to open directory: $dir

What it means

Thrown by the private openDir() helper in yii\helpers\BaseFileHelper when PHP's opendir() returns false for the given path. Every FileHelper scan operation (findFiles(), findDirectories(), copyDirectory(), removeDirectory()) funnels through it, so a missing or unreadable directory aborts the whole scan with an InvalidArgumentException that names the offending path. It exists to fail fast instead of silently returning an empty result set for a broken path.

Source

Thrown at framework/helpers/BaseFileHelper.php:637

        if (!isset($options['basePath'])) {
            // this should be done only once
            $options['basePath'] = realpath($dir);
            $options = static::normalizeOptions($options);
        }

        return $options;
    }

    /**
     * @param string $dir
     * @return resource
     * @throws InvalidArgumentException if unable to open directory
     */
    private static function openDir($dir)
    {
        $handle = opendir($dir);
        if ($handle === false) {
            throw new InvalidArgumentException("Unable to open directory: $dir");
        }
        return $handle;
    }

    /**
     * @param string $dir
     * @return string
     * @throws InvalidArgumentException if directory not exists
     */
    private static function clearDir($dir)
    {
        if (!is_dir($dir)) {
            throw new InvalidArgumentException("The dir argument must be a directory: $dir");
        }
        return rtrim($dir, '\/');
    }

    /**

View on GitHub (pinned to 66f00d18a2)

Solutions

  1. Verify and repair the path before scanning: if (!is_dir($dir)) mkdir($dir, 0775, true); and confirm is_readable($dir).
  2. Always pass absolute paths resolved through Yii::getAlias(), e.g. FileHelper::findFiles(Yii::getAlias('@runtime/exports')).
  3. On shared hosting, inspect open_basedir in phpinfo() and move the scanned directory inside the allowed paths (or ask the host to widen the directive).
  4. For optional directories, wrap the scan in try/catch (InvalidArgumentException) and treat failure as an empty result.

Example fix

// before
$files = \yii\helpers\FileHelper::findFiles(\Yii::getAlias('@app/attachments'));

// after
$dir = \Yii::getAlias('@app/attachments');
if (!is_dir($dir)) {
    mkdir($dir, 0775, true);
}
$files = is_readable($dir) ? \yii\helpers\FileHelper::findFiles($dir) : [];
Defensive patterns

Strategy: validation

Validate before calling

$dir = \Yii::getAlias('@webroot/uploads');
if (!is_dir($dir)) {
    throw new \RuntimeException("Directory missing: {$dir}");
}
if (!is_readable($dir)) {
    throw new \RuntimeException("Directory not readable by PHP user: {$dir}");
}
$files = \yii\helpers\FileHelper::findFiles($dir);

Try / catch

try {
    $files = \yii\helpers\FileHelper::findFiles($dir);
} catch (\yii\base\InvalidArgumentException $e) {
    \Yii::warning("Scan skipped: {$e->getMessage()}", 'filehelper');
    $files = [];
}

Prevention

When it happens

Trigger: Calling FileHelper::findFiles($dir) / findDirectories() / copyDirectory() where $dir does not exist, is a regular file, or is not readable by the PHP process; running under PHP-FPM/Apache with an open_basedir directive that excludes $dir; passing a relative path that resolves against an unexpected current working directory (CLI vs web).

Common situations: Deployment scripts scanning runtime folders (runtime/cache, uploads/) that were cleared or never created on the new host; shared hosting with open_basedir limits; permission differences between CLI (root) and web server user; a race where another process deletes the directory between the caller's is_dir() check and the scan.

Related errors


AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17). Data as JSON: /api/errors/866eff257a408b3f. Report an issue: GitHub.