PHP pathinfo() 函数

定义和用法

pathinfo() 函数返回文件路径的信息。它可以以数组形式返回路径的多个组成部分,也可以单独返回指定的部分。

pathinfo() 函数非常有用,特别是当需要同时获取文件路径的目录名、文件名、扩展名等信息时,它比单独使用 dirname()、basename() 等函数更高效。

注意:pathinfo() 函数不会检查文件或目录是否存在,它只是对路径字符串进行操作。从 PHP 5.2.0 开始,可以使用第二个参数来指定要返回的特定部分。

语法

pathinfo(string $path, int $flags = PATHINFO_ALL): mixed

参数

参数 描述
path

必需。一个文件路径字符串。

可以是绝对路径或相对路径。Windows 和 Unix/Linux 路径都支持。

flags

可选。指定要返回的信息。可以是以下常量之一:

  • PATHINFO_DIRNAME - 返回目录名
  • PATHINFO_BASENAME - 返回文件名(带扩展名)
  • PATHINFO_EXTENSION - 返回文件扩展名
  • PATHINFO_FILENAME - 返回文件名(不带扩展名)
  • PATHINFO_ALL(默认)- 返回包含所有信息的数组

返回值

返回值 描述
array flagsPATHINFO_ALL 或未指定时,返回包含所有信息的关联数组
string flags 为其他值时,返回指定的字符串部分

示例

示例1:基本用法(返回数组)

<?php
// 基本用法:返回所有信息
$path = "/var/www/html/project/index.php";
$info = pathinfo($path);

echo "完整路径: $path<br><br>";
echo "pathinfo() 返回的数组:<br>";
print_r($info);
echo "<br><br>";

// 访问数组元素
echo "目录名: " . $info['dirname'] . "<br>";
echo "文件名: " . $info['basename'] . "<br>";
echo "扩展名: " . $info['extension'] . "<br>";
echo "文件名(无扩展名): " . $info['filename'] . "<br><br>";

// 其他路径示例
$paths = [
    "/home/user/docs/report.pdf",
    "C:\\Windows\\System32\\cmd.exe",
    "相对路径/file.txt",
    "file",
    ".",
    "..",
    "/",
    "C:\\"
];

foreach ($paths as $p) {
    echo "路径: " . htmlspecialchars($p) . "<br>";
    print_r(pathinfo($p));
    echo "<br><br>";
}
?>

示例2:使用 flags 参数获取特定部分

<?php
// 演示 flags 参数的使用
$path = "/var/www/html/project/src/controller/UserController.php";

echo "完整路径: $path<br><br>";

// 获取特定部分
echo "PATHINFO_DIRNAME: " . pathinfo($path, PATHINFO_DIRNAME) . "<br>";
echo "PATHINFO_BASENAME: " . pathinfo($path, PATHINFO_BASENAME) . "<br>";
echo "PATHINFO_EXTENSION: " . pathinfo($path, PATHINFO_EXTENSION) . "<br>";
echo "PATHINFO_FILENAME: " . pathinfo($path, PATHINFO_FILENAME) . "<br><br>";

// 使用常量对应的数值(了解即可)
echo "使用数值常量:<br>";
echo "1 (PATHINFO_DIRNAME): " . pathinfo($path, 1) . "<br>";
echo "2 (PATHINFO_BASENAME): " . pathinfo($path, 2) . "<br>";
echo "4 (PATHINFO_EXTENSION): " . pathinfo($path, 4) . "<br>";
echo "8 (PATHINFO_FILENAME): " . pathinfo($path, 8) . "<br><br>";

// 组合使用(通过位或运算)
echo "组合使用(目录名和扩展名):<br>";
$combined = PATHINFO_DIRNAME | PATHINFO_EXTENSION;
$result = pathinfo($path, $combined);
echo "结果: ";
print_r($result);
echo "<br><br>";

