相信大家在日常研发过程中,难免需要和各种文件纠缠不清。比如,打开“.env”文件并从中读取配置信息、把项目中的错误信息写入到日志文件中或者获取图片的创建时间等等。在处理这些功能时,我们都需要使用到PHP文件 ...
相信大家在日常研发过程中,难免需要和各种文件纠缠不清。比如,打开“.env”文件并从中读取配置信息、把项目中的错误信息写入到日志文件中或者获取图片的创建时间等等。在处理这些功能时,我们都需要使用到PHP文件系统接口。一 什么是文件系统开始之前,我们首先需要厘清我们所研究的问题领域,理解什么是文件系统,还有我们所研究的对象。 在计算机中,文件系统(file system or filesystem)用于管理数据如何存储和如何被获取的。 - 维基百科 简单来说,就是我们应该如何管理我们的目录(文件夹)和文件。通常,我们将具有相似属性的文件,存储到同一个目录中以便后续查找,这个常见的操作就会涉及到目录和文件。 对于软件工程师来讲,一个非常典型的使用场景,就是在开发 MVC 项目时,将控制器、视图和模型等模块的文件,存储到不同的目录结构中方便管理。 无论如何,我们依据不同特性划分文件和目录都是为了解决文件存储和查找的问题。 有了这些认知后,应该自然而然的想到我们当前研究的 PHP 文件系统(或者说文件系统)其所研究的对象,简单概括起来就是:
也就是说,本文我们所讲解的 PHP 文件系统函数处理,基本都是围绕目录和文件展开的。 二 深入 PHP 文件系统在 PHP 文件系统中内置提供了超过 80 个可用的 文件系统函数。由于数量繁多功能强大,自然本文无法将对所有的系统函数逐一讲解。一来,时间过于仓促;再者,短时间内我们也没有那么多的精力将它们全部掌握。 另外,补充说明一点,PHP 标准函数库不仅为我们提供了面向过程的文件系统处理函数。同时,还封装了常用目录及文件操作的面向对象接口和迭代器接口方便大家使用:
2.1 文件系统的元数据2.1.1 什么是元数据元数据(meta data):通俗一点讲就是「数据的数据」。拿一个 php 文件来说它的元数据可以是 创建时间、文件名、文件大小 或 文件所有权限 等,这类能够表明该文件基本特征的数据就是「元数据(meta data)」了。 2.1.2 常用元数据获取1、获取文件的最后修改时间 要获取文件的上次被修改时间戳,我们可以使用函数 filemtime($filename) 或 SplFileInfo::getMTime() 方法。 注意 SplFileInfo 类实例化时接收 $filename 文件路径作为参数,后续没有特别说明默认我们已经获取到了 SplFileInfo 实例才能进行 getMTime() 等类似处理。 // 文件路径请求改成你自己的文件路径 $filename = "f://filesystem/test.txt"; // 面向过程: 获取文件时间 $modifyTimestamp = filemtime($filename); // 面向对象 $file = new SplFileInfo($filename); $modifyTimestamp = $file->getMTime(); 2、获取文件的上次访问时间 可以使用函数 fileatile($filename) 或 SplFileInfo::getATime() 方法,来获取文件的最后被访问时间戳。 // 文件路径请求改成你自己的文件路径 $filename = "f://filesystem/test.txt"; // 面向过程: 获取文件时间 $accessTimestamp = fileatime($filename); // 面向对象 $file = new SplFileInfo($filename); $accessTimestamp = $file->getATime(); 除了 filemtile 和 fileatime 之外,还有 filectime 来获取文件的 inode 修改时间(可认为是创建时间)。 有关时间的函数常用的就这些,为了方便记住,我们来看看它们是如何命名的:
是不是很简单呢! 注意,使用 filectime 时,对于 Windows 系统会获取创建时间,但对于类 Unix 系统是修改时间,因为在类 Unix 系统中多数文件系统并没有创建时间的概念。具体说明可以看 PHP: how can I get file creation date?。 3、获取文件的路径信息 除了时间这些元数据,另一个经常遇到的情况是获取文件的路径信息,包括:
获取目录信息我们可以使用 pathinfo($filename, PATHINFO_DIRNAME)、dirname($filename) 和 SplFileInfo::getPath() 比如下面给出的文件: $filename = 'F:\Program Files\SSH Communications Security\SSH Secure Shell\Output.txt'; 将会获取到 F:\Program Files\SSH Communications Security\SSH Secure Shell 这部分目录信息。
这里我们所有的文件名指的是不带扩展名后缀的文件名称,比如需要获取 your_path/filename.txt 中的 filename 部分。 需要取得文件名信息,我们可以使用 pathinfo($filename, PATHINFO_FILENAME)、basename($filename, $suffix) 和 SplFileInfo::getBasename($suffix) 获取。 这里给出的 $suffix** 指不获取 **$suffix 扩展名部分(比如不获取 $suffix = '.txt')。 请看下面的示例: $filename = 'F:\Program Files\SSH Communications Security\SSH Secure Shell\Output.txt'; 将会获取到 Output 这部分文件名信息。
扩展名我们可以使用 pathinfo($filename, PATHINFO_EXTENSION) 和 SplFileInfo::getExtension() 方法拿到。 基于前面的了解,我们可以获取到 txt 这部分扩展信息,这里不再赘述。
basename 指的是 文件名 + 扩展名 内容信息,可以使用 pathinfo($filename, PATHINFO_BASENAME)、 basename($filename)、SplFileInfo::getBasename() 和 SplFileInfo::getFilename() 方法拿到。 虽然这里我们列出了很多的函数,但是基本上还是比较容易理解的,需要注意的是:
<?php $filename = 'F:\Program Files\SSH Communications Security\SSH Secure Shell\Output.txt'; $file = new SplFileInfo($filename); // 目录路径 $directory1 = pathinfo($filename, PATHINFO_DIRNAME); $directory2 = dirname($filename); $directory3 = $file->getPath(); echo '--- directory begin: ---' . PHP_EOL; echo $directory1 . PHP_EOL, $directory2 . PHP_EOL, $directory3 . PHP_EOL; // 文件名 $suffix = '.txt'; $filename1 = pathinfo($filename, PATHINFO_FILENAME); $filename2 = basename($filename, $suffix); $filename3 = $file->getBasename($suffix); echo '--- filename begin: ---' . PHP_EOL; echo $filename1 . PHP_EOL, $filename2 . PHP_EOL, $filename3 . PHP_EOL; // 扩展名 $extension1 = pathinfo($filename, PATHINFO_EXTENSION); $extension2 = $file->getExtension(); echo '--- extension begin: ---' . PHP_EOL; echo $extension1 . PHP_EOL, $extension2 . PHP_EOL; // basename = 文件名 + 扩展名 $basename1 = pathinfo($filename, PATHINFO_BASENAME); $basename2 = basename($filename); $basename3 = $file->getBasename(); $basename4 = $file->getFilename(); echo '--- basename begin: ---' . PHP_EOL; echo $basename1 . PHP_EOL, $basename2 . PHP_EOL, $basename3 . PHP_EOL, $basename4 . PHP_EOL; 它们的运行结果如下: --- directory begin: --- F:\Program Files\SSH Communications Security\SSH Secure Shell F:\Program Files\SSH Communications Security\SSH Secure Shell F:\Program Files\SSH Communications Security\SSH Secure Shell --- filename begin: --- Output Output Output --- extension begin: --- txt txt --- basename begin: --- Output.txt Output.txt Output.txt Output.txt
另外需要注意的一点是在使用 SplFileInfo 获取 basename 时,getBasename() 和 getFilename() 返回基本一致,但是在处理根目录下的文件名获取时表现稍有不同。 4、获取文件的绝对路径 绝对路径由 realpath($path) 和 SplFileInfo::getRealpath() 获取。 5、获取文件类型 可以使用 filetype($filename) 和 SplFileInfo::getType() 来获取文件的类型。 返回值范围:
可以查看 Linux 文件类型与扩展名 相关文件类型,这里我们重点关注下 dir 目录和 file 普通文件类型即可。 6、获取文件大小 可以使用 filesize($filename) 和 SplFileInfo::getSize() 来获取文件的大小,不再赘述。 7、获取文件权限 可以使用 fileperms($filename) 和 SplFileInfo::getPerms() 来获取到文件的所属权限。 值得注意的是它们的返回值是十进制表示的权限,如果需要获取类似 0655 八进制权限表示法,我们需要对返回值进行处处理才行: // @see http://php.net/manual/zh/function.fileperms.php#refsect1-function.fileperms-examples
$permissions = substr(sprintf("%o", fileperms($filename)), -4);你可以通过 PHP: fileperms() values and convert these 了解更多关于 PHP 获取文件权限转换的更多细节。 基本上学习完这些文件元数据信息获取方法,差不多可以应对日常开发过程中的多数应用场景,尽管如此,还是建议仔细去阅读官方 文件系统函数,那里才是知识的源泉。 掌握文件的元数据,对我们了解文件的特性大有裨益,就好比两个人谈恋爱,懂得彼此才是最好的状态。 2.2 文件系统操作可以说我们日常在处理文件的过程中,更多的是在操作文件或者目录(文件夹),本节我们将学习文件系统操作相关知识。 依据文件类型的不同我们可以简单的将操作分为:
2.2.1 目录操作使用场景在处理目录时我们一般涉及如下处理: 创建目录 删除目录 打开目录 读取目录 关闭目录句柄
我们有一套 CMS 管理系统支持文件上传处理,当目录不存在时依据文件上传时间,动态的创建文件存储目录,比如,我们依据 年/月/日(2018/01/01) 格式创建目录。这里就涉及到 目录创建 的处理。
当然,文件上传完成了还不够,我们还需要读取各个目录下的所有文件。这里涉及 打开目录、读取目录 以及读取完成后 关闭目录句柄。 有了相关概念和思路后,我们具体看看究竟 PHP 文件系统给我们提供了哪些方便处理目录的函数呢? 2.2.1.1 创建目录在 PHP 文件系统扩展中同样给我们提供了处理 目录结构的系统函数。 其中创建一个新目录需要使用 [mkdir($pathname [, $mode = 0777, $recursive = false])](http://php.net/manual/zh/func... 函数。
请看一个示例: $pathname = "/path/to/your/upload/file/2018/01/01"; $created = mkdir($pathname); 创建目录是不是特别的简单呢? 但是等等,我们在类 Unix 系统中满心欢喜的使用 mkdir 并采用 $mode=0777 权限来创建一个全新的目录,但为什么当我们进入到目录中看到的目录的权限却是 0755 呢? umask 掩码这里涉及到 umask 掩码的问题! 重点: 原来我们在类 Unix 系统中创建新目录是给出的权限会默认减去当前系统的 umask 值,才是实际创建目录时的所属权限。 什么意思呢? 比如: // 我们期望创建的文件权限 $mode = 0777; // 当前系统中 umask 值 $umask = 0022;// 可以由 umask 命令查看当前系统 umask 值,默认是 0022 // 实际创建的文件权限 0777 - 0022 ------ = 0755 现在我们来对之前的实例稍作修改,看看 PHP 如何创建目录时得到希望的系统权限吧: $pathname = "/path/to/your/upload/file/2018/01/01"; // 将系统 umask 设置为 0,并取得当前 umask 值(比如默认 0022) $umask = umask(0); $created = mkdir($pathname, $mode = 0777); // 将系统 umask 设置回原值 umask($umask); 有关 umask 函数说明可以查看官方手册。另外可以查看 Why can't PHP create a directory with 777 permissions? 这个问答了解更多细节。 2.2.1.2 目录遍历面向过程的目录遍历提供两种解决方案:
目录遍历示例一,出自 官方文档: <?php
$dir = "/etc/php5/";
// Open a known directory, and proceed to read its contents
if (is_dir($dir)) {
if ($dh = opendir($dir)) {
while (($file = readdir($dh)) !== false) {
echo "filename: $file : filetype: " . filetype($dir . $file) . "\n";
}
closedir($dh);
}
}
// 输出结构类似于:
// filename: . : filetype: dir
// filename: .. : filetype: dir
// filename: apache : filetype: dir
// filename: cgi : filetype: dir
// filename: cli : filetype: dir
?>目录遍历示例二,出自 官方文档: <?php $dir = '/tmp'; $files1 = scandir($dir); print_r($files1); // 输出结构类似于: // Array // ( // [0] => . // [1] => .. // [2] => bar.php // [3] => foo.txt // [4] => somedir // ) 目录的操作处理大致就是在处理这两类问题,相比于普通文件的处理来讲简单很多,下一节我们会学习有关普通文件的处理,请大家做好战斗准备。 2.2.2 文件操作使用场景2.2.2.1 创建空文件创建空文件有两种方式: 这两个函数同其它文件系统函数使用大致相同,感兴趣的朋友可以阅读手册,这里不作展开。 2.2.2.2 删除文件删除文件由 unlink($filename) 函数完成。 2.2.2.3 复制文件复制文件由 copy($source, $dest) 函数完成,会将 $source 文件拷贝到 $dest 文件中。 如果需要移动文件(重命名)可以使用 rename($oldname, $newname) 完成这个处理。 以上都是相对简单的文件处理函数就不一一举例说明了。 接下来学习如何读取文件中的内容。依据二八原则,可以说我们百分之八十的时间都在处理文件写入和读取的处理,所以我们有必要理清如何对文件进行读取和写入。 2.2.2.4 读取文件读取文件的标准流程是:
开始之前我们需要准备一个有数据的文件,比如 F:\php_workspace\php-code-kata\read.txt,在看一个简单的文件读取示例: <?php
// 这里为了贴合读取文件的标准流程,使用 do{} while 语句,你也可以修改成 while 语句。
$filename = "F:\\php_workspace\\php-code-kata\\read.txt";
// 1. 打开一个文件句柄;
$handle = fopen($filename, $mode = 'rb');
do {
// 2. 使用文件读取函数读取文件;
$content = fgetc($handle);
echo $content;
// 3. 判断是否到文件结尾,到结尾则结束读取,否则回到操作 2;
} while (!feof($handle));
// 4. 读取完成关闭句柄;
fclose($handle);
// 读取显示大致类似:
// hello world!现在,我们来详细讲解一下上述代码做了什么处理吧:
总体来说,在读取文件时按照以上处理流程,基本上太容易出错的。不过即便如此,还是有些重点需要我们小心处理:
注意: 读取文件操作时我们推荐使用 file_get_contents。 到这里我们基本上就涵盖了文件读取的所有知识点,相信大家对文件读取已经有了一个比较系统的认知。 下面我们进入到文件写入处理中,看看文件写入的正确姿势。 2.2.2.5 读取写入典型的文件写入流程基本上和文件读取流程一致:
依据惯例我们来看一个简单的示例: <?php $filename = "F:\\php_workspace\\php-code-kata\\read.txt"; // 1. 打开一个文件句柄; $handle = fopen($filename, $mode = 'ab'); // 2. 使用文件读取函数向文件中写入内容 fwrite($handle, "hello filesystem to write!\n"); // 3. 写入完成关闭句柄; fclose($handle); 注意:这里我们以追加写入的模式 $mode = 'ab' 写入文件内容。 文件写入就如同文件读取一样的简单,相信大家能够轻松掌握这方面的知识。然而,我们显示世界可能充满了荆棘,稍不留神可能就会深陷泥沼。比如:
到这里,我们基本上就学习完 PHP 文件系统中大多数常用的函数了。然而就如我所说的那样,现实世界总是残酷的。尤其是在读写文件时,经常会遇到各种各样的错误,我们应该如何才能避免呢? 嗯,PHP 一样为我们内置了检测文件有效性的函数,规避各种错误。 2.2.2.5 如何处理文件权限及检测有效性
检测文件的有效性能够让我们规避常见的开发错误,比如:
这些内容都需要使用到文件有效性检测相关知识。
当我们能够正确的检测文件是否存在时,我们还需要面对的问题时,如果我们的文件当前用户 不可写入,我们应该如何修改权限使其可写呢? 这里就涉及修改文件权限操作,之前我们在创建目录是已经接触过 umask 掩码相关知识。这里我们将讲解那些已经创建的文件权限变更的方法。 通常,我们会使用 chmod($filename, $mode) 去修改一个文件的权限。 另外,还可以关注以下几个权限相关的处理函数:
以及,之前提到过的 umask 修改掩码函数。 文章进行到这里,其实基本上 PHP 文件系统的所有知识都已经涉及到了。那么,下回见吧?不不不... 为了应对实战(面试需要),我们可能需要进一步对目录遍历做更进一步的研究。还记得我们之前使用过 scandir 来遍历指定路径中的文件和目录夹么? 现在我们将使用面向对象的接口来重新实现一个权限的目录遍历处理。 3 面向对象的目录遍历使用面向对象的接口来遍历目录,是一个非常有意义的教程,这里我们所涉及使用的接口包括:
话不多说,我们看下如何创建一个功能强大的支持递归迭代的目录迭代程序: /**
* 目录扫描
*
* @method listContents($path, $recursive = false) 获取目录中所有文件及文件夹
*/
class DirectoryScanner
{
/**
* 获取目录中所有文件及文件夹
*
* @param $path 目录
* @param $recursive 递归获取
*
* @return array
*/
public static function listContents($path, $recursive = false)
{
$iter = $recursive ? static::getRecursiveDirectoryIterator($path) : static::getDirectoryIterator($path);
$result = [];
foreach ($iter as $file) {
if (in_array($file->getFilename(), ['.', '..'])) {
continue;
}
$result[] = clone $file;
}
return $result;
}
/**
* 获取目录迭代器
*
* @param $path 目录
*
* @return DirectoryIterator::class
*/
public static function getDirectoryIterator($path)
{
return new DirectoryIterator($path);
}
/**
* 获取递归目录迭代器
*
* @param $path 目录
* @param $mode 遍历模式: RecursiveIteratorIterator::SELF_FIRST 从当前目录开始遍历;RecursiveIteratorIterator::CHILD_FIRST 从子目录开始遍历
*
* @return RecursiveIteratorIterator::class
*/
public static function getRecursiveDirectoryIterator($path, $mode = RecursiveIteratorIterator::SELF_FIRST )
{
return new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
$mode
);
}
}
$path = 'F:\php_workspace\php-code-kata\direcotry-iterator\dir';
var_dump(DirectoryScanner::listContents($path));
var_dump(DirectoryScanner::listContents($path, true));4 PHP 文件系统思维导图文件系统思维导图
推荐学习:php视频教程 以上就是详解PHP文件系统完全指南的详细内容,更多请关注模板之家(www.mb5.com.cn)其它相关文章! |
