Skip to content

路径处理

概述

在 PHP 中处理文件路径时,需要考虑跨平台兼容性(Windows 使用 \,Unix 使用 /)。PHP 提供了 pathinfo()basename()dirname()realpath() 等函数来解析和操作路径。glob() 函数支持模式匹配路径。使用 DIRECTORY_SEPARATOR 常量可以编写跨平台代码。

适用场景

  • 文件上传后生成存储路径
  • 解析 URL 路径获取文件名
  • 构建跨平台兼容的文件路径
  • 日志文件按日期自动归档

基础概念

核心路径函数

函数功能返回值
pathinfo()返回路径组成部分array|string
basename()获取文件名部分string
dirname()获取目录名部分string
realpath()返回规范化的绝对路径string|false
glob()模式匹配路径array|false
is_dir()判断是否为目录bool
file_exists()判断路径是否存在bool

路径相关常量

php
<?php

echo DIRECTORY_SEPARATOR; // Unix: '/'  Windows: '\'
echo PHP_EOL;
echo PATH_SEPARATOR;     // Unix: ':'  Windows: ';'

PHP 8.0+ 变更

pathinfo()$flags 参数支持 PATHINFO_FILENAME,返回不含扩展名的文件名。

语法与代码示例

pathinfo 解析路径

php
<?php

$path = '/var/www/html/project/src/Controller/UserController.php';

$info = pathinfo($path);
print_r($info);
/*
[
    'dirname'   => '/var/www/html/project/src/Controller',
    'basename'  => 'UserController.php',
    'extension' => 'php',
    'filename'  => 'UserController',
]
*/

// 只获取特定部分
$ext = pathinfo($path, PATHINFO_EXTENSION);    // 'php'
$name = pathinfo($path, PATHINFO_BASENAME);     // 'UserController.php'
$dir = pathinfo($path, PATHINFO_DIRNAME);       // '/var/www/html/project/src/Controller'
$filename = pathinfo($path, PATHINFO_FILENAME); // 'UserController'

// 处理多重扩展名
$path = 'archive.tar.gz';
echo pathinfo($path, PATHINFO_EXTENSION); // 'gz'(只返回最后一个扩展名)

// 自定义处理多重扩展名
function getExtension(string $path): string
{
    $filename = basename($path);
    $parts = explode('.', $filename);
    return count($parts) > 1 ? implode('.', array_slice($parts, 1)) : '';
}

echo getExtension('archive.tar.gz'); // 'tar.gz'

basename 和 dirname

php
<?php

// basename 获取文件名
echo basename('/var/www/html/index.php');           // 'index.php'
echo basename('/var/www/html/index.php', '.php');   // 'index'
echo basename('/var/www/html/.htaccess');           // '.htaccess'

// dirname 获取目录路径
echo dirname('/var/www/html/index.php');           // '/var/www/html'
echo dirname('/var/www/html/');                    // '/var/www'
echo dirname('/var/www/html');                     // '/var/www'

// 获取多层目录
echo dirname('/var/www/html/index.php', 2);        // '/var/www'

realpath 规范化路径

php
<?php

// realpath 返回规范化的绝对路径(解析 . 和 ..)
echo realpath('/tmp/../tmp/test.txt');    // '/tmp/test.txt'
echo realpath('./src/index.php');          // '/var/www/html/project/src/index.php'

// 文件不存在时返回 false
$result = realpath('/tmp/nonexistent.txt');
if ($result === false) {
    echo "路径不存在\n";
}

// PHP 8.0+ 可以检查文件是否存在再获取真实路径
function safeRealpath(string $path): ?string
{
    $real = realpath($path);
    return $real ?: null;
}

glob 路径模式匹配

php
<?php

// 匹配所有 PHP 文件
$files = glob('/var/www/html/*.php');

// 递归匹配(** 操作符,PHP 7.2+)
$allPhp = glob('/var/www/html/**/*.php', GLOB_BRACE);

// 匹配多种扩展名
$images = glob('/var/www/html/uploads/*.{jpg,jpeg,png,gif}', GLOB_BRACE);

// 仅匹配目录
$dirs = glob('/var/www/html/*', GLOB_ONLYDIR);

// 包含隐藏文件(以 . 开头)
$all = glob('/var/www/html/{,.}*', GLOB_BRACE);

跨平台路径构建

php
<?php

// 使用 DIRECTORY_SEPARATOR 构建跨平台路径
function buildPath(string ...$parts): string
{
    return implode(DIRECTORY_SEPARATOR, $parts);
}

$path = buildPath('var', 'www', 'html', 'index.php');
echo $path; // Unix: var/www/html/index.php  Windows: var\www\html\index.php

// 更健壮的方式(自动处理首尾斜杠)
function joinPath(string ...$parts): string
{
    $result = '';
    foreach ($parts as $part) {
        if ($part === '') continue;
        $result = rtrim($result, '/\\') . DIRECTORY_SEPARATOR . ltrim($part, '/\\');
    }
    return $result;
}

echo joinPath('/var/www/', '/html/', 'project');

实战示例

上传文件路径管理

php
<?php

declare(strict_types=1);

class UploadPathManager
{
    private string $baseDir;

    public function __construct(string $baseDir)
    {
        $this->baseDir = rtrim($baseDir, '/\\');
    }

    /**
     * 按日期组织上传路径
     * /uploads/2026/07/12/uuid.jpg
     */
    public function generatePath(string $extension, string $prefix = ''): string
    {
        $dateDir = date('Y/m/d');
        $dir = $this->baseDir . DIRECTORY_SEPARATOR . $dateDir;

        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }

        $filename = $prefix . bin2hex(random_bytes(16)) . '.' . $extension;

        return $dir . DIRECTORY_SEPARATOR . $filename;
    }

    /**
     * 获取相对 Web 路径
     */
    public function getWebPath(string $absolutePath): string
    {
        return str_replace(
            [$this->baseDir, DIRECTORY_SEPARATOR],
            ['', '/'],
            $absolutePath
        );
    }

    /**
     * 验证路径是否在基础目录内
     */
    public function isWithinBaseDir(string $path): bool
    {
        $realPath = realpath($path);
        $realBase = realpath($this->baseDir);

        if ($realPath === false || $realBase === false) {
            return false;
        }

        return str_starts_with($realPath, $realBase . DIRECTORY_SEPARATOR);
    }
}

// 使用示例
$manager = new UploadPathManager('/var/www/html/uploads');

$absolutePath = $manager->generatePath('jpg');
echo "存储路径: {$absolutePath}\n";

$webPath = $manager->getWebPath($absolutePath);
echo "Web 路径: {$webPath}\n"; // /2026/07/12/xxxx.jpg

路径工具类

php
<?php

declare(strict_types=1);

class PathHelper
{
    /**
     * 获取文件扩展名(小写)
     */
    public static function getExtension(string $path): string
    {
        return strtolower(pathinfo($path, PATHINFO_EXTENSION));
    }

    /**
     * 替换文件扩展名
     */
    public static function replaceExtension(string $path, string $newExt): string
    {
        $dir = pathinfo($path, PATHINFO_DIRNAME);
        $filename = pathinfo($path, PATHINFO_FILENAME);
        $ext = ltrim($newExt, '.');

        return $dir . DIRECTORY_SEPARATOR . $filename . '.' . $ext;
    }

    /**
     * 确保路径以斜杠结尾
     */
    public static function ensureTrailingSlash(string $path): string
    {
        return rtrim($path, '/\\') . DIRECTORY_SEPARATOR;
    }

    /**
     * 规范化路径(解析 . 和 ..,但不要求文件存在)
     */
    public static function normalize(string $path): string
    {
        $parts = explode(DIRECTORY_SEPARATOR, $path);
        $normalized = [];

        foreach ($parts as $part) {
            if ($part === '' || $part === '.') {
                continue;
            }
            if ($part === '..') {
                if (!empty($normalized)) {
                    array_pop($normalized);
                }
                continue;
            }
            $normalized[] = $part;
        }

        return implode(DIRECTORY_SEPARATOR, $normalized);
    }

    /**
     * 相对路径转绝对路径
     */
    public static function absolute(string $path, string $base): string
    {
        if (str_starts_with($path, '/')) {
            return $path; // 已经是绝对路径
        }

        $absolute = $base . DIRECTORY_SEPARATOR . $path;
        return self::normalize($absolute);
    }
}

// 使用示例
echo PathHelper::getExtension('/tmp/photo.JPG') . PHP_EOL; // 'jpg'
echo PathHelper::replaceExtension('/tmp/old.txt', 'md') . PHP_EOL; // '/tmp/old.md'
echo PathHelper::normalize('/tmp/../var/www/./html') . PHP_EOL; // 'var/www/html'
echo PathHelper::absolute('src/index.php', '/var/www/html/project') . PHP_EOL;

注意事项

路径遍历攻击防护

php
<?php

// 危险:用户输入路径可能导致遍历攻击
$userInput = '../../../etc/passwd';
include('/var/www/html/' . $userInput); // 读取系统文件!

// 安全:验证路径在允许范围内
function safeInclude(string $basePath, string $userPath): string
{
    $fullPath = realpath($basePath . DIRECTORY_SEPARATOR . $userPath);
    $realBase = realpath($basePath);

    if ($fullPath === false || !str_starts_with($fullPath, $realBase)) {
        throw new InvalidArgumentException('非法路径');
    }

    return $fullPath;
}

$safePath = safeInclude('/var/www/html/views', 'user/profile.php');

Windows 路径注意事项

php
<?php

// Windows 路径可能包含盘符和反斜杠
// realpath 在 Windows 上会返回盘符格式
$path = realpath('C:\Users\test\file.txt'); // 'C:\Users\test\file.txt'

// glob 在 Windows 上使用反斜杠
$files = glob('C:\\Users\\test\\*.txt');

// 跨平台路径比较时使用 realpath
$both = realpath('/tmp/test') === realpath('/tmp/./test'); // true

最佳实践

1. 使用 DIR 构建路径

php
<?php

// 使用 __DIR__ 而非 __FILE__ 获取目录路径
$configPath = __DIR__ . '/../config/app.php';

// require 使用 __DIR__ 更安全
require_once __DIR__ . '/vendor/autoload.php';

2. 避免硬编码路径

php
<?php

// 不好
$logFile = '/var/www/html/logs/app.log';

// 好:使用常量或环境变量
define('ROOT_DIR', dirname(__DIR__));
$logFile = ROOT_DIR . DIRECTORY_SEPARATOR . 'logs' . DIRECTORY_SEPARATOR . 'app.log';

// 更好:使用配置
$logFile = getenv('LOG_FILE') ?: ROOT_DIR . '/logs/app.log';

3. 路径处理统一使用工具类

php
<?php

// 项目中统一使用 PathHelper
$uploadDir = PathHelper::ensureTrailingSlash(ROOT_DIR . '/uploads');
$thumbnail = PathHelper::replaceExtension($imagePath, 'thumb.jpg');

参考链接