Voir le source du modèle de classe PHP 5, sous licence CC by-sa.
Ce modèle est à compléter par une petite librairie de fonctions usuelles (la création de la connexion à la base de donnée via PDO, SecureIt(), ...)
Inspiration
Les sources d'inspiration de ce modèle de classe sont :
Moins rigoureux mais plus léger qu'un MVC complet
Je cherchais initialement un framework basé sur le Design Pattern MVC, mais j'ai fini par renoncer à php.MVC et aux autres frameworks php dédiés à MVC car le modèle MVC me semblait trop lourd à metrre en place et avec un temps d'apprentissage un peu long pour des projets plutôt modestes (moins de 15 jours de développement). Je cherchais néanmoins à rester dans une logique de couplage faible entre les objets métier et la base de donnée, et dans l'optique d'utiliser des design patterns, notamment le Singleton.
Pour l'accès aux données, je me suis alors tourné vers PDO plutôt que PEAR, à cause de la relative lenteur de ce dernier. PDO est une interface commune d'accès aux bases de données plutôt qu'une une véritable couche d'abstraction pour l'accès aux bases de données ; elle est livrée en standard depuis PHP 5.1 (PDO, c'est aussi un peu le buzz du moment). Cela permet de limiter le modèle n-tiers MVC à deux couches au lieu de trois : Le View et le Controler ; tandis que la couche Model et son accès à la bdd est entièrement gérée par PDO. On garde donc bien une sépartion entre les traitements métiers et la couche de présentation, mais sans s'encombrer d'une couche d'abstraction de la bdd.
Trop de n-tiers tue le n-tiers 
(Un peu facile, ce titre...) De fait, implémenter l'accès aux donnée dès le constructeur d'un objet métier peut laisser songeur, surtout si l'on est un fervent défenseur des modèles n-tiers strictement respectés. Mais la démarche du modèle présenté ici se veut pragmatique. D'expérience, développer des modèles n-tiers avec des couches rigoureusement séparées apparaît un peu factice dans des projets qui restent simples, qui ne manipulent que des données hexadécimales (et non des objets cartographiques, par exemple). Utiliser l'Orienté Objet et le design Pattern MVC pour interfacer des bases de données communes ne devrait pas systématiquement conduire à une inflation du nombre de lignes de code[1], or c'est souvent le cas. Contruire une couche entière d'abstraction stricte de la base de donnée (entre l'objet et le SGBD), est un processus très "esthétique" sur le papier, mais qui oblige à multiplier les fichiers et les classes, et ne se justifie que pour des projets ambitieux et exposés à de fortes évolutions. Ici, l'utilisation de PDO comme interface commune à plusieurs SGBD permet de maintenir un couplage faible entre l'objet et la base de donnée elle-même. Il est donc possible, avec un tel modèle, de prévoir des migrations de base de donnée (cf. la liste des bdd supportées par PDO). On devra cependant veiller à utiliser des requêtes SQL les plus standard possibles. Si donc l'appel au SGBD se fait dès la couche métier, il se veut limité et maîtrisé : Il ne se fait en fait qu'à trois endroits : dans le constructeur même de l'objet et dans deux les méthodes d'enregistrement (Record()) et de suppression (Drop()) de l'objet.
Constructeur quadrimorphe
Le constructeur de ce modèle permet de gérer rapidement les CRUD ("CREATE, READ, UPDATE, DELETE"), ces 4 cas les plus courants lorsque l'on construit une application interfaçant une base de donnée. Dans ce constructeur, on prend en compte une propriété particulière de l'objet : son identifiant unique tel qu'il existe dans la base de donnée : $id. Les autres données, qu'elles soient connues ou non, sont stockées sous forme de tableau : $tab. L'usage d'un identifiant unique, le fait qu'on le connaisse déjà ou pas, correspond rapidement à ces 4 cas de CRUD :
- Cas 1 : On ne connaît ni l'identifiant ni les autres propriétés de l'objet.
Ce cas correspond à la création d'un objet vide, lorsque l'on ne connaît aucune de ses propriétés.
- Cas 2 : On ne connaît pas l'identifiant de l'objet mais on connaît toutes ses autres propriétés stockées dans
$tab. Ce cas correspond à la création de l'objet à partir d'informations non encore enregistrées dans la base de données. Typiquement, des informations soumises par un utilisateur via un formulaire.
- Cas 3 : On ne connaît que l'identifiant de l'objet
$id, mais aucune autre propriété de l'objet. Typiquement, ce cas correspond à la recherche ou à la suppression des propriétés d'un objet dans une base de données, à partir de son identifiant $id.
- Cas 4 : On connaît tout l'objet, constitué de son identifiant
$id et de toutes ses autres propriétés stockées dans $tab. Typiquement, ce cas correspond à la mise à jour des propriétés d'un objet stockées en base de donnée.
Cela donne :
private function __construct($id,$tab) {
// CASE 1 : No $id and no $tab => CREATE - step 1/2
global $bdd;
if (!$id AND sizeOf($tab)==0) {
$this->member_id = "";
$this->member_name = "";
// etc.
}
// CASE 2 : No $id but existing $tab => CREATE - step 2/2
if (!$id AND sizeOf($tab)!=0) {
$this->member_id = (int)secureIt($tab[['member_id']]);
$this->member_name = (string)secureIt($tab[['member_name']]);
// etc.
}
// CASE 3 : Existing $id but no $tab => READ
if ($id AND sizeOf($tab)==0) {
$sql = "SELECT * from member WHERE member_id = " . $id;
$result = $bdd->query($sql, PDO::FETCH_OBJ);
$line = $result->fetch();
$this->member_id = (int)$line->member_id;
$this->member_name = (string)$line->member_name;
// etc.
}
// CASE 4 : Existing $id and existing $tab => UPDATE
if ($id AND sizeOf($tab)!=0) {
$this->member_id = (int)$id;
$this->member_name = (string)secureIt($tab[['member_name']]);
// etc.
}
}
L'intérêt de ce constructeur à 4 formes est qu'il correspond bien aux 4 cas les plus fréquents dans la manipulation d'un objet issu d'une base de données. Il permet d'effectuer rapidement des opérations basiques sur ce type d'objet : La création, la lecture, la mise à jour et la suppression d'un objet peuvent se faire dès son instanciation ou presque.
Record() pour l'insertion/modificatilon, Drop() pour la suppression
Deux méthodes complètent la classe : Record() et Drop().
Record() permet d'insérer un objet ou de le mettre à jour, selon que l'on connaît déjà ou pas son identifiant dans la base de données. Drop() permet de supprimer l'objet dès que l'on connaît son identifiant.
Conclusion
J'ignore si ce modèle de constructeur quadrimorphe existe déjà quelque part et s'il est (? peut-être) déjà référencé comme un design pattern. Quoi qu'il en soit, je ne le présente pas comme tel. Il n'est pas de moi, mais m'a été transmis (cf. mes sources). Je l'ai développé en-dehors de mes heures de boulot, c'est pourquoi je prends la liberté de le présente ici et de lui donner une licence. Le modèle de classe proposé ici n'est qu'une première version, perfectible, qui croise des bonnes pratiques distribuées par d'autres avant moi. Je ne fais que les réunir en un seul modèle. N'hésitez pas à réagir pour l'améliorer.