is_dir() 函数检查指定的文件是否是一个目录。如果文件存在并且是一个目录,则返回 TRUE,否则返回 FALSE。
这个函数在文件系统操作中非常有用,特别是当你需要区分目录和常规文件,或者在遍历目录结构时需要验证路径类型。
clearstatcache() 可以清除缓存。
is_dir(string $filename): bool
| 参数 | 描述 |
|---|---|
| filename |
必需。要检查的文件的路径。 可以是相对路径或绝对路径。相对路径将相对于当前工作目录进行解析。 |
| 返回值 | 描述 |
|---|---|
| TRUE | 指定的文件存在并且是一个目录 |
| FALSE | 文件不存在、不是目录、或者发生权限错误 |
<?php
// 检查常见的目录
$paths = [
'/tmp',
'/var/www',
'/etc/passwd',
'/nonexistent',
'.', // 当前目录
'..', // 父目录
__DIR__, // 当前脚本所在目录
__FILE__ // 当前脚本文件
];
foreach ($paths as $path) {
if (is_dir($path)) {
echo "$path 是一个目录<br>";
} else {
echo "$path 不是一个目录<br>";
}
}
?>
<?php
/**
* 列出目录中的文件和子目录
* @param string $directory 要遍历的目录
*/
function listDirectoryContents($directory) {
// 首先检查是否为目录
if (!is_dir($directory)) {
echo "错误: $directory 不是有效的目录";
return;
}
echo "目录: $directory<br>";
echo "<ul>";
// 打开目录并读取内容
if ($handle = opendir($directory)) {
while (false !== ($entry = readdir($handle))) {
// 跳过 . 和 ..
if ($entry === '.' || $entry === '..') {
continue;
}
$fullPath = $directory . '/' . $entry;
if (is_dir($fullPath)) {
echo "<li><strong>[$entry]</strong> (目录)</li>";
} else {
echo "<li>$entry (文件)</li>";
}
}
closedir($handle);
}
echo "</ul>";
}
// 使用示例
listDirectoryContents('/tmp');
?>
<?php
/**
* 递归统计目录中的文件和子目录数量
* @param string $directory 要统计的目录
* @return array 统计信息
*/
function countDirectoryItems($directory) {
$stats = [
'directories' => 0,
'files' => 0,
'size' => 0
];
// 检查是否为目录
if (!is_dir($directory)) {
return $stats;
}
// 打开目录
if ($handle = opendir($directory)) {
while (false !== ($entry = readdir($handle))) {
// 跳过 . 和 ..
if ($entry === '.' || $entry === '..') {
continue;
}
$fullPath = $directory . '/' . $entry;
if (is_dir($fullPath)) {
$stats['directories']++;
// 递归统计子目录
$subStats = countDirectoryItems($fullPath);
$stats['directories'] += $subStats['directories'];
$stats['files'] += $subStats['files'];
$stats['size'] += $subStats['size'];
} else {
$stats['files']++;
$stats['size'] += filesize($fullPath);
}
}
closedir($handle);
}
return $stats;
}
// 使用示例
$directory = '/var/www';
$stats = countDirectoryItems($directory);
echo "目录统计: $directory<br>";
echo "目录数量: " . $stats['directories'] . "<br>";
echo "文件数量: " . $stats['files'] . "<br>";
echo "总大小: " . formatBytes($stats['size']) . "<br>";
// 辅助函数:格式化字节大小
function formatBytes($bytes, $precision = 2) {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}
?>
<?php
/**
* 安全地创建目录(如果不存在)
* @param string $directory 要创建的目录路径
* @param int $permissions 目录权限,默认 0755
* @return bool 是否成功创建或已存在
*/
function createDirectoryIfNotExists($directory, $permissions = 0755) {
// 检查目录是否已存在
if (is_dir($directory)) {
echo "目录已存在: $directory<br>";
return true;
}
// 尝试创建目录
if (mkdir($directory, $permissions, true)) {
echo "目录创建成功: $directory<br>";
return true;
} else {
echo "无法创建目录: $directory<br>";
return false;
}
}
// 使用示例
$directories = [
'/tmp/test1',
'/tmp/test2/subdir',
'/var/www/uploads/images',
'/nonexistent/parent/child' // 这个可能因权限问题失败
];
foreach ($directories as $dir) {
createDirectoryIfNotExists($dir);
}
// 实际应用:为上传文件创建目录
function prepareUploadDirectory($baseDir, $subDir = null) {
$uploadDir = $baseDir;
if ($subDir) {
$uploadDir = $baseDir . '/' . trim($subDir, '/');
}
// 检查并创建目录
if (!is_dir($uploadDir)) {
if (!mkdir($uploadDir, 0755, true)) {
throw new Exception("无法创建上传目录: $uploadDir");
}
}
// 确保目录可写
if (!is_writable($uploadDir)) {
throw new Exception("上传目录不可写: $uploadDir");
}
return $uploadDir;
}
try {
$uploadPath = prepareUploadDirectory('/var/www/uploads', '2023/images');
echo "上传目录准备完成: $uploadPath<br>";
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "<br>";
}
?>
<?php
// 演示 stat 缓存的影响
$file = '/tmp/test_file.txt';
// 创建测试文件
file_put_contents($file, '测试内容');
echo "初始检查:<br>";
echo "is_dir(): " . (is_dir($file) ? 'TRUE' : 'FALSE') . "<br>";
echo "is_file(): " . (is_file($file) ? 'TRUE' : 'FALSE') . "<br><br>";
// 删除文件
unlink($file);
echo "删除文件后(未清除缓存):<br>";
echo "is_dir(): " . (is_dir($file) ? 'TRUE' : 'FALSE') . "<br>";
echo "is_file(): " . (is_file($file) ? 'TRUE' : 'FALSE') . "<br><br>";
// 清除 stat 缓存
clearstatcache();
echo "清除缓存后:<br>";
echo "is_dir(): " . (is_dir($file) ? 'TRUE' : 'FALSE') . "<br>";
echo "is_file(): " . (is_file($file) ? 'TRUE' : 'FALSE') . "<br><br>";
// 实际应用:监控目录变化
class DirectoryMonitor {
private $directory;
private $lastCheck = [];
public function __construct($directory) {
$this->directory = $directory;
$this->refresh();
}
public function refresh() {
clearstatcache(); // 清除缓存以确保获取最新状态
$contents = [];
if (is_dir($this->directory)) {
if ($handle = opendir($this->directory)) {
while (false !== ($entry = readdir($handle))) {
if ($entry !== '.' && $entry !== '..') {
$fullPath = $this->directory . '/' . $entry;
$contents[$entry] = [
'is_dir' => is_dir($fullPath),
'size' => filesize($fullPath),
'modified' => filemtime($fullPath)
];
}
}
closedir($handle);
}
}
$this->lastCheck = $contents;
}
public function getChanges() {
$oldContents = $this->lastCheck;
$this->refresh();
$newContents = $this->lastCheck;
$changes = [
'added' => [],
'removed' => [],
'modified' => []
];
// 检查新增的文件
foreach ($newContents as $name => $info) {
if (!isset($oldContents[$name])) {
$changes['added'][$name] = $info;
}
}
// 检查删除的文件
foreach ($oldContents as $name => $info) {
if (!isset($newContents[$name])) {
$changes['removed'][$name] = $info;
}
}
// 检查修改的文件
foreach ($newContents as $name => $newInfo) {
if (isset($oldContents[$name])) {
$oldInfo = $oldContents[$name];
if ($newInfo['modified'] !== $oldInfo['modified'] ||
$newInfo['size'] !== $oldInfo['size']) {
$changes['modified'][$name] = [
'old' => $oldInfo,
'new' => $newInfo
];
}
}
}
return $changes;
}
}
// 使用示例
$monitor = new DirectoryMonitor('/tmp');
echo "初始目录状态已记录<br><br>";
// 模拟一些变化
file_put_contents('/tmp/new_file.txt', '新文件内容');
mkdir('/tmp/new_dir');
$changes = $monitor->getChanges();
echo "检测到的变化:<br>";
print_r($changes);
?>
<?php
/**
* 检查符号链接指向的是否为目录
*/
function checkSymbolicLink($path) {
// 首先检查是否为符号链接
if (is_link($path)) {
echo "$path 是一个符号链接<br>";
// 获取链接目标
$target = readlink($path);
echo "链接目标: $target<br>";
// 检查目标是否为目录
if (is_dir($target)) {
echo "符号链接指向一个目录<br>";
} else {
echo "符号链接不指向目录<br>";
}
return true;
} else {
// 不是符号链接,直接检查是否为目录
if (is_dir($path)) {
echo "$path 是一个普通目录<br>";
} else {
echo "$path 不是目录<br>";
}
return false;
}
}
// 创建测试符号链接(需要适当权限)
$linkPath = '/tmp/test_link';
// 删除可能存在的旧链接
if (file_exists($linkPath)) {
unlink($linkPath);
}
// 创建指向目录的符号链接
if (symlink('/tmp', $linkPath)) {
echo "创建符号链接成功<br><br>";
// 测试符号链接
checkSymbolicLink($linkPath);
echo "<br>";
// 测试普通目录
checkSymbolicLink('/tmp');
echo "<br>";
// 测试普通文件
$testFile = '/tmp/test.txt';
file_put_contents($testFile, '测试');
checkSymbolicLink($testFile);
unlink($testFile);
}
// 清理
if (file_exists($linkPath)) {
unlink($linkPath);
}
// 实际应用:安全地解析符号链接
function resolvePath($path) {
// 如果是符号链接,解析它
if (is_link($path)) {
$target = readlink($path);
// 处理相对路径链接
if ($target[0] !== '/') {
$target = dirname($path) . '/' . $target;
}
// 递归解析
return resolvePath($target);
}
return $path;
}
echo "<br>路径解析示例:<br>";
// 创建多级符号链接进行测试
$link1 = '/tmp/link1';
$link2 = '/tmp/link2';
if (file_exists($link1)) unlink($link1);
if (file_exists($link2)) unlink($link2);
symlink('/tmp', $link1);
symlink($link1, $link2);
echo "link2 最终指向: " . resolvePath($link2) . "<br>";
echo "是否为目录: " . (is_dir(resolvePath($link2)) ? '是' : '否') . "<br>";
// 清理
unlink($link1);
unlink($link2);
?>
| 函数 | 描述 | 示例返回值 |
|---|---|---|
| is_dir() | 检查是否为目录 | /tmp → TRUE, /etc/passwd → FALSE |
| is_file() | 检查是否为常规文件 | /etc/passwd → TRUE, /tmp → FALSE |
| is_link() | 检查是否为符号链接 | 符号链接 → TRUE,其他 → FALSE |
| file_exists() | 检查文件或目录是否存在 | 存在 → TRUE,不存在 → FALSE |
| is_readable() | 检查是否可读 | 有读取权限 → TRUE |
| is_writable() | 检查是否可写 | 有写入权限 → TRUE |
重要关系:
is_dir($path) 返回 TRUE,那么 file_exists($path) 也一定返回 TRUEfile_exists($path) 返回 FALSE,那么 is_dir($path) 也一定返回 FALSEfile_exists($path) 返回 TRUE,is_dir($path) 可能返回 TRUE 或 FALSE(取决于路径类型)@is_dir() 避免不必要的警告clearstatcache()is_link() 和 readlink() 处理符号链接SplFileInfo 类提供更丰富的功能| 错误 | 原因 | 解决方法 |
|---|---|---|
| Warning: is_dir(): open_basedir restriction in effect | 路径超出了 open_basedir 的限制 | 检查 open_basedir 配置,或使用允许范围内的路径 |
| 返回 FALSE 但目录存在 | 权限不足或符号链接问题 | 检查权限,或使用 is_link() 检查是否为符号链接 |
| 缓存导致的问题 | 文件状态已改变但未清除缓存 | 使用 clearstatcache() 清除缓存 |
| 相对路径问题 | 当前工作目录改变导致相对路径解析错误 | 使用绝对路径,或保存当前工作目录 |
| 符号链接返回 FALSE | is_dir() 对符号链接返回 FALSE | 先检查是否为符号链接,再检查链接目标 |
PHP 会缓存文件系统 stat 调用结果,多次调用 is_dir() 检查同一文件通常只有第一次有实际 I/O 开销。
批量检查目录时,考虑使用 scandir() 获取所有条目,然后批量处理,而不是对每个路径单独调用 is_dir()。
如果已经知道路径类型(例如从 opendir() 获取),避免重复调用 is_dir()。
对于简单的存在性检查,file_exists() 可能比 is_dir() 稍快,但功能不同。