Libraria Standard PHP - Lucrul cu interfata Iterator
15.09.2009
Interfata Iterator este implementata in core-ul engine-ului Zend 2.0 si faciliteaza parcurgerea unor structuri de date complexe(cu proprietatea de a se parcurge intern). La prima vedere pare ceva foarte complicat, insa va puteti gandi ca orice poate fi parcurs in PHP folosind foreach, for, while, poate fi manipulat prin aceasta interfata: de la un simplu vector pana la row-urile rezultate dintr-un query SQL, continutul unui director sau liniile dintr-un fisier text.
9909 afisari 4 Rating (3 voturi) 15 min

Ma aflam chiar la inceputul carierei mele de programator web foarte mandru ca fetele ramaneau placut impresionate cand le vorbeam de arbori, backtracking si altele - si nu de putine ori am fost pus in situatia de a implementa un sistem de administrare a mai multor tipuri de utilizatori, spre exemplu studenti/tutori. Pe-atunci insa PHP4 nu dispunea nici macar de programare orientate pe obiecte in adevaratul sens al cuvantului, daramite interfete evoluate, gen Iterator. A trebuit sa vin insa cu o solutie, foarte "low"- asa cum de fapt imi permitea PHP4 ... Am folosit switch-case!

Cateva cuvinte despre libraria standard din PHP 5

Odata cu dezvoltarea programarii orientate pe obiecte in PHP5 si-a facut aparitia si o librarie standard(SPL – Standard PHP Library ) care cuprinde o serie de clase si interfete dezvoltate pentru a oferi solutii eficiente unor probleme de accesare si manipulare a datelor.

Iata mai jos o lista de interfete/clase deja existente(by default) in extensia SPL:

  1. Iteratori
    1. interface RecursiveIterator extends Iterator
    2. interface OuterIterator extends Iterator
    3. class RecursiveIteratorIterator implements OuterIterator
    4. abstract class FilterIterator implements OuterIterator
    5. class ParentIterator extends FilterIterator implements RecursiveIterator
    6. interface SeekableIterator extends Iterator
    7. class LimitIterator implements OuterIterator
    8. class CachingIterator implements OuterIterator
    9. class RecursiveCachingIterator extends CachingIterator implements RecursiveIterator
    10. class IteratorIterator implements OuterIterator
    11. class NoRewindIterator implements OuterIterator
    12. class EmptyIterator implements Iterator
    13. class InfiniteIterator extends IteratorIterator
    14. class AppendIterator implements OuterIterator
    15. class RegexIterator extends FilterIterator
    16. class RecursiveRegexIterator extends RegexIterator implements RecursiveIterator
  1. Directoare si fisiere
    1. class SplFileInfo
    2. class DirectoryIterator extends SplFileInfo implements Iterator
    3. class RecursiveDirectoryIterator extends DirectoryIterator implements RecursiveIterator
    4. class SplFileObject extends SplFileInfo implements RecursiveIterator, SeekableIterator
  1. XML
    1. class SimpleXMLIterator extends simplexml_element implements RecursiveIterator
  1. Array-uri
    1. class ArrayObject implements IteratorAggregate, ArrayAccess, Countable
    2. class ArrayIterator implements Iterator, ArrayAccess, Countable, SeekableIterator
    3. class RecursiveArrayIterator extends ArrayIterator implements RecursiveIterator
  1. Numarator
    1. interface Countable
  1. Exceptii
    1. class LogicException extends Exception
    2. class BadFunctionCallException extends LogicException
    3. class BadMethodCallException extends BadFunctionCallException
    4. class DomainException extends LogicException
    5. class InvalidArgumentException extends LogicException
    6. class LengthException extends LogicException
    7. class OutOfRangeException extends LogicException
    8. class RuntimeException extends Exception
    9. class OutOfBoundsException extends RuntimeException
    10. class OverflowException extends RuntimeException
    11. class RangeException extends RuntimeException
    12. class UnderflowException extends RuntimeException
  1. Observatori
    1. interface SplObserver
    2. interface SplSubject
    3. class SplObjectStorage
  1. Clasele de tip “Example” – implementate in subdirectorul ext/SPL si care cu timpul vor fi trecute in limbajul C.

