Як RecursiveIteratorIterator працює в PHP?


88

Як RecursiveIteratorIteratorпрацює?

У посібнику PHP немає нічого особливо документованого чи поясненого. У чому різниця між IteratorIteratorі RecursiveIteratorIterator?


2
є приклад на php.net/manual/en/recursiveiteratoriterator.construct.php, а також є вступ на php.net/manual/en/class.iteratoriterator.php - чи можете ви вказати, що саме у вас виникає з проблемами в розумінні . Що має містити Посібник, щоб полегшити його сприйняття?
Гордон,

1
Якщо ви запитаєте, як RecursiveIteratorIteratorпрацює, ви вже зрозуміли, як це IteratorIteratorпрацює? Я маю на увазі, що це в основному однаково, лише інтерфейс, який споживається двома, різний. І вас більше цікавлять деякі приклади чи ви хочете побачити різницю базової реалізації коду С?
hakre

@Gordon, я не був впевнений, як один цикл foreach може пройти всі елементи в структурі дерева
varuog

@hakra я зараз намагаюся вивчити всі вбудовані інтерфейси, а також інтерфейс spl та реалізацію ітератора. Мені було цікаво дізнатись, як це працює у фоновому режимі з циклом forach, з деякими прикладами.
varuog

@hakre Вони обидва насправді дуже різні. IteratorIteratorкарти Iteratorта IteratorAggregateв Iterator, де REcusiveIteratorIteratorвикористовується для проходження рекусивно aRecursiveIterator
Адам

Відповіді:


251

RecursiveIteratorIteratorце конкретна Iteratorреалізація обходу дерева . Це дозволяє програмісту пройти об’єм контейнера, що реалізує RecursiveIteratorінтерфейс, див. Ітератор у Вікіпедії щодо загальних принципів, типів, семантики та зразків ітераторів.

На відміну від того, IteratorIteratorякий конкретний Iteratorваріант обходу об’єкта реалізується в лінійному порядку (і за замовчуванням приймає будь-який тип Traversableу своєму конструкторі), RecursiveIteratorIteratorдозволяє циклічний перегляд усіх вузлів в упорядкованому дереві об’єктів, а його конструктор приймає a RecursiveIterator.

Коротше кажучи: RecursiveIteratorIteratorдозволяє циклічно перебирати дерево, IteratorIteratorдозволяє циклічно перебирати список. Я показую це на деяких прикладах коду нижче.

Технічно це працює шляхом виходу з лінійності шляхом обходу всіх дочірніх вузлів (якщо такі є). Це можливо, оскільки за визначенням усі дочірні вузли знову є a RecursiveIterator. Потім верхній рівень Iteratorвнутрішньо складає різні RecursiveIterators за їх глибиною і зберігає вказівник на поточну активну підгрупу Iteratorдля обходу.

Це дозволяє відвідувати всі вузли дерева.

Основні принципи такі ж, як і у IteratorIterator: Інтерфейс визначає тип ітерації, а базовий клас ітератора - реалізація цієї семантики. Порівняйте з прикладами нижче, для лінійного циклу foreachви зазвичай не задумуєтесь над деталями реалізації, якщо вам не потрібно визначити нове Iterator(наприклад, коли якийсь конкретний тип сам не реалізує Traversable).

Для рекурсивного обходу - якщо ви не використовуєте заздалегідь визначене, Traversalяке вже має ітерацію рекурсивного обходу - вам, як правило, потрібно створити інстанцію існуючої RecursiveIteratorIteratorітерації або навіть написати ітерацію рекурсивного обходу, яка є Traversableвласною, щоб мати такий тип ітерації обходу foreach.

Порада: Ви, мабуть, не реалізовували ні те, ні друге, так що, можливо, варто зробити щось для свого практичного досвіду відмінностей, які вони мають. У кінці відповіді ви знайдете пропозицію "Зроби сам".

