Nowości, zmiany i nowe funkcje w PHP 5.5 – cz.2: Support for Generators

Najnowsza wersja języka PHP (czyli 5.5) wprowadza kilka bardzo ciekawych, nowych funkcjonalności. Są nimi:

W drugiej części niniejszego wpisu, omówimy kolejną, nową funkcjonalność, czyli Generatory.

Najpierw fragment kodu, który prezentuje proste rozwiązanie zaczytania z pliku wszystkich linii tekstu, aby móc później po nich iterować.

//Funkcja czytająca wszystkie linie tekstu z pliku
function getLinesFromFile( $sPath ) {
    //Otwieramy plik do odczytu
    $hFile = fopen( $sPath, 'r' );
 
    //Odczytujemy wszystkie linie do wielkiej tablicy
    $aLines = [];
    while( false !== $sLine = fgets($hFile) ) {
        $aLines[] = $sLine;
    }
 
    //Zamykamy plik i zwracamy tablicę
    fclose( $hFile );
    return $aLines;
}
 
//Operacje na odczytanych liniach tekstu
$aLines = getLinesFromFile( $sPath );
foreach( $aLines as $sLine ) {
    //...
}

Powyższe rozwiązanie ma jedną bardzo sporą wadę – najpierw odczytujemy cały plik do tablicy, która może być tak duża, że zapełni całą dostępną pamięć.

Lepszym rozwiązaniem byłoby odczytywanie jednej linii na raz. Można to zrealizować za pomocą funkcji fseek() oraz implementacji interfejsu Iterator. Nie jest to skomplikowane, ale dzięki tzw. Generatorowi staje się to wręcz banalnie proste.

//Generator do odczytu kolejnych linii tekstu z pliku
function getLinesFromFileGenerator( $sPath ) {
    //Otwieramy plik do odczytu
    $hFile = fopen( $sPath, 'r' );
 
    //Zwracamy kolejne linie tekstu
    while( false !== $sLine = fgets($hFile) ) {
        yield $sLine;
    }
 
    //Zamykamy plik
    fclose( $hFile );
}
 
//Operacje na odczytanych liniach tekstu
$aLines = getLinesFromFileGenerator( $sPath );
foreach( $aLines as $sLine ) {
    //...
}

Łatwo nawet nie zauważyć, co się zmieniło. Zmianie uległ kod w samym środku naszej funkcji – pojawiło się nowe słowo kluczowe: yield. O co w tym chodzi?

W momencie, gdy wywołujemy funkcje generującą getLinesFromFileGenerator, to funkcja się nie wywołuje, a zamiast tego zwracany jest obiekt Generatora.

Gdy następnie wrzucimy go do pętli foreach, to wtedy następuje wywołanie kodu, który tworzy funkcję Generującą. Kod ten nie wywołuje się jednak w całości – kod wywołuje się do miesca, w którym stoi słowo kluczowe yield. Gdy wywołanie dojdzie do tego miejsca, to funkcja Generująca zwraca wartość, jaką przekazujemy do tego operatora – w naszym wypadku pierwszą linię tekstu.

Kolejny obieg pętli foreach sprawia, że sterowanie wraca do funkcji Generującej, od miesca, w którym się skończyło ostatniu – czyli operatora yield. Dzięki temu zwracana jest kolejna linia tekstu.

I tak w kółko, aż już nie będzie więcej linii do odczytania i funkcja Generująca skończy wywoływać wewnętrzną pętle while, zamknie plik i się skończy. Gdy funkcja Generująca się kończy, zwraca FALSE, co sprawia że kończy się zewnętrzna pętla foreach.

Reasumując, w Generatorach chodzi o sprytne przekazywanie sterowania do funkcji Generującej i spowrotem dzięki nowemu operatorowi yield. Powyższy przykład z plikiem i pętlami jest jedynie przykładowym wykorzystaniem. Kod wewnątrz funkcji Generującej może być dowolny.

Część 3.:blok finally po blokach try/catch