Claudiu Persoiu

Blog-ul lui Claudiu Persoiu


Iterarea obiectelor folosind PHP si SPL

without comments

Iterator pattern este probabil cel mai popular pattern din SPL. Este un exemplu foarte simplu de a demonstra avantajele unei interfete si SPL.

Motivatie:
Posibilitatea de a itera structuri de tip obiect folosind functii precum foreach(), var_dump(), print_r() etc.

Diagrama:

iterator diagram

Structura Iterator:

In SPL se gasesc mai multe interfete si clase pentru iterator.

Structura de baza pentru interfata Iterator:

/**
 * Interfata Iterator din SPL
 */
Iterator extends Traversable {
      /**
       * Intoarce elementul curent
       */
      abstract public mixed current ( void )

      /**
       * Cheia pentru elementul curent
       */
      abstract public scalar key ( void )

      /**
       * Trece la urmatorul element
       */
      abstract public void next ( void )

      /**
       * Reseteaza iterarea la pozitia initiala
       */
      abstract public void rewind ( void )

      /**
       * Verifica daca pozitia curenta este valida
       */
      abstract public boolean valid ( void )
}

Exemplu 1:

Un obiect iterabil simplu.

/**
 * Clasa care va genera un obiect interabil
 */
class Iterabil implements Iterator {

	/**
	 * Index pentru elementul iterabil
	 */
	private $_current = 0;

	/**
	 * Array cu elementele de iterat
	 */
	private $_elements = array();

	/**
	 * Constructor
	 *
	 * @param array $elements Elementele de iterat
	 */
	public function __construct($elements) {
		$this->_elements = $elements;
	}

	/**
	 * Elementul curent
	 *
	 * @return mixed Elementul curent
	 */
	public function current() {
		return $this->_elements[$this->_current];
	}

	/**
	 * Index curent
	 *
	 * @return integer Index curent
	 */
	public function key() {
		return $this->_current;
	}

	/**
	 * Trecerea la index-ul urmator
	 */
	public function next() {
		$this->_current++;
	}

	/**
	 * Resetare index
	 */
	public function rewind() {
		$this->_current = 0;
	}

	/**
	 * Verifica daca elementul curent este setat
	 *
	 * @return boolean Daca elementu curent este setat
	 */
	public function valid() {
          return isset($this->_elements[$this->_current]);
     }
}

// instantiere clasa
$obj = new Iterabil(array(1, 2, 3, 4, 5));

// iterare obiect
foreach ($obj as $value) {
     echo $value.PHP_EOL;
}

// output:
// 1
// 2
// 3
// 4
// 5

Exemplu 2:

Un exemplu putin mai complex, o clasa care permite iterarea prin proprietatile publice ale clasei care o extinde. Se folosesc Iterator si Reflection.

/**
 * Clasa care va genera un obiect cu proprietatile publice iterabile
 */
class Iterabil implements Iterator {

     /**
      * Index pentru elementul iterabil
      */
     private $_current = 0;

     /**
      * Array cu elementele de iterat
      */
     private $_elements = array();

     /**
      * Elementul curent
      *
      * @return mixed Proprietatea curenta in iteratie
      */
     public function current() {
          return $this->_elements[$this->_current]->name;
     }

     /**
      * Index curent
      *
      * @return integer Index curent
      */
     public function key() {
          return $this->_current;
     }

     /**
      * Trecerea la index-ul urmator
      */
     public function next() {
          $this->_current++;
     }

     /**
      * Resetare index si preluare prorietari
      */
     public function rewind() {
          // rewind se apeleaza prima,
          // deci aici ar trebui preluate prorprietatile obiectului
          // se initializeaza obiectul de tip ReflectionClass
          // cu parametru numele clasei curente
          $reflection = new ReflectionClass(get_class($this));

          // se extrag metodele publice
          $this->_elements = $reflection->getProperties(ReflectionMethod::IS_PUBLIC);

          // se seteaza indexul curent
          $this->_current = 0;
     }

     /**
      * Verifica daca elementul curent este setat
      *
      * @return boolean Daca elementu curent este setat
      */
     public function valid() {
         return isset($this->_elements[$this->_current]);
    }
}

/**
 * O noua clasa care va avea proprietati publice
 *
 */
class Testing extends Iterabil {
     public $proprietate1;
     public $proprietate2;
}

// instantiere clasa
$obj = new Testing();

// iterare obiect
foreach ($obj as $value) {
     echo $value.PHP_EOL;
}

// output:
// proprietate1
// proprietate2

Iar daca vrem ca exemplu de mai sus sa fie si accesibil ca un array, nu trebuie decat sa mai implementam din SPL ArrayAccess.

Structura ArrayAccess:

ArrayAccess   {
    /**
     * Verifica daca un offset exista
     */
    abstract public boolean offsetExists ( string $offset );

    /**
     * Intoarce elementul unui offset sau NULL daca nu exista
     */
    abstract public mixed offsetGet ( string $offset );

    /**
     * Seteaza o valoare pentru un offset
     */
    abstract public void offsetSet ( string $offset , string $value );

    /**
     * Dezaloca o valoare pentru un offset
     */
    abstract public void offsetUnset ( string $offset )
}

Exemplu 3:

Un exemplu destul de dificil care are rolul de a evidentia puterea interfetelor din SPL. Obiect iterabil si accesibil ca un array.

Pentru a simplifica logica de acces am folosit chiar functiile pentru parcurgerea unui array (next(), reset()).

/**
 * Clasa care va genera un obiect interabil
 */
class Iterabil implements Iterator, ArrayAccess, Countable {

     /**
      * Array cu elementele de iterat
      */
     private $_elements = array();

     /**
      * Constructor
      *
      * @param array $elements Elementele de iterat
      */
     public function __construct($elements) {
          $this->_elements = $elements;
     }

     /**
      * Elementul curent
      *
      * @return mixed Elementul curent
      */
     public function current() {
          return current($this->_elements);
     }

     /**
      * Index curent
      *
      * @return integer Index curent
      */
     public function key() {
          return key($this->_elements);
     }

     /**
      * Trecerea la index-ul urmator
      */
     public function next() {
          next($this->_elements);
     }

     /**
      * Resetare index
      */
     public function rewind() {
          reset($this->_elements);
     }

     /**
      * Verifica daca elementul curent este setat
      *
      * @return boolean Daca elementu curent este setat
      */
     public function valid() {
          return current($this->_elements)?true:false;
    }
    /**
     * Verifica daca un offset exista
     *
     * @param string $offset Element cautat
     * @return boolean Daca elementul de la offset exista
     */
    public function offsetExists($offset) {
         return isset($this->_elements[$offset]);
    }

    /**
     * Elementul de la un offset
     *
     * @param string $offset Offset array
     * @return mixed
     */
    public function offsetGet($offset) {
         return $this->_elements[$offset];
    }

    /**
     * Setare element in array
     *
     * @param string $offset Offset element
     * @param mixed $value Valoarea pentru elementul din array
     */
    public function offsetSet($offset, $value) {
         $this->_elements[$offset] = $value;
    }

    /**
     * Dezalocare element in array
     *
     * @param string $offset Offset element
     */
    public function offsetUnset($offset) {
         unset($this->_elements[$offset]);
    }

    /**
     * Numarul de elemente din array-ul curent
     *
     * @return integer Nr elemente array
     */
    public function count() {
         return count($this->_elements);
    }
}

// instantiere clasa
$obj = new Iterabil(array(1, 2, 3, 4, 5));

echo 'Iterare folosind "for":'.PHP_EOL;

// iterare ca printr-un array simplu
for($i = 0; $i < count($obj); $i++) {
     echo $obj[$i].PHP_EOL;
}

echo 'Element de sters: '.$obj[1].PHP_EOL;

unset($obj[1]);

echo 'Iterare folosind "foreach":'.PHP_EOL;

// iterare array folosind foreach
foreach ($obj as $element) {
     echo $element.PHP_EOL;
}

// Output:
//Iterare folosind "for":
//1
//2
//3
//4
//5
//Element de sters: 2
//Iterare folosind "foreach":
//1
//3
//4
//5

Written by Claudiu Persoiu

8 October 2009 at 8:38 AM

Leave a Reply