PHP tempnam() 函数

定义和用法

tempnam() 函数用于创建一个具有唯一文件名的临时文件。这个函数会创建一个临时文件并返回其路径,确保不会与现有文件冲突。

tmpfile() 不同,tempnam() 只创建文件名而不自动打开文件,且文件不会被自动删除。这给了开发者更多的控制权,但也意味着需要手动清理。

安全提示: tempnam() 创建的文件名是唯一的,但文件本身是空的。需要确保对该文件有正确的权限设置,特别是在共享环境中。

语法

tempnam ( string $directory , string $prefix ) : string|false

参数说明:

  • $directory:临时文件将被创建的目录(必需)
  • $prefix:临时文件名的前缀(必需)

参数详解

参数 描述
directory

临时文件将被创建的目录。必需参数。

  • 必须是存在的目录
  • PHP进程必须有该目录的写入权限
  • 如果目录不存在或不可写,函数将使用系统临时目录
  • 传递空字符串或 null 将使用系统临时目录
prefix

临时文件名的前缀。必需参数。

  • 用于生成文件名的前缀
  • 最多使用前5个字符(某些系统可能限制为3个)
  • 建议使用有意义的标识符,如"tmp_"或"upload_"
  • 不能包含目录分隔符或特殊字符

返回值

  • 成功时:返回新创建的临时文件的完整路径(字符串)
  • 失败时:返回 false,通常是由于以下原因:
    • 目录不存在且无法使用系统临时目录
    • 没有目录的写入权限
    • 无法生成唯一的文件名
    • 磁盘空间不足
注意: tempnam() 会实际创建一个空文件来确保文件名唯一。这意味着即使你不需要这个空文件,它也会占用一个inode。你需要手动删除这个文件。

示例

示例 1:基本用法

创建一个临时文件并写入内容:

<?php
// 在系统临时目录中创建临时文件
$tempFile = tempnam(sys_get_temp_dir(), 'TMP_');

if ($tempFile !== false) {
    echo "临时文件创建成功: " . $tempFile . "<br>";

    // 写入内容
    file_put_contents($tempFile, "这是临时文件内容");

    // 读取内容
    $content = file_get_contents($tempFile);
    echo "文件内容: " . $content . "<br>";

    // 使用后删除
    unlink($tempFile);
    echo "临时文件已删除";
} else {
    echo "临时文件创建失败";
}
?>
示例 2:在指定目录创建临时文件

在自定义目录中创建临时文件:

<?php
$customDir = '/var/tmp/myapp';

// 确保目录存在
if (!is_dir($customDir)) {
    mkdir($customDir, 0755, true);
}

// 在自定义目录中创建临时文件
$tempFile = tempnam($customDir, 'APP_');

if ($tempFile !== false) {
    echo "临时文件: " . $tempFile . "<br>";

    // 检查文件是否确实在指定目录
    $dirName = dirname($tempFile);
    echo "文件目录: " . $dirName . "<br>";

    // 获取文件名(不含路径)
    $fileName = basename($tempFile);
    echo "文件名: " . $fileName . "<br>";

    // 文件会以"APP_"开头
    echo "文件名前缀匹配: " . (strpos($fileName, 'APP_') === 0 ? '是' : '否');

    // 清理
    unlink($tempFile);
}
?>
示例 3:安全临时文件处理

使用安全的临时文件处理类:

<?php
class SecureTempFile {
    private $filePath;
    private $autoDelete = true;

    public function __construct($directory = null, $prefix = 'tmp_') {
        $dir = $directory ?: sys_get_temp_dir();
        $this->filePath = tempnam($dir, $prefix);

        if ($this->filePath === false) {
            throw new RuntimeException('无法创建临时文件');
        }

        // 设置安全权限(仅所有者可读写)
        chmod($this->filePath, 0600);
    }

    public function getPath() {
        return $this->filePath;
    }

    public function write($content) {
        return file_put_contents($this->filePath, $content);
    }

    public function read() {
        return file_get_contents($this->filePath);
    }

    public function setAutoDelete($autoDelete) {
        $this->autoDelete = (bool)$autoDelete;
    }

