Gestion d'objets PHP

PHP est un langage très souple, et qui permet notament de faire de l'objet. Mais les versions évoluant, PHP viens à devenir plus lourd, et donc plus lent. Voici donc un essai d'optimisation d'objet gérant des données contenues dans une base de données.

Cela fait bientot 2 ou 3 ans que je code en orienté objet en PHP, depuis qu'on en a fait en C++. C'est une méthode de code qui permet de fabriquer des briques logicielles très poussées, et avoir une couche d'abstraction.

Je vais prendre un exemple d'un objet qui gére des données sur une personne.

<?php
class Personne { /** * User ID de la personne, sert à retrouver la personne */ var $uid;
/** * Nom de la personne */ var $nom;
/** * Prénom de la personne */ var $prenom;
/** * Age de la personne */ var $age;
/** * Constructeur * * @access public * @params pUid UID demandé de la personne * @return void */ function Personne($pUid = NULL) { if($this->pUid) { $this->uid = $pUid; } $this->_load(); }
/** * charge les données de la personne si on précise son iud * @access private * @param void * @return void */ function _load() { if($this->uid) { $query = 'SELECT nom,prenom,age FROM personnes WHERE uid='.$this->uid; $res = mysql_query($query); $ret = mysql_fetch_row($res); list($this->nom, $this->prenom,$this->age) = each($ret); return; } $this->nom = $this->prenom = NULL; $this->age = -1; }
/** * Changemennt du nom de la personne * @access public * @param pNouveauNom le nouveau nom de la personne * @return void */ function changeNom($pNouveauNom) { $this->nom = $pNouveauNom; $query = 'UPDATE personnes SET nom="'.$this->nom.'"'; $res = mysql_query($query); }
function changePrenom($pNouveauPrenom) { $this->prenom = $pNouveauNom; $query = 'UPDATE personnes SET nom="'.$this->prenom.'"'; $res = mysql_query($query); }
function changeAge($pNouvelAge) { $this->age = $pNouvelAge; $query = 'UPDATE personnes SET nom="'.$this->age.'"'; $res = mysql_query($query); }
}; ?>

Dans une utilisation bateau des methodes, à chaque changement d'un attribut, cela génére une nouvelle requéte sur la base de données. Si votre classe devient importante, qu'elle doit gérer beaucoup de données, lors d'un changement de plusieurs attribut, il y a une requète pour chaque données, alors qu'elles sont dans la même table. Il y a donc une optimisation possible ici.

La solution que je vais utiliser ici utilise certes un peu plus de mémoire, émulera des destructeurs, mais réduira de façcon conséquente le nombre de requete MySQL. Elle consiste, pour chaque valeur, à avoir un tableau qui nous servira si la valeur a été modifiée, la nouvelle valeur, et sa place dans la base de donnée. Vous comprenez mieux avec du code ?

<?php
class Personne { /** * User ID de la personne, sert à retrouver la personne */ var $uid = array('val' => 0, 'modified' => FALSE, 'bdd' => 'uid');
/** * Nom de la personne */ var $nom = array('val' => '', 'modified' => FALSE, 'bdd' => 'nom');
/** * Prénom de la personne */ var $prenom = array('val' => '', 'modified' => FALSE, 'bdd' => 'prenom');
/** * Age de la personne */ var $age = array('val' => 0, 'modified' => FALSE, 'bdd' => 'age');
/** * Constructeur * * @access public * @params pUid UID demandé de la personne * @return void */ function Personne($pUid = NULL) { if(!register_shutdown_function(array(&$this, '_Personne'))) { die('Impossible de créer un déstructeur.'); } if($this->pUid) { //$this->uid = $pUid; $this->_change($this->uid, $pUid); } $this->_load(); }
/** * charge les données de la personne si on précise son iud * @access private * @param void * @return void */ function _load() { if(!$this->uid['val']) { $this->_change($this->nom, ''); $this->_change($this->prenom, ''); $this->_change($this->age, 0); return; } $var = get_object_vars(&$this); $selects = array(); while(list(, $v) = each($var)) { if(is_array($v) && array_key_exists('val', $v)) { $selects[] = $v['val']; } } reset($var); $select = implode(',', $selects); $query = 'SELECT '.$select.' FROM personnes WHERE uid='.$this->uid['val']; $res = mysql_query($query); $ret = mysql_fetch_row($res); $i = 0; // on réatribue la liste des variables while(list($c, ) = each($var)) { // update: c'est good :) $this->$c = $ret$i; $i++; } // les attributs ont été mis-à-jour }
/** * Destructeur "emulé" * @params void * @return void */ function _Personne() { $var = get_object_vars(&$this); $toUpdate = array(); while(list(, $v) = each($var)) { if(is_array($v) && array_key_exists('val', $v) && $v['modified'] == TRUE) { $toUpdate[] = $v['bdd'].'="'.$v['val'].'"'; } } reset($var); if(count($toUpdate) == 0) { // aucune mise-à-jour à faire, on quitte return; } $query = 'UPDATE personnes SET '.implode(',', $toUpdate); mysql_query($query); }
/** * Méthode de modification des attributs. En PHP5, on pourrai se passer d'appeler directement $this->_change, en utilisant la methodes magiques __set(), et ensuite appeler $this->_change. On pourrai alors se passer de tous les setPrenom function _change(&$pVal, $pNew) { $pVal['val'] = $pNew; $pVal['modified'] = TRUE; }
/** Changemennt du nom de la personne * @access public * @param pNouveauNom le nouveau nom de la personne * @return void */ function changeNom($pNouveauNom) { $this->_change($this->nom, $pNouveauNom); }
function changePrenom($pNouveauPrenom) { $this->_change($this->prenom, $pNouveauPrenom); }
function changeAge($pNouvelAge) { $this->_chang($this->age, $pNouvelAge); }
}; ?>

Avec tout ceci, on ne génére qu'une seule requete, celle de chargement, et une autre ( si besoin ) qui remet les attributs à jour. Certes dans ce cas, avec une classe aussi petite, on ne peut, peut-être pas mesurer le gain réel par rapport à la première méthode, mais si on éxagere les proportions, comme par exemple une vingtaines d'attributs, ne générer que 2 requétes est bien plus avantageux.

On pourrai être critique sur le temps passé à vérifier les attributs, aux boucles de créations des requétes, et il y a surement à faire pour les optimiser encore plus, mais je pense que ces temps sont bien inférieur à ceux passé à faire tous les mysql_query('UPDATE ...') pour beaucoup d'attributs.

Cette méthode est encore en pleine amélioration, et j'ai trouvé quelques astuces de plus en écrivant ce billet. J'ai également vu un article passer en anglais, sur cette méthode, mais en plus généraliste il me semble. Si quelqu'un posséde encore l'url ... ;)

Edit: Ajour d'un petit bout de code qu'il manquais dans la réattribution des valeurs des attributs dans Personne::_load()

Haut de page