PHP touch() 函数

定义和用法

touch() 函数用于设置文件的访问和修改时间。如果指定的文件不存在,PHP会尝试创建它。

这个函数常用于以下场景:创建空文件、更新文件时间戳以触发某些操作(如缓存失效)、或者模拟文件的存在。

注意: touch() 函数要求对文件所在目录有写入权限,即使文件不存在也需要目录的写入权限来创建新文件。

语法

touch ( string $filename [, int $time = time() [, int $atime ]] ) : bool

参数说明:

  • $filename:要设置时间的文件名(必需)
  • $time:要设置的修改时间(Unix时间戳),默认是当前时间(可选)
  • $atime:要设置的访问时间(Unix时间戳),如果指定,则单独设置访问时间(可选)

参数详解

参数 描述
filename

要设置时间的文件名。必需参数。

  • 可以是相对路径或绝对路径
  • 如果文件不存在,PHP会尝试创建它
  • 需要对该文件所在目录有写入权限
  • 如果文件是符号链接,会修改链接指向的原始文件
time

修改时间(mtime)。可选参数。

  • Unix时间戳格式
  • 默认值为 time()(当前时间)
  • 如果只设置此参数,访问时间也会被设置为相同的值
  • 可以使用 strtotime() 生成时间戳
atime

访问时间(atime)。可选参数。

  • Unix时间戳格式
  • 如果指定此参数,访问时间将单独设置
  • 如果不指定,访问时间会被设置为与修改时间相同
  • PHP 5.3.0 及以上版本支持

返回值

  • 成功时:返回 true
  • 失败时:返回 false,通常是由于以下原因:
    • 目录没有写入权限
    • 文件系统只读
    • 磁盘空间不足(创建新文件时)
    • 路径无效
注意: 在大多数Unix系统上,只有文件的所有者或超级用户可以修改文件的时间戳。

示例

示例 1:创建空文件

使用 touch() 创建空文件:

<?php
$filename = 'new_file.txt';

if (touch($filename)) {
    echo "文件创建成功: $filename<br>";

    // 检查文件是否存在
    if (file_exists($filename)) {
        echo "文件确实存在<br>";

        // 获取文件信息
        $fileSize = filesize($filename);
        echo "文件大小: $fileSize 字节<br>";
    }
} else {
    echo "文件创建失败";
}
?>
示例 2:更新文件时间戳

更新现有文件的时间戳:

<?php
$filename = 'existing_file.txt';

if (file_exists($filename)) {
    // 获取原始时间
    $originalMtime = filemtime($filename);
    echo "原始修改时间: " . date('Y-m-d H:i:s', $originalMtime) . "<br>";

    // 更新为当前时间
    if (touch($filename)) {
        $newMtime = filemtime($filename);
        echo "更新后修改时间: " . date('Y-m-d H:i:s', $newMtime) . "<br>";
        echo "时间已更新";
    }
} else {
    echo "文件不存在,无法更新时间戳";
}
?>
示例 3:设置特定的时间戳

将文件时间设置为过去的特定时间:

<?php
$filename = 'document.txt';

// 创建一个过去的时间(例如:2020年1月1日)
$pastTime = strtotime('2020-01-01 12:00:00');

if (touch($filename, $pastTime)) {
    echo "文件时间已设置为 2020-01-01 12:00:00<br>";

    $mtime = filemtime($filename);
    $atime = fileatime($filename);

    echo "修改时间: " . date('Y-m-d H:i:s', $mtime) . "<br>";
    echo "访问时间: " . date('Y-m-d H:i:s', $atime) . "<br>";

    // 注意:修改时间和访问时间都被设置为相同的值
    if ($mtime === $atime) {
        echo "修改时间和访问时间相同";
    }
}
?>
示例 4:分别设置修改时间和访问时间

单独设置文件的访问时间:

<?php
$filename = 'data.txt';

// 确保文件存在
if (!file_exists($filename)) {
    touch($filename);
}

// 设置不同的修改时间和访问时间
$modificationTime = strtotime('2023-01-15 10:30:00');
$accessTime = strtotime('2023-02-20 14:45:00');

// PHP 5.3.0+ 支持第三个参数
if (touch($filename, $modificationTime, $accessTime)) {
    echo "时间设置成功<br>";

    // 验证设置结果
    clearstatcache(true, $filename); // 清除缓存获取最新数据

    $mtime = filemtime($filename);
    $atime = fileatime($filename);

    echo "设置的修改时间: " . date('Y-m-d H:i:s', $modificationTime) . "<br>";
    echo "实际的修改时间: " . date('Y-m-d H:i:s', $mtime) . "<br>";
    echo "设置的访问时间: " . date('Y-m-d H:i:s', $accessTime) . "<br>";
    echo "实际的访问时间: " . date('Y-m-d H:i:s', $atime) . "<br>";

    // 检查是否设置成功
    if ($mtime === $modificationTime && $atime === $accessTime) {
        echo "✓ 时间戳设置正确";
    }
} else {
    echo "时间设置失败";
}
?>
示例 5:批量创建或更新文件