    public function __destruct() {
        if ($this->autoDelete && file_exists($this->filePath)) {
            unlink($this->filePath);
        }
    }
}

// 使用示例
try {
    $tempFile = new SecureTempFile();
    echo "临时文件: " . $tempFile->getPath() . "<br>";

    $tempFile->write("敏感数据");
    echo "内容: " . $tempFile->read() . "<br>";

    // 文件会在对象销毁时自动删除
} catch (Exception $e) {
    echo "错误: " . $e->getMessage();
}
?>
示例 4:批量创建临时文件

创建多个临时文件并管理它们:

<?php
function createTempFiles($count, $directory = null, $prefix = 'file_') {
    $dir = $directory ?: sys_get_temp_dir();
    $files = [];

    for ($i = 0; $i < $count; $i++) {
        $tempFile = tempnam($dir, $prefix);

        if ($tempFile === false) {
            // 清理已创建的文件
            foreach ($files as $file) {
                unlink($file);
            }
            throw new RuntimeException("创建第 " . ($i + 1) . " 个临时文件失败");
        }

        $files[] = $tempFile;
    }

    return $files;
}

// 使用示例
try {
    $tempFiles = createTempFiles(5, null, 'batch_');

    echo "已创建 " . count($tempFiles) . " 个临时文件:<br>";
    foreach ($tempFiles as $index => $file) {
        echo ($index + 1) . ". " . $file . "<br>";

        // 为每个文件写入不同的内容
        $content = "这是第 " . ($index + 1) . " 个临时文件的内容";
        file_put_contents($file, $content);
    }

    // 使用后清理
    foreach ($tempFiles as $file) {
        if (file_exists($file)) {
            unlink($file);
        }
    }
    echo "所有临时文件已清理";
} catch (Exception $e) {
    echo "错误: " . $e->getMessage();
}
?>
示例 5:临时文件监控和清理

监控临时文件使用并自动清理旧文件:

<?php
class TempFileManager {
    private $directory;
    private $maxAge; // 文件最大年龄(秒)
    private $prefix;

    public function __construct($directory = null, $prefix = 'tmp_', $maxAge = 3600) {
        $this->directory = $directory ?: sys_get_temp_dir();
        $this->prefix = $prefix;
        $this->maxAge = $maxAge;

        if (!is_dir($this->directory)) {
            mkdir($this->directory, 0755, true);
        }
    }

    public function create($content = null) {
        $tempFile = tempnam($this->directory, $this->prefix);

        if ($tempFile === false) {
            return false;
        }

        // 设置安全权限
        chmod($tempFile, 0600);

        // 写入初始内容(如果有)
        if ($content !== null) {
            file_put_contents($tempFile, $content);
        }

        return $tempFile;
    }

    public function cleanup() {
        $files = glob($this->directory . '/' . $this->prefix . '*');
        $cleaned = 0;
        $now = time();

        foreach ($files as $file) {
            if (!is_file($file)) continue;

            $fileAge = $now - filemtime($file);

            if ($fileAge > $this->maxAge) {
                unlink($file);
                $cleaned++;
            }
        }

        return $cleaned;
    }

    public function getStats() {
        $files = glob($this->directory . '/' . $this->prefix . '*');
        $totalSize = 0;
        $oldest = null;
        $newest = null;

        foreach ($files as $file) {
            if (!is_file($file)) continue;

            $size = filesize($file);
            $mtime = filemtime($file);

            $totalSize += $size;

            if ($oldest === null || $mtime < $oldest) {
                $oldest = $mtime;
            }

            if ($newest === null || $mtime > $newest) {
                $newest = $mtime;
            }
        }

        return [
            'count' => count($files),
            'total_size' => $totalSize,
            'oldest' => $oldest ? date('Y-m-d H:i:s', $oldest) : null,
            'newest' => $newest ? date('Y-m-d H:i:s', $newest) : null
        ];
    }
}

// 使用示例
$manager = new TempFileManager('/tmp/myapp_temp', 'app_', 1800); // 30分钟过期

// 创建临时文件
$file1 = $manager->create("第一个临时文件");
$file2 = $manager->create("第二个临时文件");

echo "创建的文件:<br>";
echo "1. " . $file1 . "<br>";
echo "2. " . $file2 . "<br>";

