ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

如何在 PHP 页面页脚中动态显示当前脚本文件的最后修改时间

如何在 PHP 页面页脚中动态显示当前脚本文件的最后修改时间 前言在页脚放一行「本站最后更新2026-09-29 10:00:00」是很多站点的常见需求尤其是小工具站和内部后台。第一反应通常是写echo date(Y-m-d, filemtime(__FILE__));本地跑起来也对于是就这么上线了。问题会在后面慢慢浮现页脚显示的时间比实际发布时间早了 8 小时页面顶部明明改过页脚时间却纹丝不动换成另一台机器部署之后时间突然变成了部署脚本执行的那一刻。这几个现象分别对应三个独立的坑时区、__FILE__的语义、以及文件 mtime 本身的含义。先说清楚一件事filemtime()返回的是文件在磁盘上的最后修改时间mtime它跟「这个页面的内容最后被谁改过」没有必然关系。git clone会把文件的 mtime 设成检出时间打包发布tar会保留原时间rsync -a保留源时间而rsync不带-a则用当前时间。所以这行字到底该显示什么取决于你想表达的是「文件何时被改动」还是「站点何时发布」——前者用 mtime 合理后者应该用一个由构建流程写入的版本常量。本文把「取时间 → 转成正确时区 → 展示 → 当缓存版本号用」这条链路讲清楚示例最低需要 PHP 8.1。一、__FILE__、__DIR__与「当前脚本」的区别这是最容易出错的一环。假设站点结构是这样site/ ├── index.php 入口 └── tpl/ └── footer.php 页脚模板里面写这行时间在footer.php里写filemtime(__FILE__)拿到的是footer.php自己的 mtime不是index.php的。页脚模板几个月不动一次而入口文件天天改于是页脚的时间永远是几个月前。正确的做法取决于你想表达什么写法拿到的是谁的时间适用场景filemtime(__FILE__)当前这个文件自己只想显示本文件何时被改filemtime(__DIR__ . /../index.php)硬编码的入口文件结构固定的站点filemtime($_SERVER[SCRIPT_FILENAME])本次请求的入口脚本多入口站点Web 环境构建期写入的常量或版本文件发布时刻最准确推荐生产使用$_SERVER[SCRIPT_FILENAME]在 CLI 下不存在所以要有兜底。另外它在某些 SAPIServer API下可能与实际路径有出入生产上更稳的做法是在构建或发布流程里生成一个版本文件页脚只负责读它。二、时区不设就默认 UTCdate()使用的是date.timezone配置或date_default_timezone_set()设置的时区。PHP 8 在未设置时区时默认使用 UTC不会报错——于是你的页面在 UTC8 时区显示的时间比真实时间早 8 小时而且不容易察觉差 8 小时看起来也像个正常时间。正确做法是用不可变的DateTimeImmutable把时区显式带上$dt (new DateTimeImmutable( . $timestamp)) -setTimezone(new DateTimeZone(Asia/Shanghai)); echo $dt-format(Y-m-d H:i:s);DateTimeImmutable有个额外好处每次修改都返回新对象不会像DateTime那样出现「某个变量在别处被改掉」的隐性 bug。三、stat 缓存改了文件读不到新值PHP 会缓存stat()系列函数的结果避免同一个请求里反复访问文件系统。这带来一个隐蔽问题在长驻进程常驻容器、queue worker、定时任务循环里循环调用filemtime()返回的可能是第一次读到的旧值。加了clearstatcache()才会重新读取clearstatcache(true, $path); // 第二个参数指定文件只清这一个这两个参数的含义是clearstatcache(bool $clear_realpath_cache false, string $filename )同时传文件名时只清除该文件的缓存比清空全部要高效。四、完整可运行示例?php declare(strict_types1); /** * lastmod.php —— 读取脚本文件的最后修改时间并格式化 * 最低 PHP 8.1 * CLI 运行php lastmod.php [文件路径] 默认取本文件 * Web 运行放到站点里访问即可 */ final class FileTime { /** * param string $timezone 目标时区生产环境建议与站点展示时区一致 */ public static function format(int $timestamp, string $timezone Asia/Shanghai): string { return (new DateTimeImmutable( . $timestamp)) -setTimezone(new DateTimeZone($timezone)) -format(Y-m-d H:i:s); } /** 人类可读的相对时间例如「3 天前」 */ public static function human(int $timestamp, ?int $now null): string { $now $now ?? time(); $diff $now - $timestamp; return match (true) { $diff 0 时间在未来请检查文件时间, $diff 60 刚刚, $diff 3600 intdiv($diff, 60) . 分钟前, $diff 86400 intdiv($diff, 3600) . 小时前, $diff 2592000 intdiv($diff, 86400) . 天前, default 很久以前, }; } /** 读取 mtime失败时抛出异常而不是返回 0 */ public static function read(string $path): int { clearstatcache(true, $path); // 长驻进程里必须清否则读到的是缓存值 $mtime filemtime($path); if ($mtime false) { // 不能直接返回 0 或 false传给 date() 会静默变成 1970-01-01 throw new RuntimeException(无法读取文件修改时间 . $path); } return $mtime; } /** 用于 HTTP 头 Last-Modified必须是 GMT 格式的固定写法 */ public static function httpDate(int $timestamp): string { return gmdate(D, d M Y H:i:s \G\M\T, $timestamp); } } // ---------------- 演示 ---------------- // Web 环境取本次请求的入口脚本CLI 环境允许用参数指定默认取本文件 $target $argv[1] ?? ($_SERVER[SCRIPT_FILENAME] ?? __FILE__); try { $mtime FileTime::read($target); } catch (RuntimeException $e) { http_response_code(500); exit($e-getMessage() . PHP_EOL); } $relativePath basename($target); printf(目标文件%s%s, $relativePath, PHP_EOL); printf(最后修改%s%s%s, FileTime::format($mtime), FileTime::human($mtime), PHP_EOL); printf(Unix 时间戳%d%s, $mtime, PHP_EOL); printf(Last-Modified 头%s%s, FileTime::httpDate($mtime), PHP_EOL); printf(静态资源版本号?v%d%s, $mtime, PHP_EOL);php lastmod.php目标文件lastmod.php 最后修改2026-09-29 10:00:00刚刚 Unix 时间戳1790647200 Last-Modified 头Tue, 29 Sep 2026 02:00:00 GMT 静态资源版本号?v1790647200注意中间两行的差异同一个时刻本地时区显示10:00:00而 HTTP 头必须是 GMT 的02:00:00 GMT。HTTP 协议要求Last-Modified用 GMT 格式用date()会带上本地时区偏移缓存协商就会失效。页脚里实际使用的形态大致是这样function footer_last_modified(string $entryFile): string { try { return 本站最后更新 . FileTime::format(FileTime::read($entryFile)); } catch (RuntimeException) { return ; // 页脚信息不该因为读不到文件而让整页崩掉 } } echo footer_last_modified($_SERVER[SCRIPT_FILENAME] ?? __FILE__);常见坑点❌ 在footer.php里用filemtime(__FILE__)以为拿到的是整站页面的时间。✅ 改用$_SERVER[SCRIPT_FILENAME]取本次请求的入口脚本或者读构建期生成的版本文件。❌ 用date()格式化filemtime()却没设置时区。✅ 用DateTimeImmutable显式带上DateTimeZone别依赖服务器的默认时区。❌ 直接把filemtime()的返回值传下去不判断false。✅ 先判断 false再使用false被强转成 0 会显示 1970-01-01把「文件不存在」这个错误伪装成一个正常时间。❌ 在长驻进程里循环读取 mtime改了文件却读不到新值。✅ 调用前clearstatcache(true, $path)只清目标文件的 stat 缓存。❌ 把 mtime 当作「内容最后修改时间」展示给用户。✅ 明确它的语义git clone得到检出时间rsync -a保留源时间tar 解包保留打包时间要展示发布时刻就用构建期写入的常量。❌ 用 mtime 当静态资源的缓存版本号却又用touch()手动改时间。✅ 版本号要与资源内容强相关改时间就是「骗缓存」要么用内容摘要要么保证发布流程一定会更新 mtime。❌ 用date()生成Last-Modified响应头。✅ HTTP 头必须用 GMT写成gmdate(D, d M Y H:i:s \G\M\T, $mtime)。❌ 每次请求都对几十个文件调用filemtime()拼版本号。✅ 每个文件一次stat系统调用文件多起来开销可观生产上把版本号在构建期算好写进一个常量文件运行时只读一次。总结需求推荐做法关键点显示文件何时被改filemtime(入口脚本路径)用$_SERVER[SCRIPT_FILENAME]不要用__FILE__显示站点何时发布构建期写入的版本常量或版本文件与文件系统时间解耦部署方式无关正确显示时间DateTimeImmutableDateTimeZone不设时区就是 UTCHTTP 缓存协商gmdate(...)必须是 GMT 格式长驻进程中读取先clearstatcache(true, $path)否则读到的可能是旧值文件读取失败抛异常或返回空字符串不要让页脚因为一个时间信息崩掉整页「显示脚本最后修改时间」看似一行代码真正决定它是否可靠的其实是三个语义问题取的是哪个文件的 mtime、这个 mtime 在你们的部署流程里意味着什么、以及时间要按哪个时区展示。把这三件事想清楚页脚那行字才能一直是对的。
返回列表