// 实际应用:根据需求获取信息
function getFileInfo($path, $infoType = 'all') {
    switch ($infoType) {
        case 'dirname':
            return pathinfo($path, PATHINFO_DIRNAME);
        case 'basename':
            return pathinfo($path, PATHINFO_BASENAME);
        case 'extension':
            return pathinfo($path, PATHINFO_EXTENSION);
        case 'filename':
            return pathinfo($path, PATHINFO_FILENAME);
        default:
            return pathinfo($path);
    }
}

echo "使用封装函数:<br>";
echo "目录名: " . getFileInfo($path, 'dirname') . "<br>";
echo "扩展名: " . getFileInfo($path, 'extension') . "<br>";
?>

示例3:处理各种文件类型和扩展名

<?php
/**
 * 演示 pathinfo() 处理各种文件类型
 */
function analyzeFile($path) {
    $info = pathinfo($path);

    echo "分析文件: " . htmlspecialchars($path) . "<br>";
    echo "目录名: " . (isset($info['dirname']) ? $info['dirname'] : 'N/A') . "<br>";
    echo "文件名: " . (isset($info['basename']) ? $info['basename'] : 'N/A') . "<br>";
    echo "扩展名: " . (isset($info['extension']) ? $info['extension'] : '无扩展名') . "<br>";
    echo "文件名(无扩展名): " . (isset($info['filename']) ? $info['filename'] : 'N/A') . "<br><br>";
}

// 测试各种文件类型
$testFiles = [
    // 常见扩展名
    '/var/www/html/index.php',
    '/home/user/document.pdf',
    '/tmp/image.jpg',
    '/usr/bin/python3',

    // 点号在文件名中
    '/path/to/version.2.0.1.tar.gz',
    '/backup/file.2023-01-01.bak',

    // 无扩展名
    '/etc/hosts',
    '/usr/bin/bash',
    'README',

    // 隐藏文件
    '/home/user/.bashrc',
    '/home/user/.gitignore',

    // 多扩展名
    '/archive/file.tar.gz',
    '/archive/file.phar.gz',

    // 特殊字符
    '/path/to/file with spaces.txt',
    '/path/to/file-with-dashes_and_underscores.pdf',

    // 相对路径
    './config/settings.ini',
    '../logs/error.log',
];

foreach ($testFiles as $file) {
    analyzeFile($file);
}

// 特殊处理:对于隐藏文件(以点开头的文件)
function getRealFilename($path) {
    $info = pathinfo($path);
    $basename = $info['basename'];

    // 如果是隐藏文件(以.开头),则整个basename都是文件名
    if (strpos($basename, '.') === 0) {
        return [
            'filename' => $basename,
            'extension' => '',
            'is_hidden' => true
        ];
    }

    return [
        'filename' => isset($info['filename']) ? $info['filename'] : $basename,
        'extension' => isset($info['extension']) ? $info['extension'] : '',
        'is_hidden' => false
    ];
}

echo "<h4>隐藏文件处理示例</h4>";
$hiddenFiles = ['.bashrc', '.gitignore', '.env.example', 'normal.txt'];
foreach ($hiddenFiles as $file) {
    $result = getRealFilename($file);
    echo "文件: $file - 文件名: {$result['filename']}, 扩展名: {$result['extension']}, 隐藏: " .
         ($result['is_hidden'] ? '是' : '否') . "<br>";
}
?>

示例4:与 dirname() 和 basename() 的比较

<?php
// 比较 pathinfo()、dirname() 和 basename()
$path = "/var/www/html/project/index.php";

echo "完整路径: $path<br><br>";

echo "<h4>使用 pathinfo()</h4>";
$info = pathinfo($path);
echo "目录名: " . $info['dirname'] . "<br>";
echo "文件名: " . $info['basename'] . "<br>";
echo "扩展名: " . $info['extension'] . "<br>";
echo "文件名(无扩展名): " . $info['filename'] . "<br><br>";

echo "<h4>使用 dirname() 和 basename()</h4>";
echo "目录名: " . dirname($path) . "<br>";
echo "文件名: " . basename($path) . "<br>";
echo "文件名(无扩展名): " . basename($path, '.php') . "<br><br>";