// 获取统计信息
$stats = $manager->getStats();
echo "<br>临时文件统计:<br>";
echo "数量: " . $stats['count'] . "<br>";
echo "总大小: " . number_format($stats['total_size']) . " 字节<br>";

// 清理旧文件
$cleaned = $manager->cleanup();
echo "清理了 " . $cleaned . " 个过期文件<br>";

// 手动清理创建的文件
if (file_exists($file1)) unlink($file1);
if (file_exists($file2)) unlink($file2);
?>

安全性考虑

安全警告

临时文件可能成为安全漏洞,特别是在共享主机环境中。以下是一些重要的安全考虑:

1. 权限设置

创建临时文件后,应立即设置适当的权限:

<?php
$tempFile = tempnam('/tmp', 'secret');
if ($tempFile) {
    // 仅所有者可读写
    chmod($tempFile, 0600);

    // 或者仅所有者可读
    // chmod($tempFile, 0400);

    // 避免使用过于宽松的权限
    // chmod($tempFile, 0644); // 不推荐:其他用户可读
    // chmod($tempFile, 0666); // 危险:所有用户可读写
}
?>
2. 竞争条件(Race Conditions)

tempnam() 创建文件后到使用文件之间可能存在竞争条件:

<?php
// 不安全的做法:存在时间窗口
$tempFile = tempnam('/tmp', 'upload');
// 在这段时间内,攻击者可能创建符号链接指向敏感文件
file_put_contents($tempFile, $_POST['data']);

// 更安全的做法:使用原子操作
function safeTempWrite($directory, $prefix, $data) {
    $tempFile = tempnam($directory, $prefix);
    if ($tempFile === false) return false;

    // 设置安全权限
    chmod($tempFile, 0600);

    // 立即打开文件并锁定
    $handle = fopen($tempFile, 'w');
    if (!$handle) {
        unlink($tempFile);
        return false;
    }

    // 获取独占锁
    if (!flock($handle, LOCK_EX)) {
        fclose($handle);
        unlink($tempFile);
        return false;
    }

    // 写入数据
    fwrite($handle, $data);
    fflush($handle);

    // 保持锁定直到处理完成
    return ['handle' => $handle, 'path' => $tempFile];
}
?>
3. 安全临时目录

使用安全的临时目录,避免使用公共可写目录:

<?php
function getSecureTempDir() {
    // 优先使用自定义目录
    $secureDirs = [
        '/var/tmp/myapp_secret',    // 自定义目录
        '/tmp/' . getmyuid(),        // 用户专属目录
        sys_get_temp_dir() . '/' . uniqid('secure_', true) // 随机子目录
    ];

    foreach ($secureDirs as $dir) {
        if (!is_dir($dir)) {
            mkdir($dir, 0700, true); // 仅所有者可访问
        }

        if (is_dir($dir) && is_writable($dir)) {
            // 检查目录是否安全(不是符号链接)
            if (is_link($dir)) {
                continue;
            }

            // 设置安全权限
            chmod($dir, 0700);
            return $dir;
        }
    }

    return false;
}
?>
4. 文件清理

确保临时文件被正确清理:

<?php
function withTempFile($callback) {
    $tempFile = tempnam(sys_get_temp_dir(), 'temp_');

    if ($tempFile === false) {
        throw new RuntimeException('无法创建临时文件');
    }

    try {
        // 设置安全权限
        chmod($tempFile, 0600);

        // 执行回调函数,传入文件路径
        $result = $callback($tempFile);

        // 确保文件被删除
        if (file_exists($tempFile)) {
            unlink($tempFile);
        }

        return $result;
    } catch (Exception $e) {
        // 发生异常时也要清理文件
        if (file_exists($tempFile)) {
            unlink($tempFile);
        }
        throw $e;
    }
}

// 使用示例
$result = withTempFile(function($tempFile) {
    file_put_contents($tempFile, '临时数据');
    return file_get_contents($tempFile);
});

echo "处理结果: " . $result;
?>

相关函数比较