Технічні відмінності коротше:

  • Хоча IteratorIteratorприймає будь-який Traversableдля лінійного обходу, RecursiveIteratorIteratorпотрібен більш конкретний RecursiveIteratorцикл по дереву.
  • Там, де IteratorIteratorвиставляється його основний Iteratorза допомогою getInnerIerator(), RecursiveIteratorIteratorнадається поточний активний суб- Iteratorлише за допомогою цього методу.
  • Хоча IteratorIteratorвін абсолютно не знає нічого подібного до батьків чи дітей, але RecursiveIteratorIteratorзнає, як дістати і подорожувати дітей.
  • IteratorIteratorне потребує стеку ітераторів, RecursiveIteratorIteratorмає такий стек і знає активний підітератор.
  • Там, де IteratorIteratorє порядок через лінійність і немає вибору, RecursiveIteratorIteratorє вибір для подальшого обходу, і він повинен приймати рішення для кожного вузла (вирішується через режим perRecursiveIteratorIterator ).
  • RecursiveIteratorIteratorмає більше методів, ніж IteratorIterator.

Підсумовуючи: RecursiveIteratorце конкретний тип ітерації (зациклювання над деревом), яка працює на власних ітераторах, а саме RecursiveIterator. Це той самий основний принцип, що і в IteratorIerator, але тип ітерації різний (лінійний порядок).

В ідеалі ви також можете створити свій власний набір. Єдине, що потрібно, це те, що ваш ітератор реалізує, Traversableщо можливо через Iteratorабо IteratorAggregate. Тоді ви можете використовувати його з foreach. Наприклад, якийсь об’ємний рекурсивний ітераційний обхід тривимірного дерева разом із відповідним інтерфейсом ітерації для об’єкта (ів) контейнера.


Давайте розглянемо кілька реальних прикладів, які не є такими абстрактними. Між інтерфейсами, конкретними ітераторами, об’єктами контейнера та семантикою ітерацій це, можливо, не така вже й погана ідея.

Візьмемо для прикладу список каталогів. Вважаємо, що на диску є такий файл та дерево каталогів:

Дерево каталогів

У той час як ітератор з лінійним порядком просто перетинає папку та файли верхнього рівня (єдиний список каталогів), рекурсивний ітератор також проходить по вкладеним папкам та перераховує всі папки та файли (перелік каталогів зі списками його підкаталогів):

Non-Recursive        Recursive
=============        =========

   [tree]            [tree]
    ├ dirA            ├ dirA
    └ fileA           │ ├ dirB
                      │ │ └ fileD
                      │ ├ fileB
                      │ └ fileC
                      └ fileA

Ви можете легко порівняти це з тим, IteratorIteratorщо не має рекурсії для обходу дерева каталогів. І те, RecursiveIteratorIteratorщо може перейти в дерево, як показує Рекурсивний перелік.

Спочатку дуже простий приклад з мікросхемою DirectoryIterator, який реалізує Traversableякий дозволяє foreachдля ітерації над ним:

$path = 'tree';
$dir  = new DirectoryIterator($path);

echo "[$path]\n";
foreach ($dir as $file) {
    echo " ├ $file\n";
}

Зразковий результат для наведеної вище структури каталогів:

[tree]
 ├ .
 ├ ..
 ├ dirA
 ├ fileA

Як бачите, це ще не використовується IteratorIteratorабо RecursiveIteratorIterator. Натомість це просто використання лише того, foreachщо працює на Traversableінтерфейсі.

Оскільки foreachза замовчуванням тип ітерації відомий лише з іменем лінійний порядок, ми можемо захотіти вказати тип ітерації явно. На перший погляд це може здатися занадто багатослівним, але для демонстраційних цілей (і щоб зробити різницю RecursiveIteratorIteratorбільш помітною пізніше), давайте вкажемо лінійний тип ітерації, явно вказуючи IteratorIteratorтип ітерації для списку каталогів:

$files = new IteratorIterator($dir);

echo "[$path]\n";
foreach ($files as $file) {
    echo " ├ $file\n";
}

Цей приклад майже ідентичний першому, різниця полягає в тому, що $filesзараз це IteratorIteratorтип ітерації для Traversable $dir:

$files = new IteratorIterator($dir);

Як зазвичай акт ітерації виконують foreach:

foreach ($files as $file) {

Вихід абсолютно однаковий. То що різниться? Різним є об’єкт, який використовується в foreach. У першому прикладі це - DirectoryIteratorу другому прикладі це IteratorIterator. Це показує гнучкість ітераторів: Ви можете замінити їх один з одним, код всередині foreachпросто продовжує працювати, як очікувалося.

Почнемо отримувати весь перелік, включаючи підкаталоги.

Оскільки ми зараз визначили тип ітерації, давайте розглянемо можливість змінити її на інший тип ітерації.

Ми знаємо, що нам потрібно обійти все дерево зараз, а не лише перший рівень. Для того, щоб мати цю роботу з простим foreachнам потрібен інший тип ітератора: RecursiveIteratorIterator. І це можна переглядати лише над об'єктами-контейнерами, які мають RecursiveIteratorінтерфейс .

Інтерфейс - це контракт. Будь-який клас, який його реалізує, може бути використаний разом із RecursiveIteratorIterator. Прикладом такого класу є RecursiveDirectoryIterator, який є чимось на зразок рекурсивного варіанту DirectoryIterator.

Давайте побачимо перший приклад коду перед тим, як писати будь-яке інше речення з I-словом:

$dir  = new RecursiveDirectoryIterator($path);

echo "[$path]\n";
foreach ($dir as $file) {
    echo " ├ $file\n";
}

Цей третій приклад майже ідентичний першому, однак він створює різні результати:

[tree]
 ├ tree\.
 ├ tree\..
 ├ tree\dirA
 ├ tree\fileA

Гаразд, не те що інше, назва файлу тепер містить назву шляху спереду, але решта також схожа.

Як показує приклад, навіть об'єкт каталогу вже реалізує RecursiveIteratorінтерфейс, цього ще недостатньо, щоб зробити foreachобхід всього дерева каталогів. Тут тут RecursiveIteratorIteratorвступає в дію. Приклад 4 показує, як:

$files = new RecursiveIteratorIterator($dir);

echo "[$path]\n";
foreach ($files as $file) {
    echo " ├ $file\n";
}

Використання RecursiveIteratorIteratorзамість попереднього $dirоб'єкта дозволить здійснити foreachобхід усіх файлів та каталогів рекурсивно. Потім перераховуються всі файли, оскільки зараз вказано тип ітерації об’єкта:

[tree]
 ├ tree\.
 ├ tree\..
 ├ tree\dirA\.
 ├ tree\dirA\..
 ├ tree\dirA\dirB\.
 ├ tree\dirA\dirB\..
 ├ tree\dirA\dirB\fileD
 ├ tree\dirA\fileB
 ├ tree\dirA\fileC
 ├ tree\fileA

Це вже повинно продемонструвати різницю між рівниною та обходом дерева. Можливий RecursiveIteratorIteratorперехід до будь-якої деревоподібної структури як списку елементів. Оскільки інформації є більше (наприклад, про рівень ітерації, що має місце в даний час), можна отримати доступ до об’єкта ітератора під час ітерації по ньому і, наприклад, зробити відступ на виході:

echo "[$path]\n";
foreach ($files as $file) {
    $indent = str_repeat('   ', $files->getDepth());
    echo $indent, " ├ $file\n";
}

І результат прикладу 5 :

[tree]
 ├ tree\.
 ├ tree\..
    ├ tree\dirA\.
    ├ tree\dirA\..
       ├ tree\dirA\dirB\.
       ├ tree\dirA\dirB\..
       ├ tree\dirA\dirB\fileD
    ├ tree\dirA\fileB
    ├ tree\dirA\fileC
 ├ tree\fileA

Звичайно, це не виграє конкурс краси, але це показує, що з рекурсивним ітератором доступна більше інформації, ніж просто лінійний порядок ключа та значення . Навіть foreachможе виражати лише такий вид лінійності, доступ до самого ітератора дозволяє отримати більше інформації.

Подібно до метаінформації, існують також різні способи обходу дерева і, отже, порядок виводу. Це режим зRecursiveIteratorIterator і він може бути встановлений за допомогою конструктора.

Наступний приклад покаже, RecursiveDirectoryIteratorщоб видалити крапкові записи ( .і ..), оскільки вони нам не потрібні. Але також буде змінено режим рекурсії, щоб взяти батьківський елемент (підкаталог) першим ( SELF_FIRST) перед дочірніми (файли та підкаталоги в підкаталозі):

$dir  = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::SELF_FIRST);

echo "[$path]\n";
foreach ($files as $file) {
    $indent = str_repeat('   ', $files->getDepth());
    echo $indent, " ├ $file\n";
}

Вихідні дані тепер показують належним чином перелічені записи підкаталогу, якщо порівняти з попередніми вихідними даними, їх там не було:

[tree]
 ├ tree\dirA
    ├ tree\dirA\dirB
       ├ tree\dirA\dirB\fileD
    ├ tree\dirA\fileB
    ├ tree\dirA\fileC
 ├ tree\fileA

Таким чином, режим рекурсії контролює, що і коли повертається фішка або лист у дереві, для прикладу каталогу:

  • LEAVES_ONLY (за замовчуванням): лише файли зі списком, без каталогів.
  • SELF_FIRST (вгорі): Каталог списку, а потім файли там.
  • CHILD_FIRST (без прикладу): Спочатку перелічіть файли у підкаталозі, а потім каталог.

Вихід з прикладу 5 з двома іншими режимами:

  LEAVES_ONLY                           CHILD_FIRST

  [tree]                                [tree]
         ├ tree\dirA\dirB\fileD                ├ tree\dirA\dirB\fileD
      ├ tree\dirA\fileB                     ├ tree\dirA\dirB
      ├ tree\dirA\fileC                     ├ tree\dirA\fileB
   ├ tree\fileA                             ├ tree\dirA\fileC
                                        ├ tree\dirA
                                        ├ tree\fileA

Порівнюючи це зі стандартним обходом, усі ці речі недоступні. Тому рекурсивна ітерація дещо складніша, коли вам потрібно обернути її головою, однак вона проста у використанні, оскільки вона поводиться так само, як ітератор, ви вводите її в foreachі готово.

Думаю, цього достатньо прикладів для однієї відповіді. Ви можете знайти повний вихідний код, а також приклад для відображення красивих дерев ascii в цьому суть: https://gist.github.com/3599532

Зроби сам: склади RecursiveTreeIteratorробочий рядок за рядком.

Приклад 5 продемонстрував, що доступна метаінформація про стан ітератора. Однак це було цілеспрямовано продемонстровано в рамках foreachітерації. У реальному житті це природно належить всередині RecursiveIterator.

Кращим прикладом є RecursiveTreeIterator, він піклується про відступ, префікс тощо. Дивіться такий фрагмент коду:

$dir   = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$lines = new RecursiveTreeIterator($dir);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));

RecursiveTreeIteratorПризначений для роботи лінії по лінії, вихід досить прямо вперед з однією невеликою проблемою:

[tree]
 ├ tree\dirA
 │ ├ tree\dirA\dirB
 │ │ └ tree\dirA\dirB\fileD
 │ ├ tree\dirA\fileB
 │ └ tree\dirA\fileC
 └ tree\fileA

При використанні у поєднанні з a RecursiveDirectoryIteratorвін відображає всю назву шляху, а не лише ім'я файлу. Решта виглядає непогано. Це тому, що імена файлів генеруються SplFileInfo. Натомість вони повинні відображатися як базове ім'я. Бажаний результат є наступним:

/// Solved ///

[tree]
 ├ dirA
 │ ├ dirB
 │ │ └ fileD
 │ ├ fileB
 │ └ fileC
 └ fileA

Створіть клас декоратора, який можна використовувати RecursiveTreeIteratorзамість RecursiveDirectoryIterator. Він повинен містити базове ім'я поточного SplFileInfoзамість імені шляху. Остаточний фрагмент коду тоді може виглядати так:

$lines = new RecursiveTreeIterator(
    new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));

Ці фрагменти, зокрема, $unicodeTreePrefixє частиною суті в Додатку: Зроби сам: склади RecursiveTreeIteratorробочий рядок за рядком. .


Він не відповідає на поставлені запитання, містить фактичні помилки та пропускає основні моменти, коли ви переходите до налаштування ітерації. Загалом, це виглядає як погана спроба отримати нагороду за тему, про яку ви мало що знаєте, або, якщо знаєте, не можете визначити відповідь на поставлені запитання.
салата

2
Ну, що не відповідає на моє запитання "чому", ви просто робите більше слів, не кажучи багато. Можливо, ви починаєте з фактично помилки, яка враховує? Вкажіть на це, не тримайте це в своїй таємниці.
hakre

Перше речення неправильне: "RecursiveIteratorIterator - це IteratorIterator, який підтримує ...", це не відповідає дійсності.
салата