// 性能比较
function comparePerformance($path, $iterations = 10000) {
    $start1 = microtime(true);
    for ($i = 0; $i < $iterations; $i++) {
        $info = pathinfo($path);
        $dirname = $info['dirname'];
        $basename = $info['basename'];
        $extension = $info['extension'];
        $filename = $info['filename'];
    }
    $time1 = microtime(true) - $start1;

    $start2 = microtime(true);
    for ($i = 0; $i < $iterations; $i++) {
        $dirname = dirname($path);
        $basename = basename($path);
        $extension = pathinfo($path, PATHINFO_EXTENSION);
        $filename = basename($path, '.' . pathinfo($path, PATHINFO_EXTENSION));
    }
    $time2 = microtime(true) - $start2;

    return [
        'pathinfo' => $time1,
        'separate' => $time2,
        'difference' => $time2 - $time1,
        'percentage' => ($time1 / $time2) * 100
    ];
}

echo "<h4>性能比较(10000次迭代)</h4>";
$performance = comparePerformance($path, 10000);
echo "pathinfo(): " . round($performance['pathinfo'] * 1000, 3) . " 毫秒<br>";
echo "分开调用: " . round($performance['separate'] * 1000, 3) . " 毫秒<br>";
echo "差异: " . round($performance['difference'] * 1000, 3) . " 毫秒<br>";
echo "pathinfo() 效率: " . round($performance['percentage'], 2) . "%<br><br>";

// 实际建议
echo "<h4>使用建议</h4>";
echo "1. 当需要获取多个路径信息时,使用 pathinfo() 更高效<br>";
echo "2. 当只需要一个信息(如目录名)时,使用 dirname() 可能更直接<br>";
echo "3. pathinfo() 返回的数组便于一次获取所有信息,代码更简洁<br>";
?>

示例5:实际应用 - 文件上传处理

<?php
/**
 * 文件上传处理类
 */
class FileUploadHandler {

    /**
     * 处理上传的文件信息
     */
    public static function processUpload($uploadedFile) {
        // 获取文件信息
        $fileInfo = pathinfo($uploadedFile['name']);

        // 构建安全文件名
        $safeFilename = self::generateSafeFilename($fileInfo);

        // 验证文件类型
        $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx'];
        if (!in_array(strtolower($fileInfo['extension']), $allowedExtensions)) {
            throw new Exception("文件类型不允许");
        }

        // 验证文件大小(最大5MB)
        $maxSize = 5 * 1024 * 1024; // 5MB
        if ($uploadedFile['size'] > $maxSize) {
            throw new Exception("文件太大,最大允许5MB");
        }

        return [
            'original_name' => $uploadedFile['name'],
            'safe_name' => $safeFilename,
            'extension' => $fileInfo['extension'],
            'size' => $uploadedFile['size'],
            'mime_type' => $uploadedFile['type'],
            'temp_path' => $uploadedFile['tmp_name']
        ];
    }

    /**
     * 生成安全的文件名
     */
    private static function generateSafeFilename($fileInfo) {
        // 清理文件名(移除特殊字符)
        $cleanName = preg_replace('/[^a-zA-Z0-9._-]/', '_', $fileInfo['filename']);

        // 限制长度
        $maxLength = 100;
        if (strlen($cleanName) > $maxLength) {
            $cleanName = substr($cleanName, 0, $maxLength);
        }

        // 添加时间戳防止重名
        $timestamp = time();
        $random = rand(1000, 9999);

        // 重建完整文件名
        $safeName = $cleanName . '_' . $timestamp . '_' . $random;
        if (!empty($fileInfo['extension'])) {
            $safeName .= '.' . $fileInfo['extension'];
        }

        return $safeName;
    }