批量处理多个文件:

<?php
function batchTouchFiles($fileList, $time = null) {
    $results = [
        'success' => 0,
        'failed' => 0,
        'details' => []
    ];

    foreach ($fileList as $index => $filename) {
        if ($time === null) {
            $result = touch($filename);
        } else {
            $result = touch($filename, $time);
        }

        $status = $result ? '成功' : '失败';
        $results['details'][] = [
            'file' => $filename,
            'status' => $status,
            'exists' => file_exists($filename)
        ];

        if ($result) {
            $results['success']++;
        } else {
            $results['failed']++;
        }
    }

    return $results;
}

// 使用示例
$files = [
    'file1.txt',
    'file2.txt',
    'file3.txt',
    '/var/www/temp/file4.txt'
];

// 设置为明天的时间
$futureTime = strtotime('+1 day');

$batchResult = batchTouchFiles($files, $futureTime);

echo "批量处理结果:<br>";
echo "成功: {$batchResult['success']} 个<br>";
echo "失败: {$batchResult['failed']} 个<br><br>";

echo "详细信息:<br>";
foreach ($batchResult['details'] as $detail) {
    $exists = $detail['exists'] ? '存在' : '不存在';
    echo "文件: {$detail['file']} - 状态: {$detail['status']} - 存在: {$exists}<br>";
}
?>

文件时间戳详解

Unix/Linux系统中,文件有三种主要的时间戳:

时间戳类型 缩写 描述 touch() 函数影响
修改时间 mtime 文件内容最后被修改的时间 总是修改(通过第二个参数)
访问时间 atime 文件最后被访问(读取)的时间 修改(如果指定第三个参数)
状态更改时间 ctime 文件元数据(如权限)最后改变的时间 间接影响(当文件被创建或时间戳改变时)
时间戳操作演示
<?php
// 演示三种时间戳的区别
$filename = 'demo_times.txt';

// 确保文件存在
if (!file_exists($filename)) {
    file_put_contents($filename, '初始内容');
}

echo "初始状态:<br>";
showFileTimes($filename);

// 使用 touch() 更新时间和访问时间
echo "<br>使用 touch() 后:<br>";
$newTime = strtotime('2023-12-25 10:00:00');
touch($filename, $newTime);
showFileTimes($filename);

// 修改文件内容
echo "<br>修改文件内容后:<br>";
file_put_contents($filename, '修改后的内容');
showFileTimes($filename);

// 修改文件权限
echo "<br>修改文件权限后:<br>";
chmod($filename, 0644);
clearstatcache(true, $filename);
showFileTimes($filename);

function showFileTimes($filename) {
    clearstatcache(true, $filename);

    $mtime = filemtime($filename);  // 修改时间
    $atime = fileatime($filename);  // 访问时间
    $ctime = filectime($filename);  // 状态更改时间

    echo "mtime: " . date('Y-m-d H:i:s', $mtime) . "<br>";
    echo "atime: " . date('Y-m-d H:i:s', $atime) . "<br>";
    echo "ctime: " . date('Y-m-d H:i:s', $ctime) . "<br>";

    // 清理演示文件
    if (strpos($filename, 'demo_') === 0) {
        unlink($filename);
    }
}
?>

实际应用场景

场景1:缓存失效机制

使用文件时间戳作为缓存失效的标记:

<?php
class CacheManager {
    private $cacheDir;

    public function __construct($cacheDir = '/tmp/cache') {
        $this->cacheDir = $cacheDir;
        if (!is_dir($cacheDir)) {
            mkdir($cacheDir, 0755, true);
        }
    }

    public function isCacheValid($cacheKey, $ttl = 3600) {
        $cacheFile = $this->getCacheFilePath($cacheKey);

        if (!file_exists($cacheFile)) {
            return false;
        }

        $cacheAge = time() - filemtime($cacheFile);
        return $cacheAge <= $ttl;
    }

    public function invalidateCache($cacheKey) {
        $cacheFile = $this->getCacheFilePath($cacheKey);

        if (file_exists($cacheFile)) {
            // 通过更新文件时间戳来标记缓存失效
            touch($cacheFile, time() - 86400); // 设置为24小时前
            return true;
        }

        return false;
    }

    public function refreshCache($cacheKey) {
        $cacheFile = $this->getCacheFilePath($cacheKey);

        // 更新缓存文件的时间戳
        return touch($cacheFile);
    }

    public function getCacheFilePath($cacheKey) {
        $hash = md5($cacheKey);
        return $this->cacheDir . '/' . $hash . '.cache';
    }
}