1
@salathe: Запрошуємо вас за відгук. Я відредагував відповідь, щоб звернутися до нього. Перше речення справді було неправильним і неповним. Я все ще залишив конкретні деталі реалізації, RecursiveIteratorIteratorоскільки це спільно з іншими типами, але я дав деякі технічні відомості про те, як це насправді працює. Приклади, які, на мою думку, добре показують відмінності: тип ітерації є основною відмінністю між ними. Немає ідеї, якщо ви купуєте тип ітерації, ви монетируєте це трохи по-іншому, але IMHO непростий із семантичними типами ітерацій.
hakre 02

1
Перша частина дещо вдосконалена, але як тільки ви починаєте переходити до прикладів, вона все одно потрапляє в фактичні неточності. Якби ви обрізали відповідь за горизонтальним правилом, це було б значно покращено.
salathe

32

У чому різниця між IteratorIteratorі RecursiveIteratorIterator?

Щоб зрозуміти різницю між цими двома ітераторами, спочатку слід трохи зрозуміти використовувані правила іменування та те, що ми розуміємо під «рекурсивними» ітераторами.

Рекурсивні та нерекурсивні ітератори

PHP має не "рекурсивні" ітератори, такі як ArrayIteratorі FilesystemIterator. Існують також "рекурсивні" ітератори, такі як RecursiveArrayIteratorі RecursiveDirectoryIterator. Останні мають методи, що дозволяють їх вивчити, перші - ні.

Коли екземпляри цих ітераторів перекреслюються самостійно, навіть рекурсивні, значення надходять лише з "верхнього" рівня, навіть якщо цикл переглядає вкладений масив або каталог із підкаталогами.

Рекурсивні ітератори реалізують рекурсивну поведінку (через hasChildren(), getChildren()), але не використовують її.

Можливо, було б краще думати про рекурсивні ітератори як про "повторювані" ітератори, вони мають можливість ітерації рекурсивно, але просто ітерація над екземпляром одного з цих класів цього не зробить. Щоб використати рекурсивну поведінку, продовжуйте читати.

RecursiveIteratorIterator

Тут RecursiveIteratorIteratorвходить грати. Він має знання про те, як викликати "повторювані" ітератори таким чином, щоб просвердлити структуру в звичайному, плоскому циклі. Він застосовує рекурсивну поведінку до дії. По суті, це робить роботу, переходячи через кожне зі значень в ітераторі, шукаючи, чи є "діти", до яких можна повернутися, чи ні, а також вступаючи в ці колекції дітей і виходячи з них. Ви приклеюєте екземпляр RecursiveIteratorIteratorу foreach, і він занурюється в структуру, так що вам не потрібно.

Якби RecursiveIteratorIteratorне використовувалося, вам довелося б писати власні рекурсивні цикли, щоб використовувати рекурсивну поведінку, перевіряючи ітератори "рекурсивності" hasChildren()та використовуючи їх getChildren().

Отже, це короткий огляд того RecursiveIteratorIterator, чим він відрізняється від IteratorIterator? Ну, ви в основному задаєте таке саме запитання, як і яка різниця між кошеням і деревом? Те, що обидва вони з’являються в одній енциклопедії (або в посібнику для ітераторів), ще не означає, що слід заплутатися між ними.

ІтераторІтератор

Завданням IteratorIteratorє взяти будь-який Traversableоб’єкт і обернути його таким чином, щоб він задовольняв Iteratorінтерфейсу. Використання цього полягає в тому, щоб потім мати можливість застосувати специфічну для ітератора поведінку до неітераторного об’єкта.

Щоб навести практичний приклад, DatePeriodклас є, Traversableале не є Iterator. Таким чином, ми можемо перебирати його значення за допомогою, foreach()але не можемо робити інших дій, які ми зазвичай робимо з ітератором, наприклад, фільтрації.

ЗАВДАННЯ : Прокрутіть понеділок, середу та п’ятницю наступних чотирьох тижнів.

Так, це тривіально, використовуючи foreach-ing над DatePeriodі використовуючи внутрішній if()цикл; але справа не в цьому прикладі!

$period = new DatePeriod(new DateTime, new DateInterval('P1D'), 28);
$dates  = new CallbackFilterIterator($period, function ($date) {
    return in_array($date->format('l'), array('Monday', 'Wednesday', 'Friday'));
});
foreach ($dates as $date) { … }

Вищезазначений фрагмент не працюватиме, оскільки CallbackFilterIteratorочікується екземпляр класу, який реалізує Iteratorінтерфейс, а DatePeriodце ні. Однак, оскільки це так, Traversableми можемо легко задовольнити цю вимогу, використовуючи IteratorIterator.

$period = new IteratorIterator(new DatePeriod(…));

Як бачите, це не має нічого спільного з ітерацією класів ітераторів, ні рекурсією, і в цьому полягає різниця між IteratorIteratorі RecursiveIteratorIterator.

Резюме

RecursiveIteraratorIteratorпризначений для ітерації RecursiveIterator(ітератора, що повторюється), використовуючи доступну рекурсивну поведінку.

IteratorIteratorпризначений для застосування Iteratorповедінки до неітераторів, Traversableоб’єктів.


Це не IteratorIteratorпросто стандартний тип обходу лінійного порядку Traversableоб’єктів? Ті, які можна було б використовувати без нього просто так, foreachяк є? А ще далі, це не RecursiveIterator завждиTraversable і , отже , не тільки , IteratorIteratorа й RecursiveIteratorIteratorзавжди «для застосування Iteratorповедінка не-ітератор, об'єкти Traversable» ? (Я б зараз сказав, що foreachзастосовує тип ітерації через об'єкт ітератора до об'єктів контейнера, що реалізують інтерфейс типу ітератора, тому це завжди ітератори-контейнери-об'єкти Traversable)
hakre

Як зазначено у моїй відповіді, IteratorIteratorце клас, який полягає у загортанні Traversableоб'єктів у файл Iterator. Більше нічого . Здається, ви застосовуєте цей термін загальніше.
салата

Здавалося б, інформативна відповідь. Одне питання, чи не буде RecursiveIteratorIterator також обертати об’єкти, щоб вони також мали доступ до поведінки Iterator? Єдиною різницею між ними буде те, що RecursiveIteratorIterator може детально розгорнути, тоді як IteratorIterator не може?
Mike Purcell

@salathe, ти знаєш, чому рекурсивний ітератор (RecursiveDirectoryIterator) не реалізує поведінку hasChildren (), getChildren ()?
anru

8
+1 за вимову "рецидив". Назва довго збивала мене з шляху, оскільки Recursivein RecursiveIteratorпередбачає поведінку, тоді як більш підходяще ім'я могло б бути таким, яке описує здатність, наприклад RecursibleIterator.
козел

0

При використанні з iterator_to_array(), RecursiveIteratorIteratorбуде рекурсивно проходити масив, щоб знайти всі значення. Це означає, що це згладить вихідний масив.

IteratorIterator збереже початкову ієрархічну структуру.

Цей приклад наочно покаже вам різницю:

$array = array(
               'ford',
               'model' => 'F150',
               'color' => 'blue', 
               'options' => array('radio' => 'satellite')
               );

$recursiveIterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
var_dump(iterator_to_array($recursiveIterator, true));

$iterator = new IteratorIterator(new ArrayIterator($array));
var_dump(iterator_to_array($iterator,true));

Це абсолютно оманливе. new IteratorIterator(new ArrayIterator($array))еквівалентно new ArrayIterator($array), тобто зовнішнє IteratorIteratorнічого не робить. Більше того, згладжування виводу не має нічого спільного iterator_to_array- воно просто перетворює ітератор у масив. Згладжування є властивістю того, як RecursiveArrayIteratorпроходить його внутрішній ітератор.
Quolonel Question

0

RecursiveDirectoryIterator він відображає ціле ім'я шляху, а не тільки ім'я файлу. Решта виглядає непогано. Це тому, що імена файлів генеруються SplFileInfo. Натомість вони повинні відображатися як базове ім'я. Бажаний результат є наступним:

$path =__DIR__;
$dir = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($dir,RecursiveIteratorIterator::SELF_FIRST);
while ($files->valid()) {
    $file = $files->current();
    $filename = $file->getFilename();
    $deep = $files->getDepth();
    $indent = str_repeat('│ ', $deep);
    $files->next();
    $valid = $files->valid();
    if ($valid and ($files->getDepth() - 1 == $deep or $files->getDepth() == $deep)) {
        echo $indent, "├ $filename\n";
    } else {
        echo $indent, "└ $filename\n";
    }
}

вихід:

tree
 ├ dirA
 │ ├ dirB
 │ │ └ fileD
 │ ├ fileB
 │ └ fileC
 └ fileA
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.