    /**
     * 保存上传的文件
     */
    public static function saveUpload($uploadInfo, $targetDir) {
        // 确保目标目录存在
        if (!is_dir($targetDir)) {
            mkdir($targetDir, 0755, true);
        }

        // 构建完整目标路径
        $targetPath = $targetDir . '/' . $uploadInfo['safe_name'];

        // 移动文件
        if (move_uploaded_file($uploadInfo['temp_path'], $targetPath)) {
            return $targetPath;
        } else {
            throw new Exception("文件保存失败");
        }
    }

    /**
     * 获取文件类型信息
     */
    public static function getFileTypeInfo($path) {
        $info = pathinfo($path);

        // 常见文件类型映射
        $typeMap = [
            'image' => ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'],
            'document' => ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'rtf'],
            'archive' => ['zip', 'rar', 'tar', 'gz', '7z'],
            'code' => ['php', 'js', 'css', 'html', 'htm', 'json', 'xml', 'py', 'java'],
            'video' => ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv'],
            'audio' => ['mp3', 'wav', 'ogg', 'm4a', 'flac']
        ];

        $extension = strtolower($info['extension'] ?? '');
        $fileType = 'other';

        foreach ($typeMap as $type => $extensions) {
            if (in_array($extension, $extensions)) {
                $fileType = $type;
                break;
            }
        }

        return [
            'filename' => $info['filename'] ?? '',
            'extension' => $extension,
            'type' => $fileType,
            'full_path' => $path
        ];
    }
}

// 使用示例
echo "<h4>文件上传处理示例</h4>";

// 模拟上传的文件(实际环境中来自 $_FILES 数组)
$uploadedFile = [
    'name' => '用户文件-测试 (1).pdf',
    'type' => 'application/pdf',
    'size' => 204800, // 200KB
    'tmp_name' => '/tmp/php1234.tmp',
    'error' => 0
];

try {
    // 处理上传
    $fileInfo = FileUploadHandler::processUpload($uploadedFile);

    echo "原始文件名: " . $fileInfo['original_name'] . "<br>";
    echo "安全文件名: " . $fileInfo['safe_name'] . "<br>";
    echo "文件扩展名: " . $fileInfo['extension'] . "<br>";
    echo "文件大小: " . round($fileInfo['size'] / 1024, 2) . " KB<br>";
    echo "MIME 类型: " . $fileInfo['mime_type'] . "<br><br>";

    // 保存文件
    $targetDir = '/var/www/uploads';
    $savedPath = FileUploadHandler::saveUpload($fileInfo, $targetDir);
    echo "文件保存到: $savedPath<br><br>";

    // 获取文件类型信息
    $typeInfo = FileUploadHandler::getFileTypeInfo($savedPath);
    echo "文件类型分析:<br>";
    echo "文件名: " . $typeInfo['filename'] . "<br>";
    echo "扩展名: " . $typeInfo['extension'] . "<br>";
    echo "文件类型: " . $typeInfo['type'] . "<br>";

} catch (Exception $e) {
    echo "错误: " . $e->getMessage() . "<br>";
}

// 批量处理示例
echo "<br><h4>批量文件类型分析</h4>";
$files = [
    '/var/www/html/image.jpg',
    '/var/www/html/document.pdf',
    '/var/www/html/script.js',
    '/var/www/html/archive.zip',
    '/var/www/html/video.mp4',
    '/var/www/html/readme.txt'
];

foreach ($files as $file) {
    $info = FileUploadHandler::getFileTypeInfo($file);
    $basename = basename($file);
    echo "$basename - 类型: {$info['type']}, 扩展名: {$info['extension']}<br>";
}
?>

示例6:高级路径处理工具类

<?php
/**
 * 高级路径处理工具类
 */
class PathUtils {

