Claudiu Persoiu

Blog-ul lui Claudiu Persoiu


Archive for the ‘Design patterns’ Category

Factory method pattern folosind PHP 5.3 si late static bindings

without comments

Factory method design pattern introdus de GoF (Gang of Four) pe care l-am abordat si eu intr-un blog anterior, are la baza ideea unei metode care genereaza obiecte. In general implementarea este destul de “slaba” pentru ca “factory”-ul este destul de hardcodat (la fel ca mine in blogul anterior).

PHP 5.3 ofera totusi posibilitatea unei implementari foarte interesante folosind “Late static bindings“.

Clasele pe baza carora se vor genera obiectele:

// clasa abstracta de baza care urmeaza sa fie mostenita
abstract class Drink {

    // ingrediente
    protected $ingredients;

    // metoda publica care genereaza bautura
    abstract public function MakeDrink();
}

// o clasa derivata pentru ceai
class Tea_Drink extends Drink {

    // ingrediente pentru ceai
    protected $ingredients = array('tea', 'sugar', 'mink', 'water');

    // generare ceai
    public function MakeDrink() {

        // fa ceai
    }
}

// clasa derivata pentru Bloody Mary
class BloodyMary_Drink extends Drink {

    // ingrediente Bloody Mary
    protected $ingredients = array('votka', 'salt', 'tomato juice');

    // generare Bloody Mary
    public function MakeDrink() {

        // fa BloodyMary

    }
}

Idea este de a avea o clasa abstracta care va fi extinsa intr-un mod cat mai simplu pentru a genera noi clase factory.

PHP 5

In PHP 5 clasa ar arata cam asa:

// clasa Factory abstracta
abstract class absFactory {

    // numele clasei de baza
    static protected $base_class = '';

    // metoda factory
    public static function getInstance($type) {

        // numele clasei rezultat
        $class_name = $type . '_' . self::$base_class;

        // se testeaza daca clasa exista
        // aici se poate adauga si un autoloader eventual
        if(!class_exists($class_name)) {
            throw new Exception( 'Class ' . $class_name . ' not loaded!');
        }

        // se verifica daca clasa mosteneste clasa de baza
        if(!is_subclass_of($class_name, self::$base_class)) {
            throw new Exception(
                'Class ' . $class_name . ' is not a child of ' . self::$base_class
            );
        }

        // noul obiect
        return new $class_name;

    }

}

Pentru ca metoda getInstance este statica si proprietatea este statica.

Daca incercam:

class DrinkFactory extends absFactory {

    static protected $base_class = 'Drink';
}

try {

    $obj = DrinkFactory::getInstance('Tea');

} catch (Exception $e) {

    echo $e->getMessage();
}

Output-ul va fi:

Class Tea_ not loaded!

Din pricina “self”, nu putem apela pur si simplu metoda getInstance() din clasa copil pentru ca valoarea lui $base_class va fi “” in loc de “Drink”, trebuie suprascrisa metoda getInstance(). Lucru prea “complicat”.

O versiune functionala in PHP 5 ar fi:

class DrinkFactory extends absFactory {

    public static function getInstance($type) {

        self::$base_class = 'Drink';

        // metoda factory a clasei factory de baza
        parent::getInstance($type);

    }

}

try {

    $obj = DrinkFactory::getInstance('Tea');

} catch (Exception $e) {

    echo $e->getMessage();
}

Dar nu mi se pare tocmai “elegant”.

PHP 5.3

Aici avem “Late static bindings”, care in principiu este introducerea cuvantului “static”.

Clasa de baza factory arata cam asa:

// clasa Factory abstracta
abstract class absFactory {

    // numele clasei de baza
    static protected $base_class = '';

    // metoda factory
    public static function getInstance($type) {

        // numele clasei rezultat
        $class_name = $type . '_' . static::$base_class;

        // se testeaza daca clasa exista
        // aici se poate adauga si un autoloader eventual
        if(!class_exists($class_name)) {
            throw new Exception( 'Class ' . $class_name . ' not loaded!');
        }

        // se verifica daca clasa mosteneste clasa de baza
        if(!is_subclass_of($class_name, static::$base_class)) {
            throw new Exception(
                'Class ' . $class_name . ' is not a child of ' . static::$base_class
            );
        }

        // noul obiect
        return new $class_name;

    }

}

Diferentele fata de PHP 5 sunt marcate cu bold. O schimbare atat de mica permite totusi o clasa factory mult mai “atragatoare”:

class DrinkFactory extends absFactory {

     static protected $base_class = 'Drink';

}

try {

    $obj = DrinkFactory::getInstance('Tea');

} catch (Exception $e) {

    echo $e->getMessage();
}

Practic in aceasta varianta nu se inlocuieste decat proprietatea statica relevanta in acest context.

Written by Claudiu Persoiu

24 January 2010 at 5:46 PM

Posted in Design patterns,PHP

Tagged with , , ,

Factory method design patten in PHP

without comments

Un design patten creational care reprezinta o solutie pentru a genera obiecte fara sa se specifice clasa acestuia. In mod particular orice metoda care genereaza obiecte reprezinta o implementare de Factory Method pattern.

Motivatie:

O motivatie este ca, sarcina de a creea obiectul este delegata catre metoda factory.

Diagrama:

factory method pattern

Implementare:
Clasele care genereaza obiecte prin factory, clasa comuna care va fi extinsa si clasa cu metoda fatory:

// clasa comuna care va fi extinsa
abstract class OutputBase {
     // output headerele necesare
     abstract public function header();

     // corpul rezultat
     abstract public function body($data);

}

// clasa pentru output xml
class XMLOutput extends OutputBase {

     // output header xml
     public function header() {
          header('content-type: text/xml');
     }

     // document xml
     public function body($data) {
          $res = '';

          $res .= '';

          foreach ($data as $root => $item) {

               $res .= '';

               foreach ($item as $key => $val) {
                    $res .= '<'.$key.'>'.$val.'';
               }

               $res .= '';
          }

          $res .= '';

          return $res;

     }

}

// clasa pentru output csv
class CSVOutput extends OutputBase {

     // output header csv
     public function header() {
          header("Content-type: text/plain");
     }

     // corp csv
     public function body($data) {
          $res = '';

          $keys = array_keys($data[0]);

          foreach ($keys as $key) {
               $res .= '"'.$key.'";';
          }
          $res .= "\r\n";

          foreach ($data as $item) {
               foreach ($item as $val) {
                    $res .= '"'.$val.'";';
               }

               $res .= "\r\n";
          }

          return $res;
     }
}

// clasa cu metoda factory
// este abstracta pentru a nu fi instantiata
abstract class OutputFactory {

     // constanta pentru timpul xml
     const XML = 1;

     // constanta pentru tipul csv
     const CSV = 2;

     // metoda statica factory
     public static function getInstance($type) {
          // in functie de constanta primita ca parametru
          // va intoarce unul din obiecte
          switch ($type) {
               case self::XML :
                    return new XMLOutput();
               break;

               case self::CSV :
                    return new CSVOutput();
               break;
          }

          // daca valoarea primita ca parametru nu este una din constante
          // se arunca o exceptie
          throw new Exception('Invalid class type!');
     }

}

Exemplu:

// se de date
$data = array(
               array(
                    'a' => 1,
                    'b' => 2,
                    'c' => 3
                    ),
               array(
                    'a' => 4,
                    'b' => 5,
                    'c' => 6
                    )
          );

//Bloc try-catch pentru a prinde eventualele exceptii
try {
     // generare obiect
     $obj = OutputFactory::getInstance(OutputFactory::XML);

     // output headere
     $obj->header();

     // afisare body
     echo $obj->body($data);

} catch (Exception $e) {

     $e->getMessage();

}

Written by Claudiu Persoiu

23 January 2010 at 6:21 PM

Posted in Design patterns

Tagged with ,

Singleton pattern in PHP

without comments

Probabil cel mai cunoscut pattern de PHP si nu numai.

Motivatie:

Restrictionarea la o singura instanta a unei resurse in toata aplicatia, accesibila de oriunde.

Diagrama:

singleton

Implementare:

Exemplul clasic pentru PHP este resursa de conectare la baze de date. Conexiunea la baza de date trebuie sa fie unica in toata aplicatia pentru a nu stabili conexiuni multiple la baza de date si in consecinta sa fie consumate resurse inutil.

Pentru ca sa existe o singura instanta constructorul nu trebuie sa fie accesibil decat printr-o metoda statica, iar obiectul rezultatul nu trebuie sa aiba posibilitatea de clonare. De asemenea clasa nu trebuie sa aiba posibilitatea de a fi extinsa.

Exemplu:

// date conectare db
define ( 'DB_HOST', 'localhost' );
define ( 'DB_USER', 'user' );
define ( 'DB_PASS', 'pass' );
define ( 'DB_DATABASE', 'db' );

/**
 * Clasa singleton pentru managementul conexiunii la baza de date folosind mysqli
 */
final class SingletonDB {

     /**
      * Instanta singleton
      *
      * @var SingletonDB
      */
     protected static $_instance = null;

     /**
      * Connection resource
      *
      * @var resource
      */
     protected $_connection = null;

     /**
      * Constructor care realizeaza si conexiunea la baza de date
      *
      */
     protected function __construct() {
          // conectare server baze de date
          $this->_connection = new mysqli ( DB_HOST, DB_USER, DB_PASS );

          // in cazul in care conexiunea este nereusita se trimite o exceptie
          if ($this->_connection->connect_error) {
               throw new Exception ( 'Error connection to mysql server' );
          }

          // selectare baza de date
          $this->_connection->select_db ( DB_DATABASE );

          // in cazul in care selectarea este nereusita se trimite o exceptie
          if ($this->_connection->error) {
               throw new Exception ( 'Error selecting database' );
          }

     }

     /**
      * Rulare interogare SQL
      *
      * @param string $query Interogare SQL
      * @return mysqli_result
      */
     public function query($query) {
          return $this->_connection->query ( $query );
     }

     /**
      * Acces mesaj eroare
      *
      * @return string
      */
     public function error() {
          return $this->_connection->error;
     }

     /**
      * Metoda care se apeleaza la clonare
      * Este protected pentru ca obiectul nu trebuie clonat
      *
      */
     protected function __clone() {
     }

     /**
      * Intrerupe conexiunea
      *
      */
     public function __destruct() {
          $this->_connection->close ();
     }

     /**
      * Metoda care va intoarce instanta singleton
      *
      * @return SingletonDB
      */
     public static function getInstance() {
          if (self::$_instance == null) {
               self::$_instance = new SingletonDB ( );
          }

          return self::$_instance;
     }
}

try {
     // Instanta SingletonDB
     $db = SingletonDB::getInstance ();

     // Interogare de executat
     $q = 'SELECT * FROM test';

     // interogarea va returna un obiectu mysqli_result
     $result = $db->query ( $q );

     // Se verifica daca exista erori si se afisaza
     if ($db->error ()) {

          echo $db->error ();

     } else {
          // se extrag rezultatele si se afisaza pe ecran
          while ( $row = $result->fetch_assoc () ) {
               var_dump ( $row );
          }
          // se inchide obiectul de mysqli_result
          $result->close ();
     }
// in cazul in care apar exceptii acestea se afisaza
} catch ( Exception $e ) {
     echo $e->getMessage ();
}

Written by Claudiu Persoiu

10 October 2009 at 8:33 PM

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