Stanąłem dziś przed takim dylematem: chciałem utworzyć listę obiektów (każdy obiekt to konkretny wpis w bazie danych). No OK, samo utworzenie listy to niewielki problem. Problemem jest to, że w całej aplikacji używam dość zaawansowanych “ułatwiaczy”: przeładowywanie __isset(), __unset(), __set() i __get(). Np. mam obiekt $p, instancję klasy Post. Ma on różne własności, np tytuł (Post::$title), treść (Post::$body). Dostęp do nich jest za pomocą przeładowanych metod __set() i __get(), co pozwala mi na zautomatyzowanie kilku rzeczy – jak na przykład niewidoczną dla programisty zamianę znaków nowej linii na HTMLowe znaczniki <br />. Ale ja nie o tym miałem…
Chciałem zachować naturalność rozwiązań także w klasie agregującej obiekty Post. Czyli potraktować de facto obiektu agregatu (który ma kilka zadań, jak np utworzenie tej listy obiektów) jako tablicy. Tworząc instancję klasy, chcę mu zapodać: utwórz listę wpisów z kategorii o ID = 2, obiektów ma być 30. No i z tego korzystać. Mogę oczywiście dać sobie dostęp do jednej z własności tegoż obiektu-agregatu, która będzie tablicą i zawierać w sobie będzie wszystkie pobrane wpisy. Ale czy wspominałem coś o naturalności rozwiązań? :)
PHP 5 dostarcza nam coś, co nazywa się SPL, czyli Standard PHP Library. Jedną z klas (a konkretnie intefejsów) modułu SPL jest ArrayAccess.
Aby tego użyć, należy zadeklarować klasę jako implementującą z tego interfejsu:
class Aggregate implements ArrayAccess {}
A w niej trzeba zaimplementować 4 metody: offsetExists($offset), offsetGet($offset), offsetSet($offset, $value), offsetUnset($offset).
Przykład tego, jak powinna wyglądać cała klasa:
class Aggregate implements ArrayAccess
{
private $data = array();
public function __construct($array=null)
{
if (!is_null($array)) {
$this->data = $array;
}
}
public function offsetExists($offset)
{
return array_key_exists($offset, $this->data);
}
public function offsetGet($offset)
{
return $this->data[$offset];
}
public function offsetSet($offset, $value)
{
$this->data[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->data[$offset]);
}
}
Do tego przydałby się prosty sposób na sprawdzenie liczby obiektów które są na liście (kazaliśmy pobrać 30, ale to jest ilość maksymalna – w bazie danych może być ich mniej). Wykonamy to poprzez interfejs Countable i metodę count(). Nagłówek naszej klasy zmieniamy na taki:
class Aggregate implements ArrayAccess, Countable
I dodajemy metodę count():
public function count()
{
return count($this->data);
}
Żeby poczuć się jakbyśmy pracowali z prawdziwą tablicą, brakuje nam jeszcze możliwości iteracji po poszczególnych elementach tablicy. Do tego trzeba zaimplementować interfejs Iterator. Do listy implementowanych interfejsów dodajemy Iterator:
class Aggregate implements ArrayAccess, Countable, Iterator
I implementujemy jego metody:
public function current()
{
return current($this->data);
}
public function key()
{
return key($this->data);
}
public function next()
{
return next($this->data);
}
public function rewind()
{
return reset($this->data);
}
public function valid()
{
return current($this->data);
}
Od tego momentu możemy każdą instancję klasy Aggregate traktować jak tablicę. Pobieramy dowolny element tak jak z normalnej tablicy: $aggregate[2], tak samo zmieniać jego zawartosć: $aggregate[2] = 'something';, iterować za pomocą foreach() {}, usuwać elementy za pomocą unset() etc. Jedyne czego nie osiągnęłem, ale też specjalnie nie próbowałem, to dodawać elementów za pomocą notacji []. Ale myślę że w większości zastosowań nie jest to potrzebne.
Cała, działająca klasa wraz z przykładem użycia: urzenia.net/wp-content/class_aggregate.php.
UPDATE:
Udoskonalona wersja klasy, odporna na błąd z metody valid (), a także pozwalająca na dodawanie kolejnych elementów tablicy PHPowym idiomem $aggregate[] = 'wartosc' znajduje się pod adresem: urzenia.net/wp-content/class_aggregate_new.php.

