PHP Conference Japan 2024

RecursiveDirectoryIterator 类

(PHP 5, PHP 7, PHP 8)

简介

RecursiveDirectoryIterator 提供了一个用于递归遍历文件系统目录的接口。

类概要

class RecursiveDirectoryIterator extends FilesystemIterator implements RecursiveIterator {
/* 继承的常量 */
/* 方法 */
public __construct(string $directory, int $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO)
public getSubPath(): string
public hasChildren(bool $allowLinks = false): bool
public key(): string
public next(): void
public rewind(): void
/* 继承的方法 */
public SplFileInfo::getBasename(string $suffix = ""): string
public SplFileInfo::openFile(string $mode = "r", bool $useIncludePath = false, ?resource $context = null): SplFileObject
public SplFileInfo::setFileClass(string $class = SplFileObject::class): void
public SplFileInfo::setInfoClass(string $class = SplFileInfo::class): void
}

目录

添加注释

用户贡献的笔记 16 个笔记

Thriault
14 年前
如果您想递归获取项目文件夹中的所有 *.php 文件,可以使用以下方法

<?php

$Directory
= new RecursiveDirectoryIterator('path/to/project/');
$Iterator = new RecursiveIteratorIterator($Directory);
$Regex = new RegexIterator($Iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);

?>

$Regex 将包含每个 PHP 文件的单个索引数组。
sun
10 年前
由于我不断在网络上遇到无意中陷入这个陷阱的实现——请注意

RecursiveDirectoryIterator 会无限递归到整个文件系统树中。

除非您有意无限递归且没有限制,否则请勿执行以下操作

<?php
$directory
= new \RecursiveDirectoryIterator($path);
$iterator = new \RecursiveIteratorIterator($directory);
$files = array();
foreach (
$iterator as $info) {
if (...
自定义条件...) {
$files[] = $info->getPathname();
}
}
?>

1. RecursiveDirectoryIterator 只是一个递归迭代器,它会递归到其子级,直到找不到更多子级为止。

2. RecursiveIteratorIterator 的实例化导致 RecursiveDirectoryIterator *立即* 无限递归到整个文件系统树(从给定的基本路径开始)。

3. 不必要的文件系统递归很慢。在 90% 的情况下,这不是您想要的。

请记住这个简单的经验法则

→ 必须过滤 RecursiveDirectoryIterator,或者您有充分的理由说明为什么不应该过滤它。

在 PHP <5.4 中,实现以下内容 - 您的自定义条件会移动到一个合适的过滤器中



<?php
$directory
= new \RecursiveDirectoryIterator($path, \FilesystemIterator::FOLLOW_SYMLINKS);
$filter = new MyRecursiveFilterIterator($directory);
$iterator = new \RecursiveIteratorIterator($filter);
$files = array();
foreach (
$iterator as $info) {
$files[] = $info->getPathname();
}

class
MyRecursiveFilterIterator extends \RecursiveFilterIterator {

public function
accept() {
$filename = $this->current()->getFilename();
// 跳过隐藏文件和目录。
if ($name[0] === '.') {
return
FALSE;
}
if (
$this->isDir()) {
// 仅递归进入目标子目录。
return $name === 'wanted_dirname';
}
else {
// 仅处理感兴趣的文件。
return strpos($name, 'wanted_filename') === 0;
}
}

}
?>

在 PHP 5.4 及以上版本中,PHP 核心解决了创建全新类以处理此问题的繁琐问题,您可以利用新的 RecursiveCallbackFilterIterator 来代替。

<?php
$directory
= new \RecursiveDirectoryIterator($path, \FilesystemIterator::FOLLOW_SYMLINKS);
$filter = new \RecursiveCallbackFilterIterator($directory, function ($current, $key, $iterator) {
// 跳过隐藏文件和目录。
if ($current->getFilename()[0] === '.') {
return
FALSE;
}
if (
$current->isDir()) {
// 仅递归进入目标子目录。
return $current->getFilename() === 'wanted_dirname';
}
else {
// 仅处理感兴趣的文件。
return strpos($current->getFilename(), 'wanted_filename') === 0;
}
});
$iterator = new \RecursiveIteratorIterator($filter);
$files = array();
foreach (
$iterator as $info) {
$files[] = $info->getPathname();
}
?>

玩得开心!
alvaro at demogracia dot com
16 年前
使用示例

<?php

$path
= realpath('/etc');

$objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST);
foreach(
$objects as $name => $object){
echo
"$name\n";
}

?>

这将打印 $path 下所有文件和目录的列表(包括 $path 本身)。如果要省略目录,请移除 RecursiveIteratorIterator::SELF_FIRST 部分。
TDP
7 年前
Windows 和 Linux 之间文件的排序顺序存在差异。

<?php
$it
= new RecursiveIteratorIterator(
new
RecursiveDirectoryIterator( 'path/to/dir' )
//, 标志无关紧要
);
...
?>

在 Windows 上,您将获得按名称排序的文件。在 Linux 上,它们没有排序。
catinahat at cool dot fr dot nf
11 年前
如果您需要将嵌套目录树转换为多维数组,请使用此代码

<?php
$ritit
= new RecursiveIteratorIterator(new RecursiveDirectoryIterator($startpath), RecursiveIteratorIterator::CHILD_FIRST);
$r = array();
foreach (
$ritit as $splFileInfo) {
$path = $splFileInfo->isDir()
? array(
$splFileInfo->getFilename() => array())
: array(
$splFileInfo->getFilename());

for (
$depth = $ritit->getDepth() - 1; $depth >= 0; $depth--) {
$path = array($ritit->getSubIterator($depth)->current()->getFilename() => $path);
}
$r = array_merge_recursive($r, $path);
}

print_r($r);
?>
antennen
13 年前
如果您将 RecursiveDirectoryIterator 与 RecursiveIteratorIterator 一起使用并遇到 UnexpectedValueException,您可以使用此小技巧来忽略这些目录,例如 Linux 上的 lost+found。

<?php
class IgnorantRecursiveDirectoryIterator extends RecursiveDirectoryIterator {
function
getChildren() {
try {
return new
IgnorantRecursiveDirectoryIterator($this->getPathname());
} catch(
UnexpectedValueException $e) {
return new
RecursiveArrayIterator(array());
}
}
}
?>

使用方法与普通的 RecursiveDirectoryIterator 一样。
megar
15 年前
使用示例
查看所有文件并计算空间使用情况

<?php
$ite
=new RecursiveDirectoryIterator("/path/");

$bytestotal=0;
$nbfiles=0;
foreach (new
RecursiveIteratorIterator($ite) as $filename=>$cur) {
$filesize=$cur->getSize();
$bytestotal+=$filesize;
$nbfiles++;
echo
"$filename => $filesize\n";
}

$bytestotal=number_format($bytestotal);
echo
"总计: $nbfiles 个文件,$bytestotal 字节\n";
?>
alexandrebr at gmail dot com
8 年前
我尝试使用 RecursiveDirectoryIterator 来转储所有文件(及其属性,例如大小/是否为链接/是否为目录/修改时间/权限/所有者/组)来自一个大型目录(约 400,000 个文件),并过滤一些特定的目标文件/文件夹。

使用 RecursiveDirectoryIterator 和 SplFileInfo,转储大约需要 50 秒才能执行,但它确实可以工作。

然而,为了提高性能,我决定使用另一个版本的相同脚本,只使用直接的文件函数,例如“readdir”、“filesize”、“filemtime”等,并自己添加递归(if(is_dir($path)) doRecursivity($path);)。

运行后,脚本的完成时间从大约50秒缩短到大约20秒(在Linux CentOS 7,SSD 300IPs上)。

奇怪的是,在Windows 7,Sata3(使用完全相同的文件[镜像])上,时间从大约63秒缩短到大约57秒。

我相信这种性能提升是由于SPL的面向对象方法,它运行了许多不必要的额外代码来执行相同的任务,并提高可靠性,而直接的文件函数更像是与C对应函数的别名,因此速度更快。

因此,如果您处理大量文件,使用RecursiveDirectoryIterator可能不是最佳选择。
dev_zakaria at outlook dot com
5年前
如果您想显示所有目录中的所有文件,则需要按照以下步骤操作:

$dir = new RecursiveDirectoryIterator(getcwd());
$files = new RecursiveIteratorIterator($dir);

foreach($files as $file){
echo $file->getFileName();
echo PHP_EOL; // 换行
}

现在,如果您想显示完整路径,请按照以下步骤操作:

$dir = new RecursiveDirectoryIterator(getcwd());
$files = new RecursiveIteratorIterator($dir);

foreach($files as $file){
echo $file->getPath().$file->getFileName();
echo PHP_EOL;
}

