Claudiu Persoiu

Blog-ul lui Claudiu Persoiu


Archive for the ‘SPL’ tag

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

PHP observer pattern si SPL

without comments

Observer pattern se refera la un obiect “subiect” care are asociata o lista de obiecte dependente, numite observatori, pe care le apeleaza automat de fiecare data cand se intampla o actiune.

Un mic exemplu de ce se foloseste:

– sa zicem ca avem o clasa pe care se fac niste modificari:

class Actiune {
    private $val;
    function __construrct() {
        // ceva cod in constructor
    }

    function change($val) {
        $this->val = $val;
    }
}

De fiecare data cand se face modifica $val vrem sa se apeleze o metoda a unui obiect “observator”:

class Actiune {
    private $val;
    function __construrct() {
        // ceva cod in constructor
    }

    function change($val, $observator) {
        $this->val = $val;
        $observator->update($this);
    }
}

Teoretic nu suna rau, dar cu cat sunt mai multe metode cu atat exista o dependenta mai mare si de fiecare data cand se adauga un obiect nou de tip observator trebuie modificata clasa, avand toate sansele sa rezulte intr-un haos aproape imposibil de portat.

Acum observator pattern arata cam asa:

diagrama

SPL (Standard PHP Library), care este bine cunoscut pentru iteratorii definiti, vine cu interfetele SplSubject si SplObserver, pentru subiect respectiv observator.

O implementare arata cam asta:

/**
 * clasa care trebuie urmarita
 */
class Actiune implements SplSubject {
    private $observatori = array();
    private $val;

    /**
     * metoda atasare obiect observator
     *
     * @param SplObserver $observator
     */
    function attach(SplObserver $observator) {
        $this->observatori[] = $observator;
    }

    /**
     * metoda deatasare obiect observator
     *
     * @param SplObserver $observator
     */
    function detach(SplObserver $observator) {
        $observatori = array();
        foreach($this->observatori as $observatorul) {
            if($observatorul != $observator) $observatori[] = $observatorul;
        }
        $this->observatori = $observatori;
    }

    /**
     * metoda care notifica obiectele de tip observator
     */
    function notify() {
        foreach($this->observatori as $observator) {
            $observator->update($this);
        }
    }

    /**
     * metoda care face modificarea in clasa
     *
     * @param int $val
     */
    function update($val) {
        echo 'facem update...
';
        $this->val = $val;
        $this->notify();
    }

    /**
     * metoda publica care intoarce statusul obiectului
     *
     * @return int
     */
    function getStatus() {
        return $this->val;
    }
}

/**
 * o clasa observator
 */
class Observator implements SplObserver {
    function update(SplSubject $subiect) {
        echo $subiect->getStatus();
    }
}

// instanta observator
$observator = new Observator();

// instanta subiect
$subiect = new Actiune();

// atasare observator la subiect
$subiect->attach($observator);

// update subiect
$subiect->update(5);

Ce mi se pare mie ciudat este ca nu exista o documentatie pentru aceste interfetele din SPL. Chiar pe site-ul zend exista un articol PHP Patterns: The Observer Pattern care nu foloseste SPL, iar asta in masura in care exista documentatie pentru namespaces chiar inainte sa apara PHP 5.3.

Written by Claudiu Persoiu

4 August 2009 at 6:52 PM