Genialne, choć mało przydatne. Muszę więcej doczytać o SPL, iteratory to zdaje się wierzchołek góry lodowej…
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-1053
Czy mało przydatne… kwestia gustu. Ja w Core zastosowanie znalazłem ;) Mam po prostu inteligentny (sam ładuje swoją zawartość etc) agregat na obiekty, do których to obiektów mam przyjemny dostęp korzystając z operatorów tablicy.
Staram się robić jak najbardziej przyjazne dla programiasty interfejsy, tak żeby używanie obiektów było jak najbardziej naturalne. W tym kontekście podejście zaprezentowane powyżej jest jak najbardziej przydatne :)
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-1077
No dobra, od czasu do czasu się przydaje :)
> Jedyne czego nie osiągnęłem, ale też specjalnie nie próbowałem, to dodawać elementów za pomocą notacji [].
?
Z tego, co wiem, to jest to możliwe.
(Może bug PHP?)
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-1088
Do zwykłej tablicy – nie ma problemu. Gorzej do tej tworzonej w sposób z artykułu :)
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-1090
No więc właśnie mi chodzi o tą “tablicę” implementującą ArrayAccess.
Sprawdziłem w książce Sebastiana Bergmanna – na 100% można.
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-1091
OK, sorry że tak na dwa razy, ale już wiem, gdzie masz błąd :)
CHYBA powinno być tak:
public function valid(){
return current($this->data) + 1;
}
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-1095
Kurde, cofam to, co napisałem, ale jednak w tej raczej jest błąd, bo powinna ona sprawdzać, czy jest następny element tablicy.
(mój przykład tworzy pętlę nieskończoną ^^)
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-1096
“Iterator::valid ( ) [abstract]
Check if there is a current element after calls to rewind() or next(). ” – SPL Reference
“valid() – sprawdza, czy w zbiorze znajduje się kolejny element” – Sebastian Bergmann (bądź tłumacz).
Reference ma zawsze rację, wobec czego ja też się myliłem. Sorry za flood, już więcej się nie odzywam.
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-1097
Dobry flood nie jest zły – przynajmniej Ty się czegoś dowiedziałeś, i ktoś kto to później będzie czytał – też :) A mało brakowało żebym i ja… ;)
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-1121
Zawsze jeszcze można zrobić tak:
:)class Aggregate extends ArrayObject { }
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-5765
[...] PS. Kilka przykładów użycia magicznych właściwości znajduje się też w innym moim wpisie: Obiekt jako tablica (wprowadzenie do SPL). [...]
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-32103
Sory, że odkopuję tak stary wpis, ale akurat przypadkiem na niego trafiłem.
Przed chwilą właśnie potrzebowałem zaimplementować interfejs Iteratora w jednej ze swoich klas. I chciałem podzielić się swoimi spostrzeżeniami :)
Jeśli klasa “opakowuje” tablicę (jak w Twoim przykładzie) to można skorzystać z interfejsu IteratorAggregate i dodać metodę:
public function getIterator() {
$tmp = new ArrayObject($this->data);
return $tmp->getIterator();
}
O wiele mniej pisania niż w przypadku samodzielnej implementacji wszystkich 5 metod interfejsu Iterator :)
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-32517
@Eluś:
Różnica jest taka, że własne metody mogą być “leniwe”, mogą pobierać dane z bazy po jednym rekordzie, mogą robić przy okazji wiele innych rzeczy. W przypadku ArrayObject() musisz podać wszystkie elementy, czyli np wybrać z DBMS 4 mln rekordów… :)
Tablica była przykładem na którym najprościej było mi coś pokazać :)
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-32524
Witaj! Trochę głupio mi odkopywać wpis sprzed dwóch lat, ale po przeczytaniu go wiedziałem za czym “googlać” to wypada podziękować. I się dopytać o drobny błąd.
To było już wspomniane w komentarzach, ale czy masz poprawniejszą wersję metody valid() do implementacji Iteratora? Foreach się na mnie wypiął i wcale nie chciał się wykonywać, okazało się że pierwszy element tablicy (pobrany z bazy danych) ma wartość 0, jak to current() zwróci to koniec.
Jutro będę się zastanawiał jak to obejść (na razie w akcie desperacji wstawiłem licznik kroków do next() i rewind(), ale to bzdura straszna). if(!isset(key($this->data)))? Hmm…
I jakie są szanse na podobnie przejrzysty tutorial do RecursiveIterator(Iterator)? :)
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-33346
Żeby valid() działało chociaż w miarę sensownie, to trzeba zrobić podobnie jak to napisałeś, czyli coś a’la ‘licznik kroków’, tylko inaczej zaimplementować: dodaj do klasy właściwość
$_pointer, i pracuj z nią zamiast z wbudowanymi funkcjami PHP. Metodacurrent ()niech zwraca element będący na pozycji$this->data[$this->_pointer],rewind ()zeruje$_pointer, anext ()inkrementuje. Wvalid ()dajesz wtedy sprawdzanie czy istnieje w$dataklucz$_pointer.Co do tutoriala – to przecież jest prawie taki sam jak
Iterator, tylko ma dwie dodatkowe metody, w sumie dość proste do implemetacji jak mnie się zdaje… :)Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-33348
Dodałem małe uaktualnienie na końcu artykułu.
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-33349
W sumie to się wczytałem i opitoliłem problem tak:
public function valid(){
return $this->current() !== false;
}
Mimo wszystko dzięki za szybką odpowiedź :)
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-33350
Nie załatwia Ci to przypadku gdy w tablicy masz wartość false.
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-33351
public function valid () {
return isset($this->aData[key($this->aData)]);
}
Adres bezpośredni: http://urzenia.net/298/obiekt-jako-tablica-wprowadzenie-do-spl/#comment-33386