J’ai récemment eu à coder à mon taf des jeux de tests pour valider le comportement de mon serveur de pub, et pour ça, j’ai codé des petits scripts tout bête pour créer/modifier les données dans une base postgresql. J’aurai pu prendre un ORM tout fait (propel, doctrine…), mais j’ai juste codé mes classes tout bêtement et le plus simplement du monde. Un exemple:
{
public $id;
public $marque;
public $couleur;
};
Pour les requêtes d’insert, au lieu d’avoir des méthodes spécialisées dans chaque classe, j’ai codé juste une fonction save() qui prend l’objet à sauver, et qui parse grâce aux fonctions get_class_* ses propriétés pour générer les requêtes d’INSERT ou d’UPDATE. Cette fonction ressembait dans un premier temps à:
{
$class_name = get_class($obj);
$class_vars = get_class_vars($class_name);
$fields_names = '';
$fields_values = '';
$fields_count = 0;
foreach($class_vars as $var_name => $var_value)
{
if(isset($obj->$var_name))
{
$fields_names .= $var_name . ', ';
if(is_string($obj->$var_name))
$fields_values .= '\'' . $obj->$var_name . '\', ';
else
$fields_values .= $obj->$var_name . ', ';
$fields_count ++;
}
}
if(0 == $fields_count)
{
return -1;
}
// Remove trailing ', ';
$fields_names = substr($fields_names, 0, strlen($fields_names) - 2);
$fields_values = substr($fields_values, 0, strlen($fields_values) - 2);
return "INSERT INTO " . $class_name . "(" . $fields_names . ") VALUES (" . $fields_values . ");";
}
Ca me contentait dans pas mal de cas simples, mais très rapidement, cela à montré ses limites. J’avais besoin de connaître plus précisement les types des champs dans la base. Et la solution a été d’utiliser les annotations, pour ne pas avoir de longues et lourdes modifications à faire dans toutes mes classes d’objet. PHP ne propose pas out of the box un système complet d’annotation, mais incorpore les outils pour développer un comportement analogue: les classes Reflection.
Ces classes permettent de faire du « reverse engineering » sur les classes, les interfaces etc pour retrouver les différentes composantes de ces objets, qu’ils soient instanciés ou non. Et de plus, elles permettent de récupérer les commentaires des classes, des méthodes et des propriétés s’ils existent. Il n’y a qu’à les mettre en application:
/**
* Commentaire classe voiture
*/
class Voiture
{
/**
* Commentaire propriete id
*/
public $id;
}
$voiture = new Voiture;
$reflection = new ReflectionClass($voiture);
echo "Classe:\n";
echo $reflection->getDocComment() . "\n";
foreach($reflection->getProperties() as $property)
{
echo $property->name . ":\n";
echo $property->getDocComment() . "\n";
}
Ce qui donne:
/**
* Commentaire classe voiture
*/
id:
/**
* Commentaire propriete id
*/
$
Il ne resterait plus maintenant qu’à définir ses keywords, parser les commentaires propement pis à coder son propre mini-orm sans modifier les objets finaux.
Et voilà un début:
class DbObject
{
public function save()
{
$schema = $this->buildSchema($this);
/* Ici y a du code */
return TRUE;
}
private function buildSchema()
{
$class_name = get_class($this);
$class_vars = get_class_vars($class_name);
$class_reflection = new ReflectionClass($this);
echo $class_reflection->getDocComment();
$schema = array();
foreach($class_vars as $var_name => $var_value)
{
$prop_reflection = $class_reflection->getProperty($var_name);
$comment = $prop_reflection->getDocComment();
$comment = preg_replace(',\/\*\*(.*)\*\/,', '$1', $comment);
$comments = preg_split(',\n,', $comment);
$key = $val = NULL;
$schema[$var_name] = array();
foreach($comments as $comment_line)
{
if(preg_match(',@(.*?): (.*),i', $comment_line, $matches))
{
$key = $matches[1];
$val = $matches[2];
$schema[$var_name][trim($key)] = trim($val);
}
}
}
var_dump($schema);
return $schema;
}
};
class Voiture extends DbObject
{
/**-
* @DbType: integer
* @DbValue: 0
*/
public $id;
/** @DbType: string
*
* Blabla.
*
*/
public $marque;
/** @DbType: string */
public $couleur;
};
$voiture = new Voiture;
$voiture->id = 1;
$voiture->marque = 'renault';
$voiture->couleur = 'bleu';
$voiture->save();
buildSchema retournera dans cet exemple:
array(3) {
["id"]=>
array(2) {
["DbType"]=>
string(7) "integer"
["DbValue"]=>
string(1) "0"
}
["marque"]=>
array(1) {
["DbType"]=>
string(6) "string"
}
["couleur"]=>
array(1) {
["DbType"]=>
string(6) "string"
}
}
Pour finir, quelques petites références sur le sujet:
- http://www.php.net/manual/fr/book.reflection.php
- http://sebastian-bergmann.de/archives/488-Annotations-in-PHP.html
Et au final, au lieu de réinventer une roue bien vieille, je suis parti utiliser Addendum: