RecursiveIterator
and its relatives are quite handy in any tree-related situation. Probably the most used is RecursiveDirectoryIterator
for getting all files recursively in a directory. But this family of iterators has some issues to be addressed so that developers do not end up fleeing. First, you must not fear SPL. But there's more. Let's see…Usage
Use of recursive iterators is quite simple once you get it: All must be wrapped inside aRecursiveIteratorIterator
in order to achieve it's recursive behaviour. So you do: $iterator = new RecursiveIteratorIterator($recursiveIterator);
foreach ($iterator as $item) {
echo $item, PHP_EOL;
}
(Yes, that's not a typo, is an iterator of recursive iterators)So, to iterate recursively over all the files in a directory, we can do:
$recursive = new RecursiveDirectoryIterator('/path', RecursiveDirectoryIterator::SKIP_DOTS);
$iterator = new RecursiveIteratorIterator($recursive);
foreach ($iterator as $file) {
echo $file, PHP_EOL;
}
RecursiveIteratorIterator
offers some modes of operation, namely:LEAVES_ONLY
: Iterates over all nodes that have no children.SELF_FIRST
: When a node with children is found, process it first, then iterate over it's children.CHILD_FIRST
: Iterate first over children, then process the node.
/path |- file1.php |- dir1 | |- file2.php | |- dir2 | \- file3.php |- dir3 \- dir4 \- file4.php
LEAVES_ONLY
/dir1/file2.php /dir1/dir2/file3.php /dir4/file4.php /file1.php
SELF_FIRST
/dir3 /dir1 /dir1/file2.php /dir1/dir2 /dir1/dir2/file3.php /dir4 /dir4/file4.php /file1.php
CHILD_FIRST
/dir3 /dir1/file2.php /dir1/dir2/file3.php /dir1/dir2 /dir1 /dir4/file4.php /dir4 /file1.phpNote that
LEAVES_ONLY
does not treat dir3
as a leave because, although it has no children, is elegible for inner iteration.Filtering
Another issue with directory iteration is permission. Sooner or later you will get something like this:PHP Fatal error: Uncaught exception 'UnexpectedValueException' with message 'RecursiveDirectoryIterator::__construct(/path): failed to open dir: Permission denied'If you're a happy exception-deaf coder, there's an undocumented flag for you:
RecursiveIteratorIterator::CATCH_GET_CHILD
. This flag makes the iterator to skip nodes throwing exceptions, and also its child.$iterator = new RecursiveIteratorIterator($recursive, RecursiveIteratorIterator::LEAVES_ONLY, RecursiveIteratorIterator::CATCH_GET_CHILD);
But if you're like me, you don't want to flush your exceptions down the bathroom: They're telling you you're doing something wrong. And that's good from a developer's point of view.So you must inherit from
RecursiveDirectoryIterator
and add the behaviour, or create a RecursiveFilterIterator
.RecursiveFilterIterator
As I did in this entry, you can create a class for filtering entriesclass myRecursiveFilterIterator extends \RecursiveFilterIterator
{
protected $filter;
public function __construct(\RecursiveIterator $iterator, \Closure $filter)
{
$this->filter = $filter;
parent::__construct($iterator);
}
public function accept()
{
return $this->filter->__invoke(parent::current());
}
public function getChildren()
{
return new self($this->getInnerIterator()->getChildren(), $this->filter);
}
}
Have fun with SPL and RecursiveIterators!
No comments:
Post a Comment