Interfata Iterator

<?php 
 
Iterator extends Traversable {
  // Metodele interfetei Iterator
  abstract public mixed current()
  abstract public scalar key()
  abstract public void next()
  abstract public void rewind()
  abstract public boolean valid()
}
 
?>

Aceasta interfata este implementata in core-ul engine-ului Zend 2.0 si faciliteaza parcurgerea unor structuri de date complexe(cu proprietatea de a se parcurge intern). La prima vedere pare ceva foarte complicat, insa va puteti gandi ca orice poate fi parcurs in PHP folosind foreach, for, while, poate fi manipulat prin aceasta interfata: de la un simplu vector pana la row-urile rezultate dintr-un query SQL, continutul unui director sau liniile dintr-un fisier text.

Studiind interogarile din tabele MySQL, putem observa modul de parcurgere al rezultatului unui query:

<?php 
 
  // Structura complexa 
  $result = mysql_query("SELECT * FROM Utilizatori"); 
  
  // Parcurgerea simpla
  while ( $row = mysql_fetch_array($result) ) { 
      echo $row["Email"];  
  }
?>

Similar putem parcurge continutul unui director:

<?php 
  
  // Structura complexa 
  $dir = opendir('/director'); 
 
  // Parcurgere simpla
  while ( $file = readdir($dir) ) {
      // ...
  } 
?>

Iar pentru un fisier text:

<?php
  // Structura complexa
  $file = fopen("/director/fisier.txt", "r"); 
  
  // Parcurgere simpla
  while (!feof($file)) { 
      $line = fgets($file);
      // ...
  }
?>

Puteti observa deja ca in linii mari este vorba de 2 operatii care se repeta, indiferent  de tipul resursei manipulate:

  1. Initializarea
  2. Parcurgerea

Ne punem astfel urmatoarea intrebare: exista oare posibilitatea de a abstractiza aceste 2 operatii astfel incat indiferent de tipul de structura de data complexa, secventa de initializare-parcurgere sa fie absolut identica?

Haideti sa vedem mai intai ce metode avem la dispozitie in interfata Iterator:

  1. rewind()

Similar functiei reset() pentru vectori, aceasta metoda “deruleaza” iteratorul la primul element al structurii cu care se lucreaza.

  1. current()

Similar functiei current() pentru vectori, aceasta metoda intoarce elemental curent al structurii manipulate.

  1. key()

Similar functiei key() pentru vectori, aceasta metoda intoarce index-ul elementului curent al structurii manipulate.

  1. next()

Similar functiei next() pentru vectori, aceasta metoda  “trece” la urmatorul element al structurii manipulate.

  1. valid()

Aceasta functie este utilizata dupa apelarea rewind() sau next() pentru a testa daca elementul curent al structurii exista. Cu alte cuvinte, metoda este folosita pentru a testa daca s-a ajuns la finalul structurii de date.

Un mic exemplu – RSSIterator

Chiar daca nu este foarte practic, acest exemplu va va ajuta sa intelegeti mai bine cum lucreaza defapt Iterator. Este limpede urmatorul exemplu(nu face rabat de la regula initializare-parcurgere):

<?php 
   
   //Incarcarea continutului RSS-ului
   $file = file_get_contents("http://www.e-learn.ro/rss.php?section=4");
   
   //Initializarea obiectului de tip SimpleXMLElement
   $xml = new SimpleXMLElement($file);
 
   //Parcurgerea obiectului initializat mai sus
   foreach($xml->channel->item as $feed){
      echo $feed->title.'<br>';
   }
 
?>

Trebuie retinut faptul ca avand de-aface cu niste metode abstracte, suntem obligati ca la implementarea clasei RSSIterator sa definim fiecare metoda in parte.

<?php
 
class RSSIterator implements Iterator {
    
  // Adresa de unde se incarca RSS-ul
  private $RSSUrl; 
    
  //Continutul RSS-ului
  private $RSSContent;
    
  //Index-ul elementului curent
  private $currentIndex = 0;
    
  //Elementul curent
  private $currentItem;
    
  //Numar total de elemente ce se gasesc in RSS
  private $noItems;
    
  public function __construct($RSSUrl) { 
        
       if($RSSUrl != null) {
            
         //Initializarea proprietatii $this->RSSUrl cu adresa RSS-ului
         $this->RSSUrl = $RSSUrl;            
            
         //Incarcarea RSS-ului folosind SimpleXML
         $this->RSSContent = simplexml_load_file($this->RSSUrl, 
         'SimpleXMLElement', LIBXML_ERR_NONE);
            
         //Initializarea numarului total de elemente din RSS
         $this->noItems = count($this->RSSContent->channel->item);
            
         //Initializarea elemntului curent
         $this->currentItem = $this->RSSContent->channel->item[0];
         
       }
        
  }
    
  public function rewind() {
        
       //Setarea index-ului curent la 0
       $this->currentIndex = 0;
        
       //Setarea elementului curent
       $this->currentItem = $this->RSSContent->channel->item[0];
  }
    
  public function current() {        
        
       //Se returneaza elementul curent
       return $this->currentItem;
        
  }
    
  public function key() {
        
      // Se returneaza index-ul curent
      return $this->currentIndex;
 
  }
    
  public function next() {
        
      //Incrementarea index-ului curent
      $this->currentIndex++;
        
      //Testare daca nu s-a ajuns la finalul RSS-ului
      if($this->currentIndex < $this->noItems) {
         /*
           Folosirea unei variabile auxiliare pentru a lucra mai usor 
           cu elementul curent din RSS    
         */
         $item = $this->RSSContent->channel->item;    
          
         //Initializarea elementului curent cu urmatorul element din RSS
         $this->currentItem = $item[$this->currentIndex];    
            
      }
        
  }
    
  public function valid() {
        
      /*
        Cat timp currentIndex este mai mic decat numarul total 
        de elemente din RSS nu s-a ajuns la finalul RSS-ului
      */
      if($this->currentIndex < $this->noItems)            
         return true;
      else 
         return false;
  }
  
}
 
?>

Acum, apelarea poate fi facuta in cele 2 moduri:

<?php
 
  //Instantierea clasei RSSIterator
  $RSSIterator = new  RSSIterator('http://www.e-learn.ro/rss.php?section=1');
  
  //Parcurgerea RSSIterator
  foreach ($RSSIterator as $Index  =&gt; $Value) {
     echo  '<br/>'.$Index.' '.$Value->title;<br>
  }
?>

sau

<?php
 
  //Resetarea RSSIterator
  $RSSIterator->rewind();
  
  //Parcurgerea RSSIterator
  while ($RSSIterator->valid()) {
  
     echo  '<br/>'.$RSSIterator->key().' '.$RSSIterator->current()->title;<br>
     $RSSIterator->next();<br>
     
  }
?>

Ca si observatie, este bine de stiut faptul ca apelarea folosind metodele obiectului RSSIterator este mai rapida decat utilizarea clasicului foreach, deoarece acesta din urma introduce un nou nivel de redirectare la executarea secventei propriu-zise de cod.

In speranta ca ati gasit util acest tutorial, va propun sa va implementati propria versiune de Iterator si sa postati codul in galeria de materiale(asa cum am facut-o si eu). In felul aceasta, avand mai multe exemple de implementare/utilizare, invatarea va deveni mai usoara.

Spor la lucru!

Copyright © 2008-2010 E-LEARN.ro. Toate drepturile rezervate. Conceput si realizat de Neokinetics Software.