路径处理
概述
在 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');