Claudiu Persoiu

Blog-ul lui Claudiu Persoiu


Archive for 19 May 2013

Finally finally in PHP 5.5

with one comment

Cea mai noua versiune de PHP este aproape gata. In momentul cand scriu acest blog, PHP 5.5 este in RC 1.

Cum am mai spus si in blogul anterior, lista de noutati este disponibila la: http://www.php.net/manual/en/migration55.new-features.php

Al doilea feature ca popularitate este “finally”: http://www.php.net/manual/en/language.exceptions.php

Care-i treaba cu “finally“?

Pare un pic confuz, pare un bloc care se executa dupa ce se termina blocul de try catch. Dar ce este nou?

Sa zicem ca avem un bloc try/catch:

PHP 5.x < 5.5:

// open resouce
$resource = new Resouce();
try {
    // do stuff with the resouce
    $resouce->doStuff();
} catch (Exception $e) {
    // log exception
    syslog(LOG_ERR, $e->getMessage());
}
// release resouce
unset($resouce);

PHP 5.5

// open resouce
$resource = new Resouce();
try {
    // do stuff with the resouce
    $resouce->doStuff();
} catch (Exception $e) {
    // log exception
    syslog(LOG_ERR, $e->getMessage());
} finally {
    // release resouce
    unset($resouce);
}

Pana aici nu exista nici un motiv pentru care este nevoie de un nou bloc. Am prins exceptia, am facut logging pe ea si am continuat.

Dar sa zicem ca este o resursa si vrem sa o eliberam, iar ulterior sa aruncam exceptia. O varianta ar fi sa eliberam resursa in catch.

Dar mai ramane cazul “fericit”, sa zicem ca trebuie sa o eliberam si atunci.

// open resouce
$resource = new Resouce();
try {
    // do stuff with the resouce
    $resouce->doStuff();
} catch (Exception $e) {
    // release resouce
    unset($resource);
    // perpetuate exception
    throw $e;
}

Sa complicam si mai mult, sa zicem ca avem n tipuri de exceptii. Vor rezulta n conditii de catch, plus 1 pentru cazul fericit, iar in toate trebuie sa eliberam resursa. Nu foarte eficient…

O alta varianta este sa stocam exceptia intr-o variabila si, dupa ce am eliberat resursa, sa aruncam si exceptia, daca este cazul.

// variable to store the exception
$exception = false;

// open resouce
$resource = new Resouce();
try {
    // do stuff with the resouce
    $resouce->doStuff();
} catch (Exception $e) {
    $exception = $e;
}

// release resouce
unset($resource);

if($exception) {
    throw $exception;
}

Acesta este unul din modurile in care se realizeaza in prezent. Functioneaza, dar nu evidentiaza faptul ca poate doar vrem sa eliberam resursa si sa ne continuam viata in liniste.

Varianta PHP 5.5

In manualul php.net:

In PHP 5.5 and later, a finally block may also be specified after the catch blocks. Code within the finally block will always be executed after the tryand catch blocks, regardless of whether an exception has been thrown, and before normal execution resumes.

De fiecare data cand blocul se executa, indiferent daca se executa cu succes sau nu, finally se va executa. Deci, pentru exemplul:

try {
     echo 'Do stuff' . PHP_EOL;
     throw new Exception('testing');
} finally {
     echo 'inside finally' . PHP_EOL;
}

Outputul va fi:

Do stuff
inside finally

Fatal error: Uncaught exception 'Exception' with message 'testing' in...

Daca vrem sa prindem si exceptia:

try {
     echo 'Do stuff' . PHP_EOL;
     throw new Exception('testing');
} catch (Exception $e) {
     echo 'do something with the exception' . PHP_EOL;
} finally {
     echo 'inside finally' . PHP_EOL;
}

Outputul va fi:

Do stuff
do something with the exception
inside finally

Si chiar daca luam cazul si mai particular, cand prindem exceptia, apoi o aruncam:

try {
     echo 'Do stuff' . PHP_EOL;
     throw new Exception('testing');
} catch (Exception $e) {
     echo 'do something with the exception' . PHP_EOL;
     throw $e;
} finally {
     echo 'inside finally' . PHP_EOL;
}

Pare ca acum am reusit sa prevenim executia blocului finally? Nu este tocmai asa…

Do stuff
do something with the exception
inside finally

Fatal error: Uncaught exception 'Exception' with message 'testing' in...

Cu alte cuvinte, blocul finally se executa de fiecare data, indiferent de rezultat.

Written by Claudiu Persoiu

19 May 2013 at 7:34 PM

Posted in PHP

Tagged with , ,

Generating generators in PHP 5.5

without comments

O noua versiune de PHP este pe cale sa se lanseze. In momentul cand scriu acest blog, PHP 5.5 este in beta4.

Dornic de a vedea noutatile, am compilat noua versiune beta. Lista de noutati este disponibila la: http://www.php.net/manual/en/migration55.new-features.php

Cel mai important feature il reprezinta generatoarele (generators).

Generare de generators in PHP 5.5

Un generator este practic o functie care contine un apel catre “yield”.

Sa luam exemplul de pe php.net:

<?php
function xrange($start, $limit, $step = 1) {
    for ($i = $start; $i <= $limit; $i += $step) {
        yield $i;
    }
}

echo 'Single digit odd numbers: ';

/* Note that an array is never created or returned,
 * which saves memory. */ 
foreach (xrange(1, 9, 2) as $number) {
    echo "$number ";
}
?>

Practic, generatorul (xrange in acest caz), in loc sa intoarca un array, o sa genereze cate o valoare pentru a fi prelucrata.

Dar stai… oare asta nu era deja posibil pana la aceasta versiune?

Generatori inainte de PHP 5.5

Pana la versiunea de PHP 5.5 aveam deja iteratori:

<?php

class xrange implements Iterator
{
    private $position = 0;
    private $start;
    private $limit;
    private $step;

    public function __construct($start, $limit, $step = 1)
    {
        $this->start = $start;
        $this->limit = $limit;
        $this->step = $step;
        $this->position = 0;
    }

    function rewind()
    {
        $this->position = 0;
    }

    function current()
    {
        return $this->start + ($this->position * $this->step);
    }

    function key()
    {
        return $this->position;
    }

    function next()
    {
        ++$this->position;
    }

    function valid()
    {
        return $this->current() <= $this->limit;
    }
}

echo 'Single digit odd numbers: ';

/* Note that an array is never created or returned,
 * which saves memory. */
foreach (new xrange(2, 9, 2) as $number) {
    echo "$number ";
}
?>

In afara de faptul ca Iteratorul este un obiect cu mai multe proprietati, practic putem atinge acelasi rezultat.

Dar de ce era nevoie de generatori atunci? Simplu! In loc sa folosim ~40 linii de cod, putem folosi pur si simplu 5 ca sa atingem acelasi scop.

Un alt lucru interesant este ca:

get_class(printer());

va intoarce Generator.

Deci, practic, un generator va intoarce inapoi un obiect de tip Generator, iar acest obiect extinde Iterator.

Diferenta majora, asa cum este si pe site-ul php.net, este ca generatorul nu poate fi resetat, merge intr-o singura directie.

Trimiterea de informatii catre generator

Da, generatorii functioneaza in doua sensuri, doar ca un anume generator este bun doar pentru un singur sens. Daca sintaxa de mai sus este pentru “producerea” de rezultate, sintaxa de mai jos este pentru “consumare” de date.

Sintaxa pentru un generator “consumator” este simpla:

<?php
function printer() {
    $counter = 0;
    while(true) {
        $counter++;
        $value = yield;
        echo $value . $counter . PHP_EOL;
    }
    echo ‘Never executed...' . PHP_EOL;
}

$printer = printer();
$printer->send('Hello!');
echo 'Something is happening over here...' . PHP_EOL;
$printer->send('Hello!');
?>

Outputul va fi:

Hello!1
Something is happening over here...
Hello!2

Practic, valoarea din yield poate fi folosita ca orice alta valoare. Ce este interesant este while-ul. Pe php.net este urmatorul comentariu:

// Sends the given value to the
// generator as the result of
// the yield expression and
// resumes execution of the
// generator.

Este nevoie de un loop, pentru ca generatorul se va opri dupa ce proceseaza valoarea si va continua doar atunci cand primeste o noua valoare. Daca scoatem while-ul, doar prima valoare va fi procesata, indiferent de cate ori vom apela send().

Un lucru interesant este ca ceea ce este dupa loop nu se va executa, in cazul meu linia:

echo ‘Never executed...' . PHP_EOL;

Desi pare un loc potrivit sa eliberezi o resursa (ex. BD sau fisier), de fapt nu este, pentru ca acel cod nu se va executa.

Mi se pare util la logging. Din nou, nimic ce nu putea fi facut si pana acum, dar totusi permite o abordare mult mai usoara.

Am descoperit totusi un lucru care nu functioneaza:

<?php
function printer() {
    while(true) {
        echo yield . PHP_EOL;
    }
}

$printer = printer();
$printer->send('Hello world!');

foreach($printer as $line) {
    echo $line . PHP_EOF;
}

Un pic haotic, nu? Eram curios ce se intampla:
Fatal error: Uncaught exception ‘Exception’ with message ‘Cannot rewind a generator that was already run’ in…

Odata ce a fost folosit send() pe un iterator, nu mai poti sa iterezi prin el. Evident se poate genera unul nou, cu:

printer();

Ce este si mai confuz este ca Generator este o clasa final, deci nu poate fi extinsa, iar daca incerci sa o instantiezi direct (desi chiar daca ar functiona ar fi inutil):
Catchable fatal error: The “Generator” class is reserved for internal use and cannot be manually instantiated in…

Concluzia

Este un feature interesant, pentru ca simplifica mult lucrurile atunci cand vrei sa construiesti un iterator.

De asemenea, functionalitatea de send() mi se pare foarte interesanta, nu pentru ca face ceva nou, ci pentru ca il face mai usor.

Nu-mi place in schimb ca este aceeasi sintaxa pentru ambele variante de generatori si mai mult, ce este dupa while nu se mai executa.

Mi se pare usor confuza sintaxa, pentru ca nu este o diferentiere clara. Pe de alta parte, se pare ca asta exista deja in Python, deci pentru inspiratie se pot folosi exemplele din acest limbaj.

Written by Claudiu Persoiu

10 May 2013 at 9:11 AM

Posted in PHP

Tagged with , , ,