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
- Verify and repair the path before scanning: if (!is_dir($dir)) mkdir($dir, 0775, true); and confirm is_readable($dir).
- Always pass absolute paths resolved through Yii::getAlias(), e.g. FileHelper::findFiles(Yii::getAlias('@runtime/exports')).
- 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).
- 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
- Create scan-target directories during deployment (mkdir -p) instead of assuming they exist.
- Resolve every filesystem path through Yii::getAlias() so paths are absolute and rooted in known aliases.
- Grant the web server user read+execute on every directory being scanned.
- When moving to shared hosting, check open_basedir covers all scanned paths.
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
- The dir argument must be a directory: $dir
- Failed to create directory "{$path}": {message}
- Failed to change permissions for directory "{$path}": {messa
- FileDependency::fileName must be set
- If exclude/include pattern is an array it must contain the p
AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17).
Data as JSON: /api/errors/866eff257a408b3f.
Report an issue: GitHub.