// 使用示例
$cache = new CacheManager();

if ($cache->isCacheValid('user_data_123')) {
    echo "缓存有效";
    // 从缓存读取数据
} else {
    echo "缓存已过期或不存在";
    // 重新生成缓存
    // ...
    $cache->refreshCache('user_data_123');
}

// 手动使缓存失效
$cache->invalidateCache('user_data_123');
?>
场景2:文件锁定机制

使用文件时间戳实现简单的文件锁定:

<?php
class FileLock {
    private $lockDir;
    private $lockTimeout = 300; // 5分钟超时

    public function __construct($lockDir = '/tmp/locks') {
        $this->lockDir = $lockDir;
        if (!is_dir($lockDir)) {
            mkdir($lockDir, 0755, true);
        }
    }

    public function acquireLock($lockName) {
        $lockFile = $this->getLockFilePath($lockName);

        // 如果锁文件不存在,创建它并获取锁
        if (!file_exists($lockFile)) {
            return touch($lockFile);
        }

        // 检查锁是否已过期
        $lockAge = time() - filemtime($lockFile);
        if ($lockAge > $this->lockTimeout) {
            // 锁已过期,更新它并获取锁
            return touch($lockFile);
        }

        // 锁仍有效
        return false;
    }

    public function releaseLock($lockName) {
        $lockFile = $this->getLockFilePath($lockName);

        if (file_exists($lockFile)) {
            // 将锁文件时间设置为过去,使其立即过期
            return touch($lockFile, time() - $this->lockTimeout - 1);
        }

        return true;
    }

    public function refreshLock($lockName) {
        $lockFile = $this->getLockFilePath($lockName);

        if (file_exists($lockFile)) {
            // 更新锁的时间戳,延长锁的有效期
            return touch($lockFile);
        }

        return false;
    }

    public function isLocked($lockName) {
        $lockFile = $this->getLockFilePath($lockName);

        if (!file_exists($lockFile)) {
            return false;
        }

        $lockAge = time() - filemtime($lockFile);
        return $lockAge <= $this->lockTimeout;
    }

    private function getLockFilePath($lockName) {
        return $this->lockDir . '/' . md5($lockName) . '.lock';
    }
}

// 使用示例
$lock = new FileLock();

if ($lock->acquireLock('critical_operation')) {
    echo "获取锁成功,执行临界操作...<br>";

    // 执行需要锁的操作
    sleep(2);

    // 操作完成后释放锁
    $lock->releaseLock('critical_operation');
    echo "操作完成,锁已释放";
} else {
    echo "无法获取锁,可能有其他进程正在执行相同操作";
}
?>

注意事项和最佳实践

重要提示
  • 权限要求: 需要对文件所在目录有写入权限,即使只是更新时间戳
  • 符号链接: touch() 会影响符号链接指向的原始文件,而不是链接本身
  • 时间精度: 文件系统的时间戳精度可能只有秒级,毫秒级的时间可能被截断
  • 缓存问题: 使用 clearstatcache() 清除文件状态缓存以获取最新时间戳
  • 时区设置: 确保PHP的时区设置正确,特别是处理跨时区的文件时
最佳实践示例
<?php
// 1. 总是检查返回值
$filename = 'important.txt';
if (!touch($filename)) {
    // 处理错误
    $error = error_get_last();
    echo "无法更新文件时间: " . ($error['message'] ?? '未知错误');
}

// 2. 使用clearstatcache()获取最新时间戳
$file = 'data.txt';
touch($file, strtotime('2023-01-01'));
clearstatcache(true, $file); // 清除指定文件的缓存
$mtime = filemtime($file);

// 3. 确保目录存在并有正确权限
function safeTouch($filename, $time = null) {
    $dir = dirname($filename);

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

    // 检查目录是否可写
    if (!is_writable($dir)) {
        return false;
    }

    // 执行touch操作
    if ($time === null) {
        return touch($filename);
    } else {
        return touch($filename, $time);
    }
}

// 4. 处理未来时间戳(某些文件系统可能不支持)
function touchWithValidation($filename, $time) {
    $currentTime = time();

    // 不允许设置未来的时间戳(除非有特殊需求)
    if ($time > $currentTime + 3600) { // 超过当前时间1小时
        return false;
    }

    return touch($filename, $time);
}
?>
常见错误
  • 权限错误: 试图在没有写入权限的目录中创建或更新文件
  • 路径错误: 使用不存在的目录路径
  • 缓存问题: 忘记调用clearstatcache()导致获取过时的时间戳信息
  • 符号链接混淆: 期望更新符号链接本身的时间戳,但实际更新了目标文件
  • 未来时间戳: 设置过远的未来时间戳可能不被所有文件系统支持