Introduction

PHP5 a apporté un grand nombre d’améliorations notamment au niveau programmation objet. Il est par exemple possible de définir la visibilité des attributs (public, private, protected,… ). L’ajout de fonctions dites « magiques » est également une évolution notable de cette nouvelle version (chargement automatique des classes manquantes, surcharge des getters/setters).
Dans cet article nous allons voir comment faciliter au maximum la manipulation de nos objets en php. L’idée est simplement de pouvoir enregistrer des messages dans une base de données.

1. Liste des fichiers

  • test.php fichier à exécuter pour tester la manipulation d’objets
  • database.class.php Classe gérant les connexions à une base de données mysql
  • message.class.php Classe permettant de charger, ajouter/modifier, supprimer des messages dans la bdd

2. Structure de la table

Ci-dessous le code sql de création de la table :
CREATE TABLE IF NOT EXISTS `messages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`firstname` varchar(255) NOT NULL,
`lastname` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`phone` varchar(255) NOT NULL,
`message` text NOT NULL,
`ip` varchar(255) NOT NULL,
`host` varchar(255) NOT NULL,
`creationdate` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM;

3. Classe database

Ci-dessous le code de la classe database. Je l’ai développé en suivant le pattern singleton ce qui signifie qu’une seule instance de ma classe sera créée. L’utilisation de cette classe est très simple puisqu’il suffit d’appeler à n’importe quel endroit du code database::getInstance()->MaMethode() ; sans se préoccuper de créer une instance de « database ». Vous remarquerez également la présence de la ligne « database::getInstance(); » après la déclaration de la classe. Ceci permet de créer l’instance de database et donc d’initialiser la connexion. L’absence de bloc statique en php m’oblige à effectuer cette initialisation en dehors de la classe.

<?php
/**
 * Manage queries for a mysql db
 * 
 * <p>database class provides some methods to initialize a mysql db connexion.<br/>
 * database class is a singleton, use database::getInstance() to get db instance and call all methods
 * 
 * To use this class, please change db connexion informations (host, user, pass, base)
 * </p>
 * 
 * @name database
 * @author Olivier Hélin <contact@olivierhelin.com>
 * @link 
 * @copyright Olivier Hélin 2010
 * @version 1.0.0
 * @package test
 */
 
class database {
    private $_host = "127.0.0.1:3306";
    private $_user = "root";
    private $_pass = "";
    private $_base = "test";
 
    private $_cursor = null;
    private $_ressource = null;
    private $_errorNum = 0;
    private $_errorMsg = '';
 
    /**
     * Shared instance
     */		
    private static $_instance;
 
    /**
    * Constructor
    * 
    * <p>Create the static instance of the singleton</p>
    * 
    * @name database::__construct()
    * @return void
    */    
    private function __construct () {
    	$this->_ressource = mysql_connect($this->_host, $this->_user, $this->_pass);
		mysql_select_db( $this->_base );
 
		//set utf-8
		$this->query("SET CHARACTER SET utf8;");
		$this->query("SET NAMES utf8;");
    }
 
    /**
    * Clone
    * 
    * <p>Avoid extern copy of instance</p>
    * 
    * @return void
    */    
    private function __clone () {}
 
 
    /**
    * getInstance
    * 
    * <p>Get the shared db instance</p>
    * 
    * @return database instance
    */      
    public static function getInstance () {
        if (!(self::$_instance instanceof self))
            self::$_instance = new self();
 
        return self::$_instance;
    }    
 
 
    /**
    * getObject
    * 
    * <p>Get one tuple from database (SELECT requests)</p>
    * 
    * @param selection query $sqlquery
    * @return dbrow
    */
	function getObject ( $sql_query ) {
		$this->_cursor = mysql_query ( $sql_query, $this->_ressource );
		if ( $this->_cursor != null ) {
			$object = mysql_fetch_object ( $this->_cursor );
			mysql_free_result ( $this->_cursor );
			return $object;
		}
		return false;
	}
 
	/**
    * getObjects
    * 
    * <p>Get a collection from database (SELECT requests)</p>
    * 
    * @param selection query $sqlquery
    * @return array
    */	
	function getObjects ( $sql_query ) {
		$this->_cursor = mysql_query ( $sql_query, $this->_ressource );
		$objects = array();
		if ( $this->_cursor ) {
			while ( $object = mysql_fetch_object ( $this->_cursor ) )
				array_push ( $objects, $object );
			mysql_free_result ( $this->_cursor );
		}
		return $objects;
	}
 
	/**
	 * Execute a query (INSERT, DELETE, UPDATE,... requests)
	 *
	 * @param sql query to execute $sql_query
	 * @return mysql_query result
	 */		
	function query ( $sql_query ) {
		$this->_errorNum = 0;
		$this->_errorMsg = '';
		$this->_cursor = mysql_query( $sql_query, $this->_ressource );
		if (!$this->_cursor) {
			$this->_errorNum = mysql_errno( $this->_ressource );
			$this->_errorMsg = mysql_error( $this->_ressource ) ." SQL=".$sql_query ;
			echo $this->_errorMsg;
			return false;
		}
		return $this->_cursor;
	}
 
	/**
	 * Get the latest id
	 *
	 * @return lastest id
	 */			
	function getInsertId () {
		if ( $this->_ressource != null )
			return mysql_insert_id( $this->_ressource )	;
		return false;
	}
 
	/**
	 * Check if $table exist
	 *
	 * @param table $table
	 * @return boolean
	 */
	function isTableExist($table){
	    $query = "SHOW TABLES FROM ".$this->_base;
	    $runQuery = mysql_query($query);
 
	    $tables = array();
	    while($row = mysql_fetch_row($runQuery)){
	        $tables[] = $row[0];
	    }
 
	    if(in_array($table, $tables)){
	        return true;
	    }
	    return false;
	}
 
	/**
	 * List all tables of the database
	 *
	 * @return array which contains all tables names
	 */
	function listTables(){
	    $query = "SHOW TABLES FROM ".$this->_base;
	    $runQuery = mysql_query($query);
 
	    $tables = array();
	    while($row = mysql_fetch_row($runQuery)){
	        $tables[] = $row[0];
	    }
	    return $tables;
	}	
 
	/**
	 * List all columns of $table
	 *
	 * @param  $table
	 * @return array which contains all tables names
	 */
	function listColumnsOfTable($table){
	    $query = "SHOW COLUMNS FROM ".$table;
 
	    $columns = array();
	    if (!$result) {  
			return $columns;
		}  
		if (mysql_num_rows($result) > 0) {  
			while ($row = mysql_fetch_assoc($result)) { 
				$columns[] = $row;
			}
		}
		return $columns;
	}
}
$_db = database::getInstance();
?>

4. Classe message

Toutes nos propriétés sont en « private » ce qui signifie qu’on ne peut y accéder directement, elles sont également toutes préfixées d’un « _ » uniquement par convention et pour identifier les propriétés au premier coup d’œil.
Exemple :

$msg = new message() ;
$msg->_firstname = 'toto';

Ce code nous renvoie une erreur, cependant grâce aux fonctions magiques __get et __set il est possible de rendre l’affectation et la récupération de la valeur d’un attribut transparent.
Le mécanisme des getters setters est assez simple à appréhender, dans le code des méthodes __get et __set, je récupère le nom de l’attribut par exemple « firstname », je le préfixe d’un « _ » pour obtenir « _firstname » et je teste si l’attribut existe ou non grâce à la « réflection ». Quand je donne une valeur à un attribut j’applique la méthode mysql_real_escape_string qui ajoute des « \ » devant les quotes notamment afin d’éviter les attaques de type injections SQL. A contrario, lorsque je récupère la valeur d’un attribut j’applique la méthode inverse : mysql_real_escape_string qui enlève les « \ » ajoutés précédemment.

NOTE : Il est également possible d’utiliser les fonctions addslashes et stripslashes ce qui m’aurait éviter la ligne « database::getInstance(); » après la déclaration de la classe database.class.php. En effet, mysql_real_escape_string et mysql_real_unescape_string nécessite une connexion déjà ouverte sur la base mysql.

Code :

<?php
class message {
	private $_id;
	private $_firstname;
	private $_lastname;
	private $_email;
	private $_phone;
	private $_message;
	private $_ip;
	private $_host;
	private $_creationdate;
 
 
	/**
	 * Create new message instance
	 */
	function __construct() {
		$this->_host = getHostByAddr($_SERVER["REMOTE_ADDR"]);
		$this->_ip = $_SERVER["REMOTE_ADDR"];
		$this->_creationdate = date('Y-m-d H:i:s');
	}
 
	/**
	 * Get a property
	 *
	 * @param object $name
	 */
	function __get($name) {
		//--Reflection
		$reflection = new ReflectionClass(get_class($this));
		//--Retrieve name attribute
		$tmp='_'.$name;
 
		if($reflection->hasProperty('_'.$name)){
			return mysql_real_unescape_string($this->$tmp);
		}
		else{
			throw new Exception('The property '.$name.' is invalid.');
		}
 
	}
 
	/**
	 * Set a property
	 *
	 * @param object $name
	 * @param object $value
	 */	
	function __set($name,$value) {
		//--Reflection
		$reflection = new ReflectionClass(get_class($this));
		//--Retrive name attribute
		$tmp='_'.$name;
 
		if($reflection->hasProperty('_'.$name)){
			$this->$tmp = mysql_real_escape_string($value);
		}
		else{
			throw new Exception('The property '.$name.' is invalid.');
		}
	}
 
	/**
	 * Load the message by id
	 *
	 * @param int oid
	 */
	function load($oid=null ) {
		if ( $oid != null && $oid!=0){
			$this->_id = $oid;
			$res = database::getInstance()->getObject( 'SELECT * FROM messages WHERE id = "'.$this->_id.'";' );
			//Check if getObject doesn't return null
			if($res){
				$this->_firstname		= $res->firstname;
				$this->_lastname		= $res->lastname;
				$this->_email			= $res->email;
				$this->_phone			= $res->phone;
				$this->_message			= $res->message;
				$this->_ip				= $res->ip;
				$this->_host			= $res->host;
				$this->_creationdate	= $res->creationdate;
			}
		}
	}
 
	/**
	 * Store the message
	 */
	function store() {
		global $_db;		
 
		if ( $this->_id == 0 ) {
			$this->creationdate = date('Y-m-d H:i:s');
			$_db->query
			( "INSERT INTO `message` 
			( `id`, `firstname` , `lastname` , `email` , `phone`, `message`, `ip`, `host`, `creationdate`) 
			VALUES ('', '".$this->_firstname."', '".$this->_lastname."', '".$this->_email."', '".$this->_phone."', '".$this->_message."', '".$this->_ip."', '".$this->_host."', '".$this->_creationdate."' );" );
			$this->_id = $_db->getInsertId();
		}else {
			//this->lastmodificationdate = date('Y-m-d H:i:s');	
			$_db->query( 'UPDATE message SET firstname="'.$this->_firstname.'", lastname="'.$this->_lastname.'", email="'.$this->_email.'", phone="'.$this->_phone.'", message="'.$this->_message.'", ip="'.$this->_ip.'", host="'.$this->_host.'" WHERE id = "'.$this->id.'";' );
		}
	}
 
	/**
	 * Delete the message
	 */
	function delete() {
		global $_db;
		$_db->query( 'DELETE FROM message WHERE id = '.$this->_id.';');
	}
 
	/**
	 * Display message attributes values
	 */
	public function __toString() {
		return 
		'id =>'.$this->_id.'<br/>'.
		'firstname =>'.$this->_firstname.'<br/>'.
		'lastname =>'.$this->_lastname.'<br/>'.
		'email =>'.$this->_email.'<br/>'.
		'phone =>'.$this->_phone.'<br/>'.
		'message =>'.$this->_message.'<br/>'.
		'ip =>'.$this->_ip.'<br/>'.
		'host =>'.$this->_host.'<br/>'.
		'date de creation =>'.$this->_creationdate.'<br/>';
	}
}
?>

5. Fichier test.php

Dans ce fichier nous allons créer un objet message et l’enregistrer dans la base de données. Vous remarquerez que je ne fais pas d’include ou de require pour importer ma classe message.class.php. je le fais uniquement pour database.class.php afin qu’il m’initialise la connexion. Depuis php5 la fonction magique __autoload permet d’exécuter un code si on cherche à utiliser une classe qu’il ne connait pas. Dans notre exemple mes fichiers définissant les classes s’apellent « maclass.class.php ». J’utilise alors cette règle de nommage pour charger mes classes et éviter de nombreux includes sur de gros projets.
Pour créer un objet, il suffit de faire un new message() ;, de définir ses propriétés puis d’appeler la méthode store() ; qui s’occupera d’insérer un nouvel enregistrement dans la base de données si le message n’existe pas ou de faire un update si le message existe déjà. Ainsi deux appels consécutifs

$msg->store();
$msg->store();

Ne fera qu’un seul enregistrement dans la base de données.

Code:

<?php
include('./database.class.php');
 
//--Chargement des classes
function __autoload($class_name) {
	require_once $class_name . '.class.php';
}
 
//--Creation et enregistrement d'un message
$msg = new message();
$msg->message = 'mon" message';
$msg->firstname = 'olivier';
$msg->lastname = 'helin';
$msg->email = 'aa@aa.fr';
$msg->phone = '06 xx xx xx xx';
$msg->store();
?>
Last modified: 30 November 2010

Author

Comments

Bonsoir,

Il ne manque faut pas un $_db = database::getInstance(); à la fin du fichier database.class.php ? En le rajoutant, j’ai retiré la seule erreur que j’avais, et maintenant tout est nickel.
Merci encore pour ce résumé très clair de DAO en php5.

Author

Bonjour,
Merci d’avoir repéré la coquille il manquait bien un “$_db =” ! J’espère en tout cas que ce résumé vous aura aidé 🙂

Leave a Reply to Bill Cancel reply

Your email address will not be published.