对于 SPL 来说,除了我们之前学习到的各种 数据结构 以及 迭代器 之外,还有一类非常好用的功能就是对于文件的操作。今天我们就来学习这方面的内容,同时,这也是 SPL 系列文章中最后要学习的内容。
对于 PHP 的文件操作来说,在默认情况下我们都是使用面向过程的那些函数来进行操作,比如 fopen() 、 fwrite() 、 fread() 、 fgets() 这些。而 SPL 中提供的则是以面向对象的方式来进行这些文件相关的操作。
首先我们要学习的是 SplFileInfo 这个类。
$file = new SplFileInfo('./6.PHP的SPL扩展库(四)函数.php');
实例化它的时候,就需要传递一个文件路径参数。比如这里我们就直接打开上一篇文章的测试代码。然后,我们的 $file 变量就会得到一个 SplFileInfo 对象,在这个对象中,有许多和文件函数类似的方法可以供我们使用。
var_dump($file->getBasename()); // string(39) "6.PHP的SPL扩展库(四)函数.php" var_dump($file->getPathname()); // string(41) "./6.PHP的SPL扩展库(四)函数.php" var_dump($file->getFilename()); // string(39) "6.PHP的SPL扩展库(四)函数.php" var_dump($file->getRealPath()); // string(102) "/Users/zhangyue/MyDoc/博客文章/dev-blog/php/2021/01/source/6.PHP的SPL扩展库(四)函数.php" var_dump($file->getPathInfo()); // object(SplFileInfo)#2 (2) { // ["pathName":"SplFileInfo":private]=> // string(1) "." // ["fileName":"SplFileInfo":private]=> // string(1) "." // } var_dump($file->getFileInfo()); // object(SplFileInfo)#2 (2) { // ["pathName":"SplFileInfo":private]=> // string(41) "./6.PHP的SPL扩展库(四)函数.php" // ["fileName":"SplFileInfo":private]=> // string(39) "6.PHP的SPL扩展库(四)函数.php" // }
我们可以通过上面的这些测试代码来获得文件相关的一些路径信息。是不是和普通的那些文件操作函数非常类似,或者说是完全一样的命名和使用方式。
var_dump($file->getExtension()); // string(3) "php" var_dump($file->getType()); // string(4) "file" var_dump($file->getCTime()); // int(1611017967) var_dump($file->getOwner()); // int(501) var_dump($file->getGroup()); // int(20) var_dump($file->getSize()); // int(3543)
当然,文件的这些属性信息也都是可以正常获取到的,比如文件的 扩展名、类型、各种文件相关的时间、拥有者、属组、大小 之类的信息。
var_dump($file->isReadable()); // bool(true) var_dump($file->isWritable()); // bool(true) var_dump($file->isDir()); // bool(false) var_dump($file->isFile()); // bool(true) var_dump($file->isLink()); // bool(false)
当然,判断文件是否可写、可读,是否是目录或文件或连接的方法都有。总体来说,普通的文件操作函数所拥有的功能这边都是存在的,在这里我们也只是列出了一部分,更多的方法大家可以在官方文件中查阅。
上面的 SplFileInfo 中,我们好像没有对文件进行读写操作之类的功能,只能看到文件的一些属性信息。别急,对于文件的其它操作,我们使用另外一个类 SplFileObject 。它是继承自 SplFileInfo 的,并且另外又多实现了两个迭代器的接口。关于这两个多实现的接口的功能我们在后面会讲到。而相对于 SplFileInfo 来说,它扩展了文件的一些读写方法,能够让我们方便地进行文件的各种读写操作。
$txt1 = new SplFileObject('7.1.txt', 'a+'); $txt1->fwrite(date('Y-m-d H:i:s' . PHP_EOL)); // 71.txt // 2021-01-20 09:03:15 // …… // ……
在实例化的时候,我们就可以通过 SplFileObject 的第二个参数来指定文件打开的形式,这个参数和 fopen() 函数中的相关参数的作用是一样的。这里我们使用的是 a+ ,也就是追加读写的能力。因为我们要不断地测试,所以使用的就是这样的一个读写属性。在实例化之后,我们使用对象的 fwrite() 方法就可以正常的向文件中进行写入了。
$txt1->seek(0); var_dump($txt1->fread($txt1->getSize())); // string(80) "2021-01-20 09:03:15 // "
对于读取来说,我们要先将文件对象内部的句柄指针指回开头处。这里使用的 seek() 方法是不是很眼熟。没错,它的出现正是 SplFileObject 实现了 Seekable 接口的最好证明。接着,我们使用 fread() 就可以读取文件中的全部内容了。
当然,像是在普通的文件函数中最经典的 eof + fgets 这种遍历方式在 SplFileObject 中也是支持的,同样,它也支持 fgetc 、fgetss 这些方法。
$txt1->seek(0); while(!$txt1->eof()){ var_dump($txt1->fgets()); } // string(20) "2021-01-20 09:03:15 // " // string(20) "2021-01-20 09:03:16 // " // …… // ……
前面说过,SplFileObject 是继承了一些迭代器接口的,也就是说,我们可以直接遍历这个对象来获得文件里面的内容。
foreach($txt1 as $t){ var_dump($t); } // string(20) "2021-01-20 09:03:15 // " // string(20) "2021-01-20 09:03:16 // " // …… // ……
是不是很神奇?这确实是 SplFileObject 中非常神奇的一个地方。通过这种方式我们直接就可以通过 foreach() 来实现对文件内容的遍历,而且可以看出,这种遍历形式也是按行来进行遍历的,完全可以替代上面的 eof+fgets 的方式。
关于普通的面向过程的文件操作函数的学习我们在之前的文章中已经学习过了,当中大部分的函数在 SplFileInfo 和 SplFileObject 中都可以找到对应的实现,这里就不进行更多的演示了。
关于文件相关操作我们最后再学习一个对于临时文件的操作。还记得之前我们学习过的 《在PHP中操作临时文件》吧,同样地,在 SPL 库中也有对应的操作临时文件的类。
$tmp = new SplTempFileObject(0); $tmp->fwrite("tmp:" . date('Y-m-d H:i:s')); $tmp->rewind(); foreach ($tmp as $line) { var_dump($line); } // string(23) "tmp:2021-01-20 09:14:34" sleep(10); // vim /tmp/phpRhgsVZ // tmp:2021-01-20 09:14:34
怎么样,是不是也和 tmpfile() 一样通过在 /tmp 目录来保存临时文件,并且在程序运行结束后直接删除了文件。不过这里需要注意的是,我们的 SplTempFileObject 类在实例化的时候传递了一个 0 作为参数,表示的是不使用内存作为临时文件目录,而是真实地存放在临时文件目录中。如果这个参数不填或者是填大于 0 的数字,则表示是将这个临时文件存放在内存中,而大于 0 的数字则表示所使用的内存大小,不填的话默认值是 2MB 。
目录迭代器就是我们在之前讲迭代器中没有讲到的部分,也是我们说过要放在文件目录相关的文章中再讲到的内容。其实这方面的内容我们在之前也学习过,《PHP获取目录中的全部内容RecursiveDirectoryIterator》中使用的这个 RecursiveDirectoryIterator 就是 SPL 中的 递归目录迭代器 。今天,我们再学习一个比较简单的,也是这个 RecursiveDirectoryIterator 迭代器的上级迭代器,就是普通的目录迭代器的使用。
foreach (new DirectoryIterator('./') as $fileInfo) { if($fileInfo->isDot()) continue; if($fileInfo->isDir()){ echo "dir: " . $fileInfo->getFilename() , PHP_EOL; }else{ echo "file: " . $fileInfo->getFilename() , PHP_EOL; } } // file: 2.学习了解PHP中的SeasLog日志扩展.php // dir: autoloadA // file: 7.1.txt // file: 6.PHP的SPL扩展库(四)函数.php // file: 1.PHP中的一些杂项函数学习.php // file: browscap.ini // file: 7.PHP的SPL扩展库(四)文件及设计模式.php // file: 3.PHP的SPL扩展库(一)数据结构.php // file: 4.PHP的SPL扩展库(二)对象数组与数组迭代器.php // file: 1.txt // dir: autoloadB // file: 5.PHP的SPL扩展库(三)迭代器.php
同样非常简单的代码,这里就不多解释了。而它和带 递归 功能的迭代器相关的区别和其它的迭代器都一样,也是在之前的文章都讲解过的,这里就不多赘述了。
最后,我们再来复习一下 设计模式 。设计模式系列文章是较早之前写过的一系列文章了。当时就在两篇文章中讲到过 SPL 中为我们准备好并且已经实现了的一些设计模式。
迭代器相信已经不用多说了,前面的文章 《PHP的SPL扩展库(三)迭代器》中已经详细说明了 SPL 中各种迭代器的实现。而对于迭代器这种设计模式,也可以参考我们之前的文章 《PHP设计模式之迭代器模式进行深入的学习了解。另外,Iterator 这个类不是 SPL 扩展中的哦!SPL 库中只是有几种不同的具体迭代器实现而已。
除了迭代器,另一个就是观察者模式,SPL 库中直接为我们准备好了 SplObserver 和 SplSubject 这两个类来专门用于实现观察者模式。具体的实现代码和这个设计模式的讲解我们也在之前的设计模式系列文章中有过详细的说明,大家可以再过去好好学习下 《PHP设计模式之观察者模式》。
从这篇文章中可以看出一个问题,那就是 SPL 扩展库现在已经非常常用了,要不这里也不会有这么多之前文章的链接。相信大家也会在将来的业务开发中更多地使用 SPL 中操作文件的类来进行文件操作,这也是一种趋势,毕竟面向对象的方法才是更主流的方法。好了,又是一个大扩展的完结,SPL 库作为一个已经默认集成到官方的扩展,确实是值得我们花更多的时间来学习掌握的,学习好这个扩展一定不会让你失望!