函数 描述 与 tempnam() 的区别
tempnam() 创建具有唯一文件名的临时文件 创建空文件并返回路径,需要手动删除
tmpfile() 创建临时文件并返回文件句柄 文件在关闭时或脚本结束时自动删除
uniqid() 生成唯一ID 只生成字符串,不创建文件
sys_get_temp_dir() 获取系统临时目录 返回目录路径,不创建文件
fopen() with 'x' mode 以独占模式创建并打开文件 创建文件但要求文件不存在,不保证唯一性
代码示例对比
<?php
// tempnam() 示例
$tempnamFile = tempnam('/tmp', 'tempnam_');
echo "tempnam(): " . $tempnamFile . "<br>";
if ($tempnamFile) unlink($tempnamFile);

// tmpfile() 示例
$tmpfileHandle = tmpfile();
echo "tmpfile(): 返回文件句柄<br>";
if ($tmpfileHandle) fclose($tmpfileHandle); // 自动删除

// uniqid() 示例
$uniqueId = uniqid('prefix_', true);
echo "uniqid(): " . $uniqueId . "<br>";

// 组合使用:创建临时文件名(但不创建文件)
$tempDir = sys_get_temp_dir();
$tempName = $tempDir . '/' . uniqid('manual_', true);
echo "手动生成: " . $tempName . "<br>";
?>

实际应用场景

场景1:文件上传处理
<?php
class UploadProcessor {
    public function processUpload($uploadedFile) {
        // 验证上传文件
        if (!is_uploaded_file($uploadedFile['tmp_name'])) {
            throw new RuntimeException('无效的上传文件');
        }

        // 创建临时文件进行处理
        $tempFile = tempnam(sys_get_temp_dir(), 'upload_');
        if ($tempFile === false) {
            throw new RuntimeException('无法创建临时文件');
        }

        try {
            // 设置安全权限
            chmod($tempFile, 0600);

            // 将上传的文件移动到临时文件
            if (!move_uploaded_file($uploadedFile['tmp_name'], $tempFile)) {
                throw new RuntimeException('无法移动上传文件');
            }

            // 验证文件类型(例如,只允许图片)
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $mimeType = finfo_file($finfo, $tempFile);
            finfo_close($finfo);

            if (!in_array($mimeType, ['image/jpeg', 'image/png', 'image/gif'])) {
                throw new RuntimeException('不支持的文件类型');
            }

            // 处理文件(例如,调整大小)
            $this->processImage($tempFile);

            // 生成最终文件名
            $finalName = 'processed_' . basename($tempFile) . '.jpg';
            $finalPath = '/var/www/uploads/' . $finalName;

            // 移动处理后的文件
            rename($tempFile, $finalPath);

            return $finalPath;
        } catch (Exception $e) {
            // 清理临时文件
            if (file_exists($tempFile)) {
                unlink($tempFile);
            }
            throw $e;
        }
    }

    private function processImage($imagePath) {
        // 图像处理逻辑
        // ...
    }
}
?>
场景2:大数据处理
<?php
class DataProcessor {
    public function processLargeDataset($dataGenerator) {
        // 创建临时文件存储中间数据
        $tempFile = tempnam(sys_get_temp_dir(), 'dataset_');
        if ($tempFile === false) {
            throw new RuntimeException('无法创建临时文件');
        }

        // 设置安全权限
        chmod($tempFile, 0600);

        try {
            // 打开文件准备写入
            $handle = fopen($tempFile, 'w');
            if (!$handle) {
                throw new RuntimeException('无法打开临时文件');
            }

            // 写入数据
            foreach ($dataGenerator as $row) {
                $csvLine = implode(',', array_map('str_getcsv', [$row]));
                fwrite($handle, $csvLine . PHP_EOL);
            }

            fclose($handle);

            // 处理数据(例如,排序)
            $this->sortLargeFile($tempFile);

            // 读取处理结果
            $results = [];
            $handle = fopen($tempFile, 'r');
            while (($line = fgets($handle)) !== false) {
                $results[] = str_getcsv($line);
            }
            fclose($handle);

            return $results;
        } finally {
            // 确保清理临时文件
            if (file_exists($tempFile)) {
                unlink($tempFile);
            }
        }
    }

    private function sortLargeFile($filePath) {
        // 使用外部排序算法处理大文件
        // ...
    }
}
?>