Примените косую черту к папкам с помощью FilesystemIterator

Наличие следующего фрагмента для рекурсивного сопоставления содержимого текущего каталога:

$files = new RecursiveIteratorIterator
(
    new RecursiveDirectoryIterator('./',
        FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS),
    RecursiveIteratorIterator::SELF_FIRST
);

$files = array_values(array_map('strval', iterator_to_array($files)));

Возвращает что-то вроде этого:

Array
(
    [0] => ./1.png
    [1] => ./a.php
    [2] => ./adminer
    [3] => ./adminer/adminer.css
    [4] => ./adminer/adminer.php
)

Есть ли способ, которым я могу получить RecursiveDirectoryIterator / FilesystemIterator подражать GLOB_MARK поведение, которое существует в glob() функционировать? Из руководства:

GLOB_MARK - добавляет косую черту в каждый возвращенный каталог.

Я знаю, что мог бы подражать этому, просто делая:

foreach ($files as $key => $value)
{
    $files[$key] .= (is_dir($value) ? '/' : '');
}

Но для этого потребуется (излишне?) Много раз ударить по диску. Я ищу способ быстро определить, является ли путь каталогом или обычным файлом, и завершающий слеш кажется идеальным решением.

Я планирую обойти десятки (если не сотни) тысяч файлов с этим, поэтому производительность имеет решающее значение.

Бонусный вопрос: есть ли способ получить только каталоги (рекурсивно)?

2 ответа

Решение

Вы можете создать свой собственный GlobMarkIterator преимущества:

  • Возвращает конечные слеши в каталог так же, как GLOB_MARK
  • Нет необходимости использовать array_map с strval преобразовать его в строку
  • Без дополнительных foreach цикл с is_dir
  • Все так же быстро, как оригинал
  • Да, я знаю, что я обманул

пример

$ri = new RecursiveIteratorIterator(new GlobMarkIterator('./', FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS), RecursiveIteratorIterator::SELF_FIRST);
$files = array_values(iterator_to_array($ri));

echo "<pre>";
print_r($files);

Выход

Array
(
    [0] => ./test/backups/ <----------- Note ending slash 
    [1] => ./test/CSV/
    [2] => ./test/CSV/abc.csv
    [3] => ./test/final/
    [4] => ./test/thumb/
    [5] => ./test/thumb/a.png
    [6] => ./test/thumb/s.svg
    [7] => ./test/thumb/sample.svg
)



Бонусный вопрос: есть ли способ получить только каталоги (рекурсивно)?

Это должен был быть другой вопрос, но все же.... Я надеюсь, что вы не будете удовлетворены и не вознаградите за это

Решение:

$ri = new RecursiveIteratorIterator( new GlobMarkDirectory('./test'), RecursiveIteratorIterator::SELF_FIRST);
$dir = array_values(iterator_to_array($ri));

echo "<pre>";
print_r($dir);

Выход

Array
(
    [0] => ./test/backups/
    [1] => ./test/CSV/
    [2] => ./test/final/
    [3] => ./test/thumb/
)

Используемый класс

GlobMarkIterator

class GlobMarkIterator extends RecursiveDirectoryIterator {
    function current() {
        return $this->isDir() ? $this->getPathname() . "/" : $this->getPathname();
    }
}

GlobMarkDirectory Учебный класс

class GlobMarkDirectory  extends RecursiveFilterIterator {
    public function __construct($path) {
        parent::__construct(new GlobMarkIterator($path, FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS));
    }
    public function accept() {
        return $this->getInnerIterator()->isDir();
    }
    public function getChildren() {
        return new GlobMarkDirectory($this->getInnerIterator()->getPathname());
    }
}



РЕДАКТИРОВАТЬ.. если вас не волнует empty dir и вы не хотите использовать isDir из-за скорости и накладных расходов, вот еще одно решение

Решение

$ri = new RecursiveIteratorIterator(new GlobMarkFastDirectory  (__DIR__), RecursiveIteratorIterator::SELF_FIRST);
$dir = array_values(array_unique(iterator_to_array($ri)));

GlobMarkFastDirectory

class GlobMarkFastDirectory  extends RecursiveDirectoryIterator {
    function current() {
        return dirname($this->getPathname())  ."/";
    }
}

Это мое лучшее усилие на данный момент:

$files = new RecursiveIteratorIterator
(
    new RecursiveDirectoryIterator
    (
        str_replace('\\', '/', realpath('./')),
        FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS
    ),
    RecursiveIteratorIterator::LEAVES_ONLY
);

$files = array_keys(iterator_to_array($files));
$folders = array();

/*
foreach ($files as $key => $value) // not needed anymore
{
    $files[$key] .= (is_dir($value) === true) ? '/' : '';
}
*/

$files = array_flip($files);

foreach ($files as $key => $value)
{
    $folder = dirname($key) . '/'; // doesn't issue a stat call

    if (array_key_exists($folder, $folders) !== true)
    {
        $folders[$folder] = 0;
    }

    $folders[$folder] += $files[$key] = sprintf('%u', filesize($key));
}

Я мог бы объединить $folders массив с $files массив, чтобы ответить на вопрос точно, но выделение одного из других было именно моей главной целью, так что нет смысла делать это.

Другие вопросы по тегам