    /**
     * 获取路径的所有可能信息
     */
    public static function getCompletePathInfo($path) {
        $info = pathinfo($path);

        // 确保所有键都存在
        $defaults = [
            'dirname' => '.',
            'basename' => basename($path),
            'extension' => '',
            'filename' => ''
        ];

        $info = array_merge($defaults, $info);

        // 如果没有 filename 但 basename 存在
        if (empty($info['filename']) && !empty($info['basename'])) {
            if (!empty($info['extension'])) {
                $info['filename'] = substr($info['basename'], 0, -strlen($info['extension']) - 1);
            } else {
                $info['filename'] = $info['basename'];
            }
        }

        // 添加额外信息
        $info['absolute'] = realpath($path) ?: $path;
        $info['exists'] = file_exists($path);
        $info['is_dir'] = is_dir($path);
        $info['is_file'] = is_file($path);
        $info['size'] = $info['exists'] && $info['is_file'] ? filesize($path) : 0;
        $info['modified'] = $info['exists'] ? filemtime($path) : 0;

        return $info;
    }

    /**
     * 安全地更改文件扩展名
     */
    public static function changeExtension($path, $newExtension) {
        $info = pathinfo($path);

        // 如果原路径没有扩展名
        if (empty($info['extension'])) {
            if (!empty($newExtension)) {
                return $info['dirname'] . '/' . $info['filename'] . '.' . ltrim($newExtension, '.');
            }
            return $path;
        }

        // 如果有扩展名,替换它
        if (!empty($newExtension)) {
            return $info['dirname'] . '/' . $info['filename'] . '.' . ltrim($newExtension, '.');
        } else {
            // 移除扩展名
            return $info['dirname'] . '/' . $info['filename'];
        }
    }

    /**
     * 比较两个路径是否指向同一个文件(规范化比较)
     */
    public static function comparePaths($path1, $path2) {
        $real1 = realpath($path1) ?: $path1;
        $real2 = realpath($path2) ?: $path2;

        // 规范化路径分隔符
        $normalized1 = str_replace('\\', '/', $real1);
        $normalized2 = str_replace('\\', '/', $real2);

        return rtrim($normalized1, '/') === rtrim($normalized2, '/');
    }

    /**
     * 获取文件的相对路径
     */
    public static function getRelativePath($from, $to) {
        $from = str_replace('\\', '/', realpath($from) ?: $from);
        $to = str_replace('\\', '/', realpath($to) ?: $to);

        $fromParts = explode('/', trim($from, '/'));
        $toParts = explode('/', trim($to, '/'));

        // 移除相同的部分
        while (count($fromParts) && count($toParts) && $fromParts[0] === $toParts[0]) {
            array_shift($fromParts);
            array_shift($toParts);
        }

        // 构建相对路径
        $relativePath = str_repeat('../', count($fromParts)) . implode('/', $toParts);

        return $relativePath ?: '.';
    }

    /**
     * 批量处理路径信息
     */
    public static function batchProcess(array $paths, callable $processor) {
        $results = [];

        foreach ($paths as $index => $path) {
            $info = pathinfo($path);
            $results[$index] = $processor($info, $path);
        }

        return $results;
    }
}

// 使用示例
echo "<h4>高级路径处理示例</h4>";

$testPath = "/var/www/html/project/src/controllers/UserController.php";

// 1. 获取完整路径信息
echo "<h5>1. 完整路径信息</h5>";
$completeInfo = PathUtils::getCompletePathInfo($testPath);
foreach ($completeInfo as $key => $value) {
    if (is_bool($value)) {
        $value = $value ? 'true' : 'false';
    }
    echo "$key: " . htmlspecialchars($value) . "<br>";
}

// 2. 更改扩展名
echo "<h5>2. 更改扩展名</h5>";
echo "原路径: $testPath<br>";
echo "改为 .html: " . PathUtils::changeExtension($testPath, 'html') . "<br>";
echo "改为 .class.php: " . PathUtils::changeExtension($testPath, 'class.php') . "<br>";
echo "移除扩展名: " . PathUtils::changeExtension($testPath, '') . "<br><br>";

// 3. 比较路径
echo "<h5>3. 路径比较</h5>";
$path1 = "/var/www/html/index.php";
$path2 = "/var/www/./html/index.php";
$path3 = "/var/www/html/../html/index.php";
$path4 = "/var/www/html/other.php";

