PHP/Iteratory: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Nie podano opisu zmian
 
Linia 9:
Przed zabraniem się za programowanie obiektowe omawialiśmy m.in. funkcje dostępu do plików. Korzystanie z nich było średnio wygodne. Przykładowo, aby odczytać zawartość katalogu, musieliśmy manipulować dużą ilością funkcji, a na dokładkę otrzymywaliśmy jedynie nazwę, nie wiedząc nawet czy mamy do czynienia z plikiem czy z katalogiem. Iteratory doskonale nadają się do operacji na systemie plików. W SPL-u mamy klasę <code>DirectoryIterator</code>, która pozwoli nam pobrać zawartość katalogu:
 
<sourcesyntaxhighlight lang="php" line><?php
try
{
Linia 34:
echo 'Błąd: '.$exception->getMessage();
}
</syntaxhighlight>
</source>
 
Teraz iteracja po katalogach jest wyjątkowo prosta, a na dodatek zyskujemy czytelne, obiektowe API. Gdyby katalog nie istniał, zamiast dziwacznych komunikatów dostaniemy normalny wyjątek, który możemy przechwycić i oprogramować według naszych potrzeb.
Linia 40:
Zastanówmy się teraz, jak wyświetlić katalogi wraz z zawartością ich podkatalogów, czyli innymi słowy - kompletne drzewo systemu plików. Jest to możliwe dzięki dwóm kolejnym iteratorom: <code>RecursiveDirectoryIterator</code> oraz <code>RecursiveIteratorIterator</code>. Powinny być one używane razem - pierwszy bowiem sam z siebie do podkatalogów nie wejdzie, ale udostępnia metody <code>getChildren()</code> i <code>hasChildren()</code>, które są wykorzystywane przez drugi. <code>RecursiveIteratorIterator</code> służy do rekurencyjnego przechodzenia po dowolnej strukturze, która implementuje powyższe dwie metody (także naszej własnej). My jednak wróćmy do naszych katalogów:
 
<sourcesyntaxhighlight lang="php" line><?php
$dirIterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('./s3/'), RecursiveIteratorIterator::SELF_FIRST);
foreach($dirIterator as $file)
Linia 53:
}
}
</syntaxhighlight>
</source>
 
Konstruktor klasy <code>RecursiveIteratorIterator</code> pobiera jako pierwszy argument obiekt iteratora, po którym należy przejść rekurencyjnie. W naszym przypadku jest to iterator do przechodzenia po katalogach. W drugim, opcjonalnym parametrze możemy ustawić sposób przechodzenia przy pomocy stałych klasowych:
Linia 65:
Powyższy przykład pokazał jeszcze jedną ciekawą właściwość iteratorów SPL, a mianowicie możliwość ich komponowania w czasie wykonywania. Zauważmy, że nie mamy jednego iteratora do rekurencyjnego przechodzenia po katalogach, ale mamy dwa mniejsze - jeden oferujący rekurencję, drugi - wędrówki po systemie plików. Dopiero łącząc je ze sobą uzyskujemy to, co chcemy. Gdy przejdziemy do późniejszych rozdziałów podręcznika zauważymy, że takie postępowanie ma swoją fachową nazwę ''dekorator''. Oto inne zastosowanie kompozycji iteratorów. Mamy tablicę z sześcioma elementami, ale chcemy wyświetlić jedynie elementy od 2 do 4, przy czym nie znamy ich indeksów (pętla '''for''' odpada). Wykorzystajmy zatem iterator tablicowy <code>ArrayIterator</code> i połączmy go z <code>LimitIterator</code>, który doda odpowiednie limity:
 
<sourcesyntaxhighlight lang="php" line><?php
$array = new ArrayIterator(
array('jabłko', 'banan', 'gruszka', 'wisienka', 'czereśnia', 'truskawka')
Linia 76:
}
echo '</ul>';
</syntaxhighlight>
</source>
 
Chociaż tablice współpracują z '''foreach''', ale nie współpracują z innymi iteratorami, dlatego w tym przypadku musimy ją opakować w specjalny obiekt klasy <code>ArrayIterator</code>. Domyślnie pokazałby on nam wszystkie owoce, ale my chcemy dostać jedynie "gruszkę" oraz "wisienkę". Tu do akcji wkracza <code>LimitIterator</code>, któremu mówimy, że chcemy dostać dwa elementy (trzeci argument) począwszy od drugiego (drugi argument - liczone od zera!). Uruchom ten przykład i spróbuj pobawić się ustawieniami, aby zobaczyć, co się stanie.
Linia 84:
Gdy wiemy już, ile można osiągnąć dzięki iteratorom, pora nauczyć się samodzielnie je tworzyć. Sprowadza się to do zaimplementowania w naszej klasie jednego z dwóch specjalnych interfejsów. Zaczniemy od omówienia prostszego <code>IteratorAggregate</code>, który wymaga dodania dokładnie jednej metody: <code>getIterator()</code>. Jej zadaniem jest... utworzenie iteratora, który zajmie się procesem iteracji w imieniu naszej klasy. Przypomnijmy sobie naszą klasę '''Config''', która opcje konfiguracyjne przechowuje w tablicy. Dla celów debugowych chcielibyśmy mieć możliwość prostego wyświetlenia wszystkich opcji. Przypomnijmy sobie strukturę klasy:
 
<sourcesyntaxhighlight lang="php" line><?php
class Config implements Countable
{
Linia 93:
 
} // end Config;
</syntaxhighlight>
</source>
 
Opcje przechowywane są w polu <code>$_config</code>, dlatego to po nim powinniśmy iterować. Dodajmy do klasy interfejs <code>IteratorAggregate</code> oraz zaimplementujmy niezbędną metodę:
 
<sourcesyntaxhighlight lang="php" line><?php
class Config implements Countable, IteratorAggregate
{
Linia 110:
// metody klasy
} // end Config;
</syntaxhighlight>
</source>
 
Ponieważ lista opcji jest tablicą, w naszym imieniu iterować będzie po niej <code>ArrayIterator</code>, który tworzymy w momencie wywołania <code>getIterator()</code>. Możemy teraz wyświetlić wszystkie opcje:
 
<sourcesyntaxhighlight lang="php" line><?php
require('./Config.php');
 
Linia 126:
}
echo '</ul>';
</syntaxhighlight>
</source>
 
=== Interfejs Iterator ===
Linia 140:
Aby lepiej zrozumieć, jak PHP z nich korzysta, spróbujmy przetłumaczyć sobie wywołanie pętli '''foreach''' na odpowiadające jej wywołanie pętli '''while''':
 
<sourcesyntaxhighlight lang="php" line><?php
// Tworzymy dowolny iterator
$iterator = new SomeIterator;
Linia 161:
$iterator->next();
}
</syntaxhighlight>
</source>
 
Wyposażeni w taką informację spróbujmy napisać iterator zliczający. Będzie on odliczać od podanej liczby zadaną ilość razy. Jako wartość będzie zwracana aktualna liczba, a jako klucz - numer iteracji.
 
<sourcesyntaxhighlight lang="php" line><?php
class CountingIterator implements Iterator
{
Linia 213:
}
echo '</ul>';
</syntaxhighlight>
</source>
 
Powyższy przykład wyświetli dziesięć kolejnych liczb, począwszy od wartości 5. Zwróćmy uwagę, że w ten sposób zmieniliśmy '''foreach''' w pętlę '''for'''. Oczywiście w prawdziwych aplikacjach WWW nie będzie to mieć zbyt wielkiego sensu, ale dobrze pokazuje istotę procesu iteracji. W ramach ćwiczenia spróbuj zaimplementować interfejs <code>Iterator</code> w miejsce <code>IteratorAggregate</code> w klasie '''Config'''. Będziesz musiał iterować po tablicy asocjacyjnej, dlatego wszystkie metody interfejsu będą zwykłymi nakładkami na analogiczne funkcje obsługi tablic: <code>reset()</code>, <code>next()</code>, <code>key()</code> oraz <code>current()</code>. Jeśli nie pamiętasz ich działania, posłuż się dokumentacją i spróbuj rozwiązać problem, jak w metodzie <code>valid()</code> sprawdzić, że dotarliśmy do końca tablicy.