如果您想跳过点,则需要将第一行更改为以下内容:
$dir = new RecursiveDirectoryIterator(getcwd(), RecursiveDirectoryIterator::SKIP_DOTS);
Josh Heidenreich
12年前
返回的对象是SplFileInfo对象的迭代器。
flobee
9年前
在这个文档页面中,我看到了隐藏隐藏文件的内容(也适用于opendir()或readdir()……应该在那里全部提及)
<?php
// 不是隐藏的,但在大多数操作系统Win、*nix、OSX上..
if ($file == '.' || $file == '..') {
// "." 当前目录信息,
// ".." 上一级目录信息,
continue;
?>

<?php
if $name[0] === '.' // 不行,各位
?>
认为
"... and then came Polly.avi" 是电影的标题。然后你该怎么办?

Windows 处理隐藏文件的方式与基于 Unix 的系统不同。

对于基于 Unix 的系统,以下内容应该有效
<?php
if (preg_match('/^(\.\w+|\.$|\.\.$)/i', $location)) {
/* 是隐藏的:
.
..
.dir
.file
*/
}
// 应该没问题:"..some thing","... some thing"
?>

我知道你这样做(if $name[0] === '.'),因为它更快。但它不正确,总有一天你会像我今天一样错过一些东西:-)
divinity76+spam at gmail dot com
2年前
如果您只需要一个简单的数组,递归地包含所有文件(而不是目录),请尝试

<?php

/**
* 返回目录及其子目录中所有文件的路径的简单字符串数组,
* 但不返回目录本身。(意味着如果目录为空,则根本不会包含它)
*
* @param string $dir
* @param bool $realpath
* @throws UnexpectedValueException 如果 $dir 不可读/不存在
* @return string[] files
*/
function get_file_list_recursively(string $dir, bool $realpath = false): array
{
$files = array();
$files = [];
foreach ((new
RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS))) as $file) {
/** @var SplFileInfo $file */
if ($realpath) {
$files[] = $file->getRealPath();
} else {
$files[] = $file->getPathname();
}
}
return
$files;
}
?>

示例返回值
<?php
array (
0 => '/home/hans/scraping/1650518081RESULTS.txt',
1 => '/home/hans/scraping/1650518121RESULTS.txt',
2 => '/home/hans/scraping/1650518679RESULTS.txt',
3 => '/home/hans/scraping/1650518780RESULTS.txt',
4 => '/home/hans/scraping/1650522198RESULTS.txt',
5 => '/home/hans/scraping/1650522927RESULTS.txt',
6 => '/home/hans/scraping/1650525391RESULTS.txt',
7 => '/home/hans/scraping/check_cache.php',
8 => '/home/hans/scraping/foo/bar.txt',
9 => '/home/hans/scraping/foobar.txt',
10 => '/home/hans/scraping/GoodProxyDaemon.php',
);
?>

(给编辑的备注,如果您能找到更好的示例返回值,请随时覆盖上面的示例)
dxvargas
5年前
当使用 FilesystemIterator::FOLLOW_SYMLINKS 选项时,如果存在指向上一级目录的符号链接,则会发生循环,最终导致目录重复。
例如(存在 -> ../)

/c
/c/..
/c/a
/c/a/c
/c/a/c/..
/c/a/c/a
/c/a/c/a/c
...(最多 40 x /c/a)
/c/a/c/a/..
/c/a/c/a/.
/c/a/c/.
/c/a/..
/c/a/.
/c/.
/..
/.

显然存在一个限制以避免出现无限循环。我不知道这方面的文档。

能够在不发生循环的情况下跟随符号链接的选项会很有趣。
Edward Rudd
10 年前
(与关于getChildren()中异常的帖子相关。)

您可以简单地使用 RecursiveIteratorIterator 的 CATCH_GET_CHILD 标志,而不是子类化。

new RecursiveIteratorIterator($diriter, RecursiveIteratorIterator::CATCH_GET_CHILD);
flaurora_sonora
5年前
不要浪费时间在这个上面。我在下面包含了一个函数,它可以更简单地完成相同的事情。如果您想在迭代时使用正则表达式,可以将正则表达式作为参数传递给递归函数。

<?php

function recursive_read($directory, $entries_array = array()) {
if(
is_dir($directory)) {
$handle = opendir($directory);
while(
FALSE !== ($entry = readdir($handle))) {
if(
$entry == '.' || $entry == '..') {
continue;
}
$Entry = $directory . DS . $entry;
if(
is_dir($Entry)) {
$entries_array = recursive_read($Entry, $entries_array);
} else {
$entries_array[] = $Entry;
}
}
closedir($handle);
}
return
$entries_array;
}

?>
dblanchard1 at bbox dot fr
11 年前
如果您想将所有文件从源目录递归复制到某个目标目录

$directory = new RecursiveDirectoryIterator("./source_path/");

foreach (new RecursiveIteratorIterator($directory) as $filename=>$current) {

$src = $current->getPathName();
$dest = "./destination_path/" . $current->getFileName();

echo "copy " . $src . " => " . $dest . "\n";

copy($src, $dest);
}

我希望它能帮助某些人,因为当我寻找这个解决方案时,我不得不转换另一个示例才能得到它。
To Top