echo "比较 $path1 和 $path2: " .
     (PathUtils::comparePaths($path1, $path2) ? '相同' : '不同') . "<br>";
echo "比较 $path1 和 $path3: " .
     (PathUtils::comparePaths($path1, $path3) ? '相同' : '不同') . "<br>";
echo "比较 $path1 和 $path4: " .
     (PathUtils::comparePaths($path1, $path4) ? '相同' : '不同') . "<br><br>";

// 4. 获取相对路径
echo "<h5>4. 相对路径计算</h5>";
$from = "/var/www/html";
$to = "/var/www/html/css/style.css";
echo "从 $from 到 $to 的相对路径: " . PathUtils::getRelativePath($from, $to) . "<br>";

$from = "/var/www/html/css";
$to = "/var/www/html/js/script.js";
echo "从 $from 到 $to 的相对路径: " . PathUtils::getRelativePath($from, $to) . "<br><br>";

// 5. 批量处理
echo "<h5>5. 批量处理</h5>";
$paths = [
    '/var/www/html/index.php',
    '/var/www/html/style.css',
    '/var/www/html/script.js',
    '/var/www/html/.htaccess'
];

$processor = function($info, $path) {
    return [
        'name' => $info['filename'],
        'ext' => $info['extension'] ?? '',
        'type' => in_array($info['extension'] ?? '', ['php', 'js', 'css']) ? 'code' : 'other'
    ];
};

$batchResults = PathUtils::batchProcess($paths, $processor);
foreach ($batchResults as $index => $result) {
    $path = $paths[$index];
    echo "路径: " . basename($path) . " - 名称: {$result['name']}, 扩展名: {$result['ext']}, 类型: {$result['type']}<br>";
}
?>

pathinfo() 返回的数组结构

键名 描述 示例
dirname 文件所在的目录路径 /var/www/html
basename 文件名(包含扩展名) index.php
extension 文件扩展名(如果有) php
filename 文件名(不包含扩展名) index

pathinfo() 在不同情况下的行为

重要行为:
  • 对于隐藏文件(以 . 开头的文件),extension 为空,filename 包含整个文件名
  • 对于没有扩展名的文件,extension 键不存在或为空字符串
  • 对于目录路径,basename 是目录名,extension 不存在
  • 当路径以分隔符结尾时,basename 可能是空字符串
  • PATHINFO_FILENAME 常量从 PHP 5.2.0 开始可用

最佳实践

  1. 检查键是否存在:使用返回的数组前,检查需要的键是否存在,特别是 extension
  2. 处理隐藏文件:对于以点开头的文件,使用专门的处理逻辑
  3. 结合 realpath() 使用:如果需要操作实际文件,先用 realpath() 规范化路径
  4. 使用 flags 参数提高性能:如果只需要部分信息,使用 flags 参数只获取所需部分
  5. 处理用户输入要小心:用户提供的路径可能包含特殊字符或目录遍历序列
  6. 考虑使用 SplFileInfo:对于面向对象的代码,考虑使用 SplFileInfo
  7. 注意多字节字符:处理包含非 ASCII 字符的路径时,使用多字节安全的函数

常见错误

错误 原因 解决方法
未定义的数组键 'extension' 文件没有扩展名,但代码直接访问 $info['extension'] 使用 isset($info['extension']) 检查或使用 null 合并运算符
隐藏文件扩展名处理错误 隐藏文件如 .envfilename.env 而不是空 专门处理以点开头的文件
路径中的多个点号 file.tar.gz 只会识别 .gz 为扩展名 根据需求自定义扩展名解析逻辑
目录路径的处理 对目录路径使用 pathinfo() 可能返回意外结果 先用 is_dir() 检查路径类型
Windows 路径问题 Windows 路径中的反斜杠可能导致解析错误 先用 str_replace('\\', '/', $path) 标准化路径

相关函数