Jak to RecursiveIteratorIterator
działa?
Podręcznik PHP nie ma nic dobrze udokumentowanego ani wyjaśnionego. Jaka jest różnica między IteratorIterator
i RecursiveIteratorIterator
?
Jak to RecursiveIteratorIterator
działa?
Podręcznik PHP nie ma nic dobrze udokumentowanego ani wyjaśnionego. Jaka jest różnica między IteratorIterator
i RecursiveIteratorIterator
?
RecursiveIteratorIterator
działa, czy już zrozumiałeś, jak IteratorIterator
działa? Mam na myśli to, że jest w zasadzie taki sam, tylko interfejs, który jest używany przez te dwa, jest inny. Czy jesteś bardziej zainteresowany kilkoma przykładami, czy chcesz zobaczyć różnice między implementacją kodu C?
IteratorIterator
mapy Iterator
i IteratorAggregate
do miejsca Iterator
, gdzie REcusiveIteratorIterator
jest używany do przemierzania recusivly aRecursiveIterator
Odpowiedzi:
RecursiveIteratorIterator
jest konkretnym Iterator
wdrażaniu przechodzenie drzewa . Umożliwia programiście przechodzenie przez obiekt kontenera, który implementuje RecursiveIterator
interfejs. Ogólne zasady, typy, semantyka i wzorce iteratorów można znaleźć w sekcji Iterator w Wikipedii .
W odróżnieniu od IteratorIterator
konkretnej Iterator
implementacji przechodzenia przez obiekt w porządku liniowym (i domyślnie akceptującej dowolny rodzaj Traversable
w swoim konstruktorze), RecursiveIteratorIterator
umożliwia pętlę po wszystkich węzłach uporządkowanego drzewa obiektów, a jego konstruktor przyjmuje plik RecursiveIterator
.
W skrócie: RecursiveIteratorIterator
pozwala na pętlę po drzewie,IteratorIterator
pozwala na zapętlenie listy. Pokażę to wkrótce z kilkoma przykładami kodu poniżej.
Technicznie rzecz biorąc, działa to przez wyrwanie się z liniowości przez przejście przez wszystkie elementy potomne węzłów (jeśli takie istnieją). Jest to możliwe, ponieważ z definicji wszystkie dzieci węzła są ponownie RecursiveIterator
. Górny poziom Iterator
następnie wewnętrznie układa różne stosy RecursiveIterator
według ich głębokości i utrzymuje wskaźnik na bieżącym aktywnym podrzędnym Iterator
celu przejścia.
Pozwala to na odwiedzenie wszystkich węzłów drzewa.
Podstawowe zasady są takie same jak w przypadku IteratorIterator
: Interfejs określa typ iteracji, a podstawowa klasa iteratora jest implementacją tej semantyki. Porównaj z poniższymi przykładami, w przypadku pętli liniowej foreach
zwykle nie myśl o szczegółach implementacji, chyba że musisz zdefiniować nowy Iterator
(np. Gdy jakiś konkretny typ sam w sobie nie jestTraversable
).
W przypadku przemierzania rekurencyjnego - chyba że nie używasz predefiniowanej Traversal
iteracji przechodzenia, która już ma rekurencyjną iterację przechodzenia - zwykle musisz utworzyć wystąpienie istniejącej RecursiveIteratorIterator
iteracji lub nawet napisać rekurencyjną iterację przechodzenia, która jest Traversable
twoją własną, aby mieć tego typu iterację przechodzenia foreach
.
Wskazówka: prawdopodobnie nie zaimplementowałeś jednego ani drugiego własnego, więc może to być coś, co warto zrobić, aby poznać praktyczne różnice, jakie mają. Na końcu odpowiedzi znajdziesz sugestię zrób to sam.
Krótko mówiąc, różnice techniczne:
IteratorIterator
pobiera dowolne Traversable
dla przechodzenia liniowego, RecursiveIteratorIterator
potrzebuje bardziej szczegółowej RecursiveIterator
pętli po drzewie.IteratorIterator
eksponuje swoją główną Iterator
przelotkę getInnerIerator()
, RecursiveIteratorIterator
udostępnia bieżące aktywne sub- Iterator
tylko za pomocą tej metody.IteratorIterator
nie jest świadomy niczego takiego jak rodzic lub dzieci, RecursiveIteratorIterator
wie również, jak zdobyć i przemierzać dzieci.IteratorIterator
nie potrzebuje stosu iteratorów, RecursiveIteratorIterator
ma taki stos i zna aktywny pod-iterator.IteratorIterator
ma swoją kolejność ze względu na liniowość i nie ma wyboru, RecursiveIteratorIterator
ma wybór dla dalszego przejścia i musi zdecydować dla każdego węzła (decyduje tryb naRecursiveIteratorIterator
).RecursiveIteratorIterator
ma więcej metod niż IteratorIterator
.Podsumowując: RecursiveIterator
to konkretny typ iteracji (pętla po drzewie), który działa na własnych iteratorach, a mianowicie RecursiveIterator
. Jest to ta sama podstawowa zasada jak w przypadku IteratorIerator
, ale typ iteracji jest inny (kolejność liniowa).
Idealnie byłoby również stworzyć własny zestaw. Jedyną konieczną rzeczą jest to, że Twój iterator implementuje, Traversable
co jest możliwe za pośrednictwem Iterator
lub IteratorAggregate
. Następnie możesz go używać z foreach
. Na przykład jakiś obiekt iteracji rekurencyjnej przechodzenia przez drzewo trójskładnikowe wraz z odpowiednim interfejsem iteracji dla obiektów kontenera.
Przyjrzyjmy się kilku przykładom z życia, które nie są tak abstrakcyjne. Pomiędzy interfejsami, konkretnymi iteratorami, obiektami kontenerów i semantyką iteracji może nie jest to zły pomysł.
Weźmy jako przykład listę katalogów. Weź pod uwagę, że masz na dysku następujące drzewo plików i katalogów:
Podczas gdy iterator z porządkiem liniowym po prostu przechodzi przez folder i pliki najwyższego poziomu (lista pojedynczego katalogu), iterator rekurencyjny przechodzi również przez podfoldery i wyświetla listę wszystkich folderów i plików (lista katalogów z listą jego podkatalogów):
Non-Recursive Recursive
============= =========
[tree] [tree]
├ dirA ├ dirA
└ fileA │ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Możesz łatwo porównać to, z IteratorIterator
którym nie ma rekursji przy przechodzeniu po drzewie katalogów. I RecursiveIteratorIterator
który może przejść do drzewa, jak pokazuje lista Rekurencyjna.
Na początek bardzo prosty przykład z DirectoryIterator
implementacją, Traversable
która pozwala foreach
na iterację :
$path = 'tree';
$dir = new DirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
Przykładowy wynik dla powyższej struktury katalogów to:
[tree]
├ .
├ ..
├ dirA
├ fileA
Jak widać, nie jest to jeszcze używane IteratorIterator
ani RecursiveIteratorIterator
. Zamiast tego po prostu używa foreach
tego, który działa na Traversable
interfejsie.
Ponieważ foreach
domyślnie zna tylko typ iteracji o nazwie porządek liniowy, możemy chcieć jawnie określić typ iteracji. Na pierwszy rzut oka może się to wydawać zbyt rozwlekłe, ale dla celów demonstracyjnych (i aby różnica była RecursiveIteratorIterator
widoczna później), określmy liniowy typ iteracji, wyraźnie określając IteratorIterator
typ iteracji dla listingu katalogów:
$files = new IteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
Ten przykład jest prawie identyczny z pierwszym, różnica polega na tym, że $files
jest to teraz IteratorIterator
typ iteracji dla Traversable
$dir
:
$files = new IteratorIterator($dir);
Jak zwykle czynność iteracji jest wykonywana przez foreach
:
foreach ($files as $file) {
Wynik jest dokładnie taki sam. Więc co się zmieniło? Inny jest obiekt używany w foreach
. W pierwszym przykładzie jest to DirectoryIterator
aw drugim przykładzie jest to IteratorIterator
. Pokazuje to elastyczność, jaką mają iteratory: możesz je zastąpić innymi, kod wewnątrz foreach
po prostu nadal działa zgodnie z oczekiwaniami.
Zacznijmy od całej listy, łącznie z podkatalogami.
Ponieważ określiliśmy teraz typ iteracji, rozważmy zmianę go na inny typ iteracji.
Wiemy, że musimy teraz przejść całe drzewo, a nie tylko pierwszy poziom. Aby wykonać tę pracę z prostym foreach
, potrzebujemy innego typu iteratora:RecursiveIteratorIterator
. I to można tylko iterować po obiektach kontenerów, które mają RecursiveIterator
interfejs .
Interfejs jest umową. Każda klasa implementująca ją może być używana razem z RecursiveIteratorIterator
. Przykładem takiej klasy jestRecursiveDirectoryIterator
, która jest czymś w rodzaju rekurencyjnej odmiany DirectoryIterator
.
Zobaczmy pierwszy przykład kodu przed napisaniem jakiegokolwiek innego zdania ze słowem I:
$dir = new RecursiveDirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
Ten trzeci przykład jest prawie identyczny z pierwszym, ale tworzy inne wyniki:
[tree]
├ tree\.
├ tree\..
├ tree\dirA
├ tree\fileA
Okej, nie tak inaczej, nazwa pliku zawiera teraz nazwę ścieżki z przodu, ale reszta również wygląda podobnie.
Jak pokazuje przykład, nawet obiekt katalogu już implementuje RecursiveIterator
interfejs, ale to nie wystarczy, aby foreach
przejść przez całe drzewo katalogów. Tutaj RecursiveIteratorIterator
zaczyna się akcja. Przykład 4 pokazuje, jak:
$files = new RecursiveIteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
Użycie RecursiveIteratorIterator
zamiast tylko poprzedniego $dir
obiektu spowoduje foreach
przechodzenie przez wszystkie pliki i katalogi w sposób rekurencyjny. Następnie wyświetla listę wszystkich plików, ponieważ typ iteracji obiektu został określony teraz:
[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
Powinno to już pokazać różnicę między przechodzeniem płaskim i przechodzeniem po drzewie. RecursiveIteratorIterator
Jest w stanie przechodzić żadnego drzewiastą strukturę w postaci listy elementów. Ponieważ jest więcej informacji (takich jak poziom, na którym obecnie odbywa się iteracja), można uzyskać dostęp do obiektu iteratora podczas iteracji po nim i na przykład wcinać dane wyjściowe:
echo "[$path]\n";
foreach ($files as $file) {
$indent = str_repeat(' ', $files->getDepth());
echo $indent, " ├ $file\n";
}
Wynik przykładu 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
Oczywiście to nie wygrywa konkursu piękności, ale pokazuje, że w przypadku iteratora rekurencyjnego dostępnych jest więcej informacji niż tylko liniowy porządek klucza i wartości . Nawet foreach
może wyrazić ten rodzaj liniowości, dostęp do samego iteratora pozwala uzyskać więcej informacji.
Podobnie jak w przypadku metainformacji, istnieją również różne sposoby poruszania się po drzewie, a tym samym porządkowania wyników. To jest tryb programuRecursiveIteratorIterator
i można go ustawić za pomocą konstruktora.
W następnym przykładzie RecursiveDirectoryIterator
polecenie usunie kropki ( .
i ..
), ponieważ ich nie potrzebujemy. Ale także tryb rekursji zostanie zmieniony, aby najpierw zająć element nadrzędny (podkatalog) (SELF_FIRST
) przed dziećmi (pliki i podkatalogi w podkatalogu):
$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";
}
Dane wyjściowe pokazują teraz poprawnie wymienione pozycje podkatalogów, jeśli porównasz z poprzednim wyjściem, których tam nie było:
[tree]
├ tree\dirA
├ tree\dirA\dirB
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
Dlatego tryb rekurencyjny kontroluje, co i kiedy zwracane jest ramię lub liść w drzewie, dla przykładu katalogu:
LEAVES_ONLY
(domyślnie): Tylko lista plików, bez katalogów.SELF_FIRST
(powyżej): Lista katalogu, a następnie plików w nim.CHILD_FIRST
(bez przykładu): Najpierw wyświetla listę plików w podkatalogu, a następnie w katalogu.Dane wyjściowe przykładu 5 z dwoma innymi trybami:
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
Porównując to ze standardowym przechodzeniem, wszystkie te rzeczy nie są dostępne. Dlatego iteracja rekurencyjna jest nieco bardziej złożona, gdy trzeba ją owinąć, jednak jest łatwa w użyciu, ponieważ zachowuje się jak iterator, umieszcza się ją w a foreach
i gotowe.
Myślę, że to wystarczające przykłady na jedną odpowiedź. Możesz znaleźć pełny kod źródłowy, a także przykład, aby wyświetlić ładnie wyglądające drzewa ascii w tym skrócie: https://gist.github.com/3599532
Zrób to sam: stwórz
RecursiveTreeIterator
pracę wiersz po wierszu.
Przykład 5 wykazał, że są dostępne metainformacje o stanie iteratora. Jednak zostało to celowo zademonstrowane w ramach foreach
iteracji. W prawdziwym życiu to naturalnie należy doRecursiveIterator
.
Lepszym przykładem jest RecursiveTreeIterator
, zajmuje się wcięciami, przedrostkami i tak dalej. Zobacz następujący fragment kodu:
$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$lines = new RecursiveTreeIterator($dir);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
RecursiveTreeIterator
Przeznaczony jest do pracy przy linii linii, wyjście jest dość proste z jednym małym problemem:
[tree]
├ tree\dirA
│ ├ tree\dirA\dirB
│ │ └ tree\dirA\dirB\fileD
│ ├ tree\dirA\fileB
│ └ tree\dirA\fileC
└ tree\fileA
W połączeniu z a RecursiveDirectoryIterator
wyświetla całą nazwę ścieżki, a nie tylko nazwę pliku. Reszta wygląda dobrze. Dzieje się tak, ponieważ nazwy plików są generowane przez SplFileInfo
. Zamiast tego powinny być wyświetlane jako nazwa basenowa. Żądane wyjście jest następujące:
/// Solved ///
[tree]
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Utwórz klasę dekoratora, której można używać RecursiveTreeIterator
zamiast RecursiveDirectoryIterator
. Powinien zawierać nazwę podstawową prądu SplFileInfo
zamiast ścieżki. Ostateczny fragment kodu mógłby wtedy wyglądać następująco:
$lines = new RecursiveTreeIterator(
new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
Te fragmenty, w tym, $unicodeTreePrefix
są częścią istoty w Dodatku: Zrób to sam: Stwórz RecursiveTreeIterator
pracę wiersz po wierszu. .
RecursiveIteratorIterator
ponieważ jest to wspólne z innymi typami, ale podałem kilka informacji technicznych, jak to faktycznie działa. Myślę, że przykłady dobrze pokazują różnice: rodzaj iteracji jest główną różnicą między nimi. Nie mam pojęcia, jeśli kupisz typ iteracji, który ubijesz trochę inaczej, ale IMHO nie jest łatwe z semantyką typów iteracji.
Jaka jest różnica między
IteratorIterator
iRecursiveIteratorIterator
?
Aby zrozumieć różnicę między tymi dwoma iteratorami, należy najpierw trochę zrozumieć stosowane konwencje nazewnictwa i co rozumiemy przez iteratory „rekurencyjne”.
PHP ma nie „rekurencyjne” iteratory, takie jak ArrayIterator
i FilesystemIterator
. Istnieją również „rekurencyjne” iteratory, takie jak RecursiveArrayIterator
iRecursiveDirectoryIterator
. Ci drudzy mają metody umożliwiające ich wnikanie, ci pierwsi nie.
Gdy instancje tych iteratorów są zapętlone samodzielnie, nawet te rekurencyjne, wartości pochodzą tylko z „najwyższego” poziomu, nawet jeśli są zapętlone po zagnieżdżonej tablicy lub katalogu z podkatalogami.
Iteratory rekurencyjne implementują zachowanie rekurencyjne (via hasChildren()
, getChildren()
), ale tego nie robią wykorzystują .
Lepiej byłoby myśleć o iteratorach rekurencyjnych jako o iteratorach „rekurencyjnych”, mają one rozszerzenie zdolność do iteracji rekurencyjnej, ale zwykłe iterowanie po instancji jednej z tych klas tego nie zrobi. Aby wykorzystać zachowanie rekurencyjne, czytaj dalej.
Tutaj właśnie RecursiveIteratorIterator
pojawia się gra. Posiada wiedzę na temat wywoływania „rekurencyjnych” iteratorów w taki sposób, aby drążyć strukturę w normalnej, płaskiej pętli. Wprowadza rekurencyjne zachowanie do działania. Zasadniczo wykonuje pracę polegającą na przechodzeniu przez każdą z wartości w iteratorze, sprawdzaniu, czy są „dzieci”, do których można się powrócić, czy nie, oraz wchodzeniu i wychodzeniu z tych zbiorów dzieci. Wbijasz wystąpienie RecursiveIteratorIterator
w foreach, a on nurkuje w strukturze, więc nie musisz tego robić.
Gdyby RecursiveIteratorIterator
nie zostało użyte, musiałbyś napisać własne pętle rekurencyjne, aby wykorzystać zachowanie rekurencyjne, sprawdzając iterator „rekurencyjny” hasChildren()
i używając getChildren()
.
Więc to jest krótkie omówienie RecursiveIteratorIterator
, czym się różni od IteratorIterator
? Cóż, w zasadzie zadajesz to samo pytanie, co Jaka jest różnica między kotkiem a drzewem? To, że oba pojawiają się w tej samej encyklopedii (lub podręczniku dla iteratorów), nie oznacza, że powinieneś się pomylić między nimi.
Zadaniem tego IteratorIterator
jest pobranie dowolnego Traversable
obiektu i owinięcie go tak, aby spełniał wymagania Iterator
interfejsu. Służy do tego możliwość zastosowania zachowania specyficznego dla iteratora na obiekcie niebędącym iteratorem.
Aby dać praktyczny przykład, DatePeriod
klasa jest, Traversable
ale nie jest Iterator
. W związku z tym możemy zapętlić jego wartości za pomocą, foreach()
ale nie możemy zrobić innych rzeczy, które normalnie wykonalibyśmy za pomocą iteratora, takich jak filtrowanie.
ZADANIE : Powtarzaj w poniedziałki, środy i piątki przez następne cztery tygodnie.
Tak, jest to trywialne foreach
omijanie DatePeriod
i używanie if()
w pętli; ale nie o to chodzi w tym przykładzie!
$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) { … }
Powyższy fragment kodu nie zadziała, ponieważ CallbackFilterIterator
oczekuje wystąpienia klasy implementującej Iterator
interfejs, a DatePeriod
nie. Ponieważ jednak tak jest Traversable
, możemy łatwo spełnić to wymaganie, używając IteratorIterator
.
$period = new IteratorIterator(new DatePeriod(…));
Jak widać, nie ma to nic wspólnego z iteracją po klasach iteratorów ani rekursją, i na tym polega różnica między IteratorIterator
i RecursiveIteratorIterator
.
RecursiveIteraratorIterator
służy do iteracji po RecursiveIterator
(„rekurencyjnym” iteratorze), wykorzystując dostępne zachowanie rekurencyjne.
IteratorIterator
służy do stosowania Iterator
zachowania do Traversable
obiektów , które nie są iteratorami .
IteratorIterator
tylko standardowy typ przechodzenia w kolejności liniowej dla Traversable
obiektów? Te, które mogłyby być użyte bez tego, tak foreach
jak jest? A nawet dalej, czy nie jest RecursiveIterator
zawsze a, Traversable
a zatem nie tylko, IteratorIterator
ale także RecursiveIteratorIterator
zawsze „do stosowania Iterator
zachowania do obiektów nie-iteracyjnych, przemieszczalnych” ? (Powiedziałbym teraz, że foreach
stosuje typ iteracji za pośrednictwem obiektu iteratora na obiektach kontenera, które implementują interfejs typu iteratora, więc są to obiekty-kontenera-iteratora, zawsze Traversable
)
IteratorIterator
to klasa, która polega na zawijaniu Traversable
obiektów w plik Iterator
. Nic więcej . Wydaje się, że używasz tego terminu bardziej ogólnie.
Recursive
in RecursiveIterator
sugeruje zachowanie, podczas gdy bardziej odpowiednia byłaby nazwa opisująca zdolności, np RecursibleIterator
.
W przypadku użycia z iterator_to_array()
, RecursiveIteratorIterator
będzie rekurencyjnie przeszukiwać tablicę, aby znaleźć wszystkie wartości. Oznacza to, że spłaszczy oryginalną tablicę.
IteratorIterator
zachowa pierwotną strukturę hierarchiczną.
Ten przykład jasno pokaże różnicę:
$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))
to znaczy new ArrayIterator($array)
, że strona zewnętrzna IteratorIterator
nic nie robi. Co więcej, spłaszczanie wyniku nie ma z tym nic wspólnego iterator_to_array
- po prostu przekształca iterator w tablicę. Spłaszczenie jest właściwością sposobu, w jaki RecursiveArrayIterator
przechodzi jego wewnętrzny iterator.
RecursiveDirectoryIterator wyświetla całą nazwę ścieżki, a nie tylko nazwę pliku. Reszta wygląda dobrze. Dzieje się tak, ponieważ nazwy plików są generowane przez SplFileInfo. Zamiast tego powinny być wyświetlane jako nazwa basenowa. Żądane wyjście jest następujące:
$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";
}
}
wynik:
tree
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA