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

Usunięta treść Dodana treść
Zyx (dyskusja | edycje)
rozpoczęcie rozdziału
 
Zyx (dyskusja | edycje)
dokończenie rozdziału
Linia 81:
 
=== Interfejs IteratorAggregate ===
 
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:
 
<source lang="php" line><?php
class Config implements Countable
{
private $_config = array();
private $_awaitingLoaders = array();
// metody klasy
 
} // end Config;
</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ę:
 
<source lang="php" line><?php
class Config implements Countable, IteratorAggregate
{
private $_config = array();
private $_awaitingLoaders = array();
public function getIterator()
{
return new ArrayIterator($this->_config);
} // end getIterator();
 
// metody klasy
} // end Config;
</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:
 
<source lang="php" line><?php
require('./Config.php');
 
$config = new Config;
$config->addLoader(new FileConfigLoader('./config.ini.php'));
 
echo '<ul>';
foreach($config as $name => $value)
{
echo '<li>'.$name.': '.$value.'</li>';
}
echo '</ul>';
</source>
 
=== Interfejs Iterator ===
 
Interfejs <code>IteratorAggregate</code> jest przydatny w najprostszych sytuacjach, gdy mamy pasujący iterator, ale pojawia się problem, skąd taki wziąć gdy żaden nam nie pasuje? Pełną kontrolę nad przebiegiem procesu iteracji zapewnia dopiero interfejs <code>Iterator</code>, który wymaga dodania do naszej klasy pięciu metod:
 
# <code>reset()</code> - ustawia kursor na początku kolekcji.
# <code>valid()</code> - sprawdza czy kursor znajduje się we właściwym położeniu (np. czy nie wyszedł poza rozmiar tablicy).
# <code>next()</code> - przesuwa kursor na kolejną pozycję.
# <code>key()</code> - zwraca klucz aktualnej pozycji.
# <code>current()</code> - zwraca wartość aktualnej pozycji.
 
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''':
 
<source lang="php" line><?php
// Tworzymy dowolny iterator
$iterator = new SomeIterator;
 
// Foreach
foreach($iterator as $name => $value)
{
...
}
 
// Równoważny mu while
$iterator->reset();
while($iterator->valid())
{
$name = $iterator->key();
$value = $iterator->value();
 
...
 
$iterator->next();
}
</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.
 
<source lang="php" line><?php
class CountingIterator implements Iterator
{
private $_start;
private $_end;
private $_key;
private $_value;
 
public function __construct($start, $offset)
{
$this->_start = $start;
$this->_offset = $offset;
} // end __construct();
 
public function reset()
{
$this->_key = 0;
$this->_value = $this->_start;
} // end reset();
 
public function valid()
{
return $this->_key < $this->_offset;
} // end valid();
 
public function next()
{
$this->_key++;
$this->_value++;
} // end next();
 
public function key()
{
return $this->_key;
} // end key();
 
public function current()
{
return $this->_value;
} // end current();
} // end CountingIterator;
 
echo '<ul>';
foreach(new CountingIterator(5, 10) as $idx => $value)
{
echo '<li>'.$idx.'. '.$value.'</li>';
}
echo '</ul>';
</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.
 
== Zakończenie ==
 
Iteratory są bardzo wygodnym elementem PHP pozwalającym ukryć przed programistą wiele zbędnych szczegółów technicznych. Jednak i tutaj należy korzystać z nich z umiarem i nie stosować, gdy nie ma ku temu żadnego logicznego uzasadnienia.