Filtry strumieni w PHP można porównać do filtrów znanych z Zend Framework. Są to klasy, które automatyzują czynności związane z odczytywaniem i zapisywaniem danych ze strumienia. Do jednego strumienia można dodać wiele filtrów, dzięki czemu programista zyskuje możliwość dowolnej modyfikacji strumienia.

Najprostszym i zarazem najlepszym przykładem zastosowania filtrów, będzie odczyt pliku i zmiana kodowania treści zawartych w pliku. W tym celu posłużę się plikiem CSV wygenerowanym z dokumentu Excel.

Typowy skrypt odczytujący dane z pliku wygląda następująco

$fp = fopen('./Book1.csv', 'r');
$output = '';
while(!feof($fp)) {
    $output .= fread($fp, 1024);
}
fclose($fp);
echo $output;

Jeśli dane w pliku nie spełniają naszych oczekiwań, wykonywane są operacje mające na celu dostosowanie ich do naszych wymagań. Sprowadza się to do wywołania funkcji zmieniającej kodowanie, zmieniającej wielkość liter, czy podmianie części danych. Jeśli zamiast tego zastosujemy filtr, odebrane dane będą od razu w pożądanej przez nas postaci.

Filtr jest klasą, która dziedziny po klasie php_user_filter. Klasa ta zawiera trzy właściwości oraz trzy metody.

Dostępne właściwości to:

  • filtername – nazwa filtru, który jest aktualnie wykorzystywany
  • params – zawiera parametry przekazane do filtra
  • stream – strumień, który aktualnie jest filtrowany

Dostęne metody:

  • onCreate – metoda wywołana przed rozpoczęciem filtrowania danych. Musi zwrócić true w przypadku poprawnego wykonania zawartych w swoim ciele operacji lub false jeśli operacje zakończyły się niepowodzeniem.
  • onClose – metoda wywołana po zakończeniu filtrowania.
  • filter – metoda odpowiedzialna za filtrowanie danych. Jest to jedyna etoda, która jest wymagana w klasie filtra. Przyjmuje cztery parametry:
    • $in – zasób wskazujący na bucket bridge, który zawiera kolejne porcje danych do filtrowania
    • $out – zasób wskazujący na bucket bridge do którego zostaną zapisane przefiltrowane dane
    • $consumed – parametr ten musi być przekazany przez referencję. Do jego wartości dodawana jest ilość aktualnie filtrowanych danych
    • $closing – parametr określający, że filtrowanie strumienia jest w ostatniej iteracji.

Metoda filter musi zwrócić jedną z trzech wartości:

  • PSFS_PASS_ON – filtrowanie zakończone sukcesem, na wyjście zwrócone zostały jakieś dane.
  • PSFS_FEED_ME – filtrowanie zakończone sukcesem, na wyjście nie zwrócono żadnych danych.
  • PSFS_ERR_FATAL – filtrowanie zakończone niepowodzeniem i nie może być kontynuowane.

Wykorzystując powyższe informacje, napiszemy klasę filtrującą, która zmieni kodowanie pliku na UTF.

class StreamFilterConvertEncoding extends php_user_filter
{
    public function filter($in, $out, &$consumed, $closing)
    {
        while($bucket = stream_bucket_make_writeable($in)) {
            $bucket->data = iconv('CP1250', 'UTF-8', $bucket->data);
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}

W powyższym kodzie tylko dwie funkcje mogą być niezrozumiałe. Pierwszą z nich jest stream_bucket_make_writeable. Zadaniem tej funkcji jest odebranie danych z wejścia i przekształcenie ich w bucket, na którym wykonywane są dalsze operacje. Drugą “egzotyczną” funkcją jest stream_bucket_append, która zapisuje dane z bucketa, do zmiennej wyjściowej.

Zmienna $bucket jest obiektem klasy stdClass i zawiera zasób (właściwość bucket), dane (właściwość data) oraz wielkość odczytanych danych (właściwość datalen).

Zastosowanie tak przygotowanego filtra jest banlnie proste i sprowadza się do dwóch kroków. Najpierw należy zarejestrować filtr

stream_filter_register('win_to_utf', 'StreamFilterConvertEncoding');

A następnie użyć go na konkretnym strumieniu

stream_filter_append($fp, 'win_to_utf');

Cały kod wygląda następująco

class StreamFilterConvertEncoding extends php_user_filter
{
    public function filter($in, $out, &$consumed, $closing)
    {
        while($bucket = stream_bucket_make_writeable($in)) {
            $bucket->data = iconv('CP1250', 'UTF-8', $bucket->data);
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}
stream_filter_register('win_to_utf', 'StreamFilterConvertEncoding');


$fp = fopen('./Book1.csv', 'r');
stream_filter_append($fp, 'win_to_utf');
$output = '';
while(!feof($fp)) {
    $output .= fread($fp, 1024);
}
fclose($fp);
echo $output;

Aby nie wymyślać koła na nowo, warto zapoznać się z listą dostępnych filtrów.