PHP/Rapid prototyping

Un article de Le wiki de 2 noisettes - noisette.ch.


Cet article est en cours de réalisation.


<troll>
  Vous avez dit : déployer une application robuste et stable avec PHP ? 
  Il y a suffisamment de discussion sur les langages non-typés pour se convaincre qu'il y a un effort à faire au niveau de la sécurité...
</troll>

Sans vouloir faire l'avocat du pour ou contre PHP, je souhaite montrer comment faire du rapid prototyping avec PHP et ses fonctions magiques __call, __get et __set.

L'idée est simple : on a une base de données relationnelle, dans laquelle on crée différentes tables. Chaque table sera mappée sur un objet PHP, contenant les attributs de la table ainsi que les méthodes standards load, store, create, delete.

La partie intéressante se situe au niveau des attributs et des getters / setters si on me permet l'utilisation des termes de Java :

  • On définit dans un tableau $fields les champs de la table à insérer, charger, mettre-à-jour et effacer
  • Puis grâce aux fonctions __get et __set, on peut y accéder comme si on avait à faire avec des attributs de la classe, le tout de manière dynamique à grand coup de foreach ($this->fields as $field).
  • Il nous suffit de faire des classe portant le nom des tables qui étendent notre classe dynamique et qui passe directement les champs au constructeur parent.
<?php

/* 
 * Usage : 
 * 
 * * Insertion
 *   $o = DBObject::factory($array_of_values); // $array_of_values doesn't contaion $this->_id()
 * 
 * * Update
 *   $o = DBObject::factory($id);
 *   $o->update($array_of_value); 
 *
 * * Select
 *   $o = DBObject::factory($id);
 */
class DBObject
{
   private $id = 0;
   private $table;
   private $fields = array();
   protected function __construct($table, $fields)
   {
       $this->table = $table;
       foreach($fields as $key) {
           $this->fields[$key] = null;
       }
   }
   public function __get($key)
   {
       return $this->fields[$key];
   }
   public function __set($key, $value)
   {
       if (array_key_exists($key, $this->fields)) {
           $this->fields[$key] = $value;
           return true;
       }
       return false;
   }
   
   protected function _id() {
       return $this->table . '_id';
   }
   
   protected function _table() {
       return $this->table;
   }
   
   public function getId() {
       return $this->id;
   }
   public function setId($id) {
       $this->id = intval($id);
   }
   
   public function load($id = 0)
   {
       global $db;
       if (!empty($id)) {
           $this->setId($id);
       }
       $query = sprintf('SELECT * FROM ' . $this->_table() . ' WHERE ' . $this->_id() . ' = %d ', $this->getId());
       $result = $db->query($query);
       if (PEAR::isError($result)) { die('Erreur 3 result : ' . $result->getMessage()); }
       $result->fetchInto($row, DB_FETCHMODE_ASSOC);
       
       foreach(array_keys($row) as $key) {
           $this->fields[$key] = $row[$key];
       }
   }
   
   public function insert($data)
   {
       global $db;
       if (!isset($data[$this->_id()]) || empty($data[$this->_id()])) {
           $id = $db->nextId($this->_table());
           $data[$this->_id()] = $id;
       }
       $this->setId($data[$this->_id()]);
       
       $fields = join(", ", array_keys($this->fields));
       foreach(array_keys($this->fields) as $field) {
           $inspoints []= "?";
       }
       $inspt = join(", ", $inspoints);
       $query = 'INSERT INTO ' . $this->_table() . ' ( ' . $fields . ' ) VALUES ( ' . $inspt . ' )';
       
       $values = array();
       foreach(array_keys($this->fields) as $field) {
           if ($field == $this->_id()) {
               $values[] = $this->getId();
           } else if (isset($data[$field])) {
               $values[] = $db->escapeSimple($data[$field]);
               $this->fields[$field] = $data[$field];
           } else {
               $values[] = $db->escapeSimple($this->fields[$field]);
           }
       }
       $stmt = $db->prepare($query);
       if (PEAR::isError($stmt)) { die('Error stmt : ' . $stmt->getMessage()); }
       $result = $db->execute($stmt, $values);
       if (PEAR::isError($result)) { die('Error result : ' . $result->getMessage()); }
       return $this->id;
   }
   public function update($data)
   {
       global $db;
       $sets = array();
       $values = array();
       foreach(array_keys($data) as $field) {
           if (array_key_exists($field, $this->fields) && $field != $this->_id()) {
               $sets[] = $field . ' = ?';
               $values[] = $db->escapeSimple($data[$field]);
           }
       }
       $set = join(", ", $sets);
       $values[] = $this->id;
       $query = 'UPDATE ' . $this->_table() . ' SET ' . $set . ' WHERE ' . $this->_id() . ' = ?';
       
       $stmt = $db->prepare($query);
       if (PEAR::isError($stmt)) { die('Error stmt : ' . $stmt->getMessage()); }
       $result = $db->execute($stmt, $values);
       if (PEAR::isError($result)) { die('Error result : ' . $result->getMessage()); }
   }
   public function delete()
   {
       global $db;
       $stmt = $db->prepare('DELETE FROM ' . $this->_table() . ' WHERE ' . $this->_id() . ' = ?');
       if (PEAR::isError($stmt)) { die('Error stmt : ' . $stmt->getMessage()); }
       $result = $db->execute($stmt, array($this->id));
       if (PEAR::isError($result)) { die('Error result : ' . $result->getMessage()); }
       
   }
   public function delete_all()
   {
       global $db;
       $stmt = $db->prepare('DELETE FROM ' . $this->_table());
       if (PEAR::isError($stmt)) { die('Error stmt : ' . $stmt->getMessage()); }
       $result = $db->execute($stmt);
       if (PEAR::isError($result)) { die('Error result : ' . $result->getMessage()); }
   }
   
   public static function factory($params, $classname = 'DBObject') {
       $o = new $classname();
       if (is_array($params)) {
           $o->insert($params);
       } else if (is_numeric($params)) {
           $o->load($params);
       } else {
           return null;
       }
       return $o;
   }
}


Debug du "Segmentation fault" avec PHP 5.1.6-r2