Pour certains projets, il est parfois nécessaire qu’ils puissent fonctionner sur plusieurs types de base de données (MySQL, MS SQL, Oracle…). Ces bases de données ont à la fois de grandes similitudes ainsi que certaines spécificités. L’idéal serait qu’en passant d’une base de données à une autre, on ne soit pas obligé de modifier le code pour chaque requête exécutée.
Dans le code suivant, une fois que les primitives d’accès aux types de bases de données sont configurées, un simple changement dans le fichier de configuration permet de passer d’un type de base donnée à un autre.
Le polymorphisme objet est utilisé ici, il est donc nécessaire au préalable de maîtriser les classes PHP 5.
La classe suivante fournit les méthodes abstraites qui devront être redéfinie par héritage pour chaque type de basse de données, elle comprend également les méthodes communes aux différentes base de données. L’intérêt est de ne pas coder les requêtes pour chaque type de base de données si elles sont identiques, mais de pouvoir les spécialiser dans le cas contraire.
<?php
/**
* Objet de connection générique
*/
require_once("config.php");
abstract class Connection
{
protected $_hConnection = null;
/*****************************************************
* Fonctions spécifiques aux bdd à redéfinir
*****************************************************/
protected abstract function FullConnect($sHost, $sUsername, $sPassword, $sDbName);
public abstract function ExecQuery($sQuery);
public abstract function FetchArrayResultInArray($result);
public abstract function FetchSingleResultInArray($result, $nRowIndex=0);
public abstract function GetLastError();
/*****************************************************
* Gestion des dates a redefinir
*****************************************************/
public abstract function DateComparison($sDateFieldName, $sDate);
public abstract function SystemDateFormat($sDateFieldName);
public abstract function FrenchDateFormat($sDateFieldName);
/*****************************************************
* Fonctions commmunes aux bdd
*****************************************************/
public function Connect()
{
$this->FullConnect(DB_HOST, DB_USERNAME, DB_PASWORD, DB_NAME);
}
public function DieOnLastError()
{
$sLastError = $this->GetLastError();
die ($sLastError);
}
/*****************************************************
* Fonctions de gestions communes aux bases de données
*****************************************************/
public function getUsers()
{
$sQuery = "SELECT * FROM `user`";
$result = $this->ExecQuery($sQuery);
$aUsers = $this->FetchArrayResultInArray($result);
return $aUsers;
}
/*****************************************************
* Fonctions de gestions spécifiques aux bases de données
*****************************************************/
public abstract function getFirstUser();
}
?>
Le dérivation de la class Connection pour MySQL:
<?php
/**
* Description of MYSQLConnectionclass
*
*/
require_once("Connection.class.php");
class MYSQLConnection extends Connection{
protected function FullConnect($sHost, $sUsername, $sPassword, $sDbName)
{
//Si la connexion est déjà établie
if ($this->_hConnection != null)
return true;
//Connexion
$this->_hConnection = mysql_connect($sHost, $sUsername, $sPassword);
if ($this->_hConnection == null)
$this->DieOnLastError();
//Sélection de la bdd
$bSuccess = mysql_select_db($sDbName, $this->_hConnection);
if(!$bSuccess)
$this->DieOnLastError();
return $bSuccess;
}
public function ExecQuery($sQuery)
{
//Si la connexion n'a pas été effectuée ou n'est pas valide
if ($this->_hConnection == null)
return null;
//Exécution de la requête
$result = mysql_query($sQuery, $this->_hConnection);
if ($result == null)
$this->DieOnLastError();
return $result;
}
public function FetchArrayResultInArray($result)
{
$aRows = array();
while($aRow = mysql_fetch_array($result))
{
array_push($aRows, $aRow);
}
return $aRows;
}
/**
* Création d'une array de donné avec seulement une valeur de la row retournée
* nRowIndex est la position de la donnée dans la row
* @param <type> $result
* @param <type> $nRowIndex
* @return <type>
*/
public function FetchSingleResultInArray($result, $nRowIndex=0)
{
$aRows = array();
while($aRow = mysql_fetch_array($result))
{
if (count($aRow)>$nRowIndex)
array_push($aRows, $aRow[$nRowIndex]);
}
return $aRows;
}
/**
* Regles de comparaison dans la clause WHERE
* @param <type> $sDateFieldName
* @param <type> $sDate
*/
public function DateComparison($sDateFieldName, $sDate)
{
$sFormat = "%s = '%s'";
$sComparison = sprintf($sFormat, $sDateFieldName, $sDate);
return $sComparison;
}
/**
* Formattage des dates dans la clause SELECT pour obtenir AAAA-MM-JJ
* @param <type> $sDateFieldName
*/
public function SystemDateFormat($sDateFieldName)
{
return $sDateFieldName;
}
/**
* Formattage des dates dans la clause SELECT pour obtenir JJ/MM/AAAA
* @param string $sDateFieldName
*/
public function FrenchDateFormat($sDateFieldName)
{
$sFormat = "date_format(%s, '%%e/%%m/%%Y')";
$sDateFormat = sprintf($sFormat, $sDateFieldName);
return $sDateFormat;
}
/**
* Renvoit la dernière erreur du serveur
* @return string LastErrorMessage
*/
public function GetLastError()
{
return mysql_error();
}
/**
* Spécialisation de la méthode pour MySQL
* Retourne le premier user
* @return Array L'utilisateur
*/
public function GetFirstUser()
{
$sQuery = "SELECT * FROM `user` LIMIT 1";
$result = $this->ExecQuery($sQuery);
$user = $this->FetchArrayResultInArray($result);
return $user;
}
}
?>
La spécialisation MS SQL:
<?php
/**
* Objet de connection MSSQL
*/
require_once("Connection.class.php");
class MSSQLConnection extends Connection
{
protected function FullConnect($sHost, $sUsername, $sPassword, $sDbName)
{
//Si la connexion est déjà établie
if ($this->_hConnection != null)
return true;
//Connexion
$this->_hConnection = mssql_connect($sHost, $sUsername, $sPassword);
if ($this->_hConnection == null)
$this->DieOnLastError();
//Sélection de la bdd
$bSuccess = mssql_select_db($sDbName, $this->_hConnection);
if(!$bSuccess)
$this->DieOnLastError();
return $bSuccess;
}
public function ExecQuery($sQuery)
{
//Si la connexion n'a pas été effectuée ou n'est pas valide
if ($this->_hConnection == null)
return null;
//Exécution de la requête
$result = mssql_query($sQuery, $this->_hConnection);
if ($result == null)
$this->DieOnLastError();
return $result;
}
public function FetchArrayResultInArray($result)
{
$aRows = array();
while($aRow = mssql_fetch_array($result))
{
array_push($aRows, $aRow);
}
return $aRows;
}
/**
* Création d'une array de données avec seulement une valeur de la row retournée
* nRowIndex est la position de la donnée dans la row
* @param <type> $result
* @param <type> $nRowIndex
* @return <type>
*/
public function FetchSingleResultInArray($result, $nRowIndex=0)
{
$aRows = array();
while($aRow = mssql_fetch_array($result))
{
if (count($aRow)>$nRowIndex)
array_push($aRows, $aRow[$nRowIndex]);
}
return $aRows;
}
/**
* Regles de comparaison dans la clause WHERE
* @param <type> $sDateFieldName
* @param <type> $sDate
*/
public function DateComparison($sDateFieldName, $sDate)
{
$sFormat = "%s = CONVERT(datetime,'%s',121)";
$sComparison = sprintf($sFormat, $sDateFieldName, $sDate);
return $sComparison;
}
/**
* Formattage des dates dans la clause SELECT pour obtenir AAAA-MM-JJ
* @param <type> $sDateFieldName
*/
public function SystemDateFormat($sDateFieldName)
{
$sFormat = "CONVERT(CHAR(10), %s, 121)";
$sDateFormat = sprintf($sFormat, $sDateFieldName);
return $sDateFormat;
}
/**
* Formattage des dates dans la clause SELECT pour obtenir JJ/MM/AAAA
* @param string $sDateFieldName
*/
public function FrenchDateFormat($sDateFieldName)
{
$sFormat = "CONVERT(CHAR(10), %1\$s, 103) AS %1\$s";
$sDateFormat = sprintf($sFormat, $sDateFieldName);
return $sDateFormat;
}
/**
* Renvoit la dernière erreur du serveur
* @return string LastErrorMessage
*/
public function GetLastError()
{
return mssql_get_last_message();
}
/**
* Spécialisation de la méthode pour MSSQL
* Retourne le premier user
* @return Array L'utilisateur
*/
public function GetFirstUser()
{
$sQuery = "SELECT TOP 1 * FROM `user`";
$result = $this->ExecQuery($sQuery);
$user = $this->FetchArrayResultInArray($result);
return $user;
}
}
?>
Une classe factory permettant de créer l’objet de connexion pour le type de base de données cible selon la configuration:
<?php
/**
* Gérateur de connection en fonction du type de base de données configuré
*/
require_once("config.php");
switch(TARGET_BD)
{
case "MSSQL":
require_once("MSSQLConnection.class.php");
break;
case "MYSQL":
require_once("MYSQLConnection.class.php");
break;
default:
die(TARGET_BD." pas encore implemente<br>");
}
class ConnectionFactory {
public static function getConnection()
{
switch(TARGET_BD)
{
case "MSSQL":
return new MSSQLConnection();
case "MYSQL":
return new MYSQLConnection();
default:
die(TARGET_BD." pas encore implemente<br>");
}
}
}
?>
Le fichier de configuration permettant de cibler le type de base de donnée et les paramètres de connexion:
<?php
//Paramètres de connexion à la bdd
define("TARGET_BD","MYSQL");
define("DB_HOST","localhost");
define("DB_USERNAME","root");
define("DB_PASWORD","pwd");
define("DB_NAME","sample");
?>
Un exemple d’utilisation qui permet d’exécuter la méthode getUsers() dont la syntaxe de la requête est commune aux différents type de bases de données et est définie dans la classe générique Connection.
La méthode getFirstUser() illustre une méthode qui est spécialisée par base de données.
On remarque qu’à l’utilisation rien de diffère l’une de l’autre grâce au mécanisme de polymorphisme:
<?php
require_once('ConnectionFactory.class.php');
$connection = ConnectionFactory::getConnection();
$connection->connect();
$users = $connection->getUsers();
$firstUser= $connection->getFirstUser();
echo("Users:<br/>");
print_r($users);
echo("<br/><br/>First User:<br/>");
print_r($firstUser)
?>
Pour ajouter de nouvelles méthodes d’accès à la base de données, il suffit de déterminer si la syntaxe de la requête est commune ou spécique à un moteur de bdd.
- Si elle commune, l’ajouter dans la classe Connection
- Si elle est spécifique, l’ajouter dans les fichiers spéciques (ici MYSQLConnection et MSSQLConnection)
Pour ajouter un nouveau moteur de base de données, il est nécessaire de créer une classe héritant de Connection, d’implémenter les méthodes spécifique (connexion, fetch, gestion des dates etc…), puis de modifier ConnectionFactory pour le prendre en compte.
Pour finir, il suffit de modifier TARGET_BD du fichier de config ainsi que les parmètres de connexion pour simplement passer d’un type de base de données à l’autre.
Cliquer ici pour télécharger les sources complètes.