L'ORM maison: la couche modèle du framework

Au menu

Présentation
La configuration
Ecrire ses requêtes
Faire une jointure
Modifier un enregistrement
Ajouter un enregistrement
Supprimer un enregistrement
Automatiser un retraitement sur des enregistrements


Présentation

L'ORM maison est inspiré de ce que j'ai pu voir: un fichier par table situé dans le répertoire modèle
Un exemple de fichier:
Il est basé sur les points suivants:
- performance
- transparence
- ergonomie


  
<?php
class model_article extends abstract_model{
  
   
protected $sClassRow='row_article';
  
   
protected $sTable='article';
   protected $sConfig='xml';
  
   
protected $tId=array('id');

   public static function getInstance(){
       return self::_getInstance(__CLASS__);
   }

   public function findById($id){
       return $this->findOne('SELECT * FROM '.$this->sTable.' WHERE id=?',$id );
   }
   public function findAll(){
       return $this->findMany('SELECT * FROM '.$this->sTable.' ORDER BY id ASC');
   }
  
}

class 
row_article extends abstract_row{
  
   
protected $sClassModel='model_article';
  
   
public function findAuteur(){
       return model_auteur::getInstance()->findById($this->auteur_id);
   }
  
   
//permet de verifier la coherence de l'enregistrement
   private function getCheck(){
       $oPluginValid=new plugin_valid($this->getTab());
       /* renseigner vos check ici
       $oPluginValid->isEqual('champ','valeurB','Le champ doit etre egal a valeurB');
       $oPluginValid->isNotEqual('champ','valeurB','Le champ ne doit pas etre egal a valeurB');
       $oPluginValid->isUpperThan('champ','valeurB','Le champ doit etre superieur a valeurB');
       $oPluginValid->isUpperOrEqualThan('champ','valeurB','Le champ doit etre superieur ou egal a valeurB');
       $oPluginValid->isLowerThan('champ','valeurB','Le champ doit etre inferieur a valeurB');
       $oPluginValid->isLowerOrEqualThan('champ','valeurB','Le champ doit etre inferieur ou egal a valeurB');
       $oPluginValid->isEmpty('champ','Le champ doit etre egal vide');
       $oPluginValid->isNotEmpty('champ','Le champ ne doit pas etre vide');
       $oPluginValid->isEmailValid('champ','Le champ n est pas un email valide');
       $oPluginValid->matchExpression('champ','/[0-9]/','Le champ doit etre correspondre a une expression');
       $oPluginValid->notMatchExpression('champ','/[a-zA-Z]/','Le champ ne doit etre correspondre a une expression');
       */

       return $oPluginValid;
   }

   public function isValid(){
       return $this->getCheck()->isValid();
   }
   public function getListError(){
       return $this->getCheck()->getListError();
   }
   public function save(){
       if(!$this->isValid()){ //verifie la coherence avant d'enregistrer
           return false;
       }
       parent::save();
       return true;
   }
  
}
?>
   

Chaque fichier ne contient que très peu de lignes:

protected $sClassRow

Le nom de la classe row du model les requêtes retournant des objets instanciant celle-ci

protected $sTable

Le nom réel de la table en base de donnée

protected $sConfig

L'identifiant de connexion utilisé (paramétré dans conf/connexion.ini.php)

protected $tId

Tableau contenant le/les clé primaire(s)


La configuration

Introduction

La majorité des profils de connexion utilisent le module pdo de php.

Exemple PDO (mysql,sqlserver,oracle,sqlite...)

Les valeurs du fichier de configuration servent à créer l'intance de pdo


  
//idee de la creation de l'objet pdo
new PDO(
   MON_PROFIL.dsn,
   MON_PROFIL.username,
   MON_PROFIL.password
);
    


Comprenez ici: que dans votre classe modèle, vous avez une variable $sConfig qui permet d'intancier un objet pdo avec le profil choisi.

Par exemple, pour se connecter à une base mysql

  
expleMysqlPdo
.dsn="mysql:dbname=maBase;host=localhost"
expleMysqlPdo.sgbd=pdo_mysql
expleMysqlPdo
.username=root
expleMysqlPdo
.password=monMotDePasse
   



qui créera une instanciation aussi simple que:

  
//idee de la creation de l'objet pdo
new PDO(
   "mysql:dbname=maBase;host=localhost"//dsn
   'root'//user
   'monMotDePasse' //password
);
    



Note pour sqlite

Utilisez l'adresse absolu de votre base sqlite et non relative
Par exemple:

  
expleSqlite
.dsn="sqlite:/var/www/maBase.sqlite"
expleSqlite.sgbd=pdo_sqlite
expleSqlite
.username=root
expleSqlite
.password=root
   




Son fonctionnement

Le commencement

Soit une table article et une table auteur liées par article.auteur_id = auteur.id
Apres la génération de notre couche modèle via le mkbuilder
On obtient deux fichiers dans le repertoire "model": model_article.php et model_auteur.php
Chaque fichier contient deux classes une commençant par "row_" (active record) et une autre commençant par "model_" (factory). C'est à dire que lorsque vous voulez effectuer un "select * from tableArticle"
Vous allez utiliser la class "model_article", et elle va retourner un tableau d'objets "row_article".


  
$tArticle
=model_article::getInstance()->findAll();
    


Quand je dis que chaque methode retourne un tableau d'objet row_article, cela veut dire que toutes les methodes que vous ajouterez dans la classe row_article seront accessibles à partir des enregistrements recupérés.

Pour l'exemple: vous voulez faire une jointure sur la table "auteur" pour récuperer son nom
Ajouter dans model_article.php

  
public function findAuteur(){
   return model_auteur::getInstance()->findOne($this->auteur_id);
   //on retourne l'enregistrement "auteur" recupere via la cle etrangere "auteur_id"
}
    


Et aussi simplement que possible vous allez afficher le nom de l'auteur ainsi

  
$tArticle
=model_article::getInstance()->findAll();
foreach(
$tArticle as $oArticle){
   echo $oArticle->findAuteur()->nom;
}
    



Voici une présentation des méthodes déjà implémentées pour chaque enregistrement via l'ORM

public function findOne($requete,$tParam)

retourne un objet en effectuant la requete $requete avec les parametres $tParam
exemple :

  
findOne
('SELECT * FROM article WHERE id=?',4);
    


vous pouvez également écrire

  
findOne
('SELECT * FROM article WHERE id=?',array(4) );
    


ou encore (via driver pdo uniquement)

  
findOne
('SELECT * FROM article WHERE id=:id',array('id'=>4) );
    



public function findOneSimple($requete,$tParam)

retourne un objet "simple" en effectuant la requete $requete avec les parametres $tParam
exemple :

  
findOneSimple
('SELECT * FROM article WHERE id=?',4);
    


vous pouvez également écrire

  
findOneSimple
('SELECT * FROM article WHERE id=?',array(4) );
    


ou encore (via driver pdo uniquement)

  
findOneSimple
('SELECT * FROM article WHERE id=:id',array('id'=>4) );
    


note: utilisez cette méthode pour de l'affichage rapide

public function findMany($requete,$tParam)

retourne un tableau d' objets en effectuant la requete $requete avec les parametres $tParam
exemple :

  
findMany
('SELECT * FROM article WHERE cat_id=?',2);
    


vous pouvez également écrire

  
findMany
('SELECT * FROM article WHERE cat_id=?',array(2) );
    


ou encore (via driver pdo uniquement)

  
findMany
('SELECT * FROM article WHERE cat_id=:cat_id',array('cat_id'=>2) );
    



public function findManySimple($requete,$tParam)

retourne un tableau d' objets "simple" en effectuant la requete $requete avec les parametres $tParam
exemple :

  
findManySimple
('SELECT * FROM article WHERE cat_id=?',2);
    


vous pouvez également écrire

  
findManySimple
('SELECT * FROM article WHERE cat_id=?',array(2) );
    


ou encore (via driver pdo uniquement)

  
findManySimple
('SELECT * FROM article WHERE cat_id=:cat_id',array('cat_id'=>2) );
    



note: utilisez cette méthode pour de l'affichage rapide

public function execute($requete,$tParam)

Execute la requete $requete avec les parametres $tParam
exemple :

  
execute
('DELETE FROM article WHERE id=?',2);
    


vous pouvez également écrire

  
execute
('DELETE FROM article WHERE id=?',array(2) );
    


ou encore (via driver pdo uniquement)

  
execute
('DELETE FROM article WHERE id=:id',array('id'=>2) );
    




Ecrire ses requêtes

Si vous avez des elements variables, par exemple la requête pour selectionner les articles de tel ou tel auteur
Selection d'article par auteur

  
public function findByAuteur($auteur_id){
   return $this->findOne('SELECT * FROM auteur where auteur_id=?',(int)$auteur_id);
}
    


vous pouvez également écrire

  
public function findByAuteur($auteur_id){
   return $this->findOne('SELECT * FROM auteur where auteur_id=?',array($auteur_id) );
}
    


ou encore (via driver pdo uniquement)

  
public function findByAuteur($auteur_id){
   return $this->findOne('SELECT * FROM auteur where auteur_id=:id',array('id'=>$iAuteur_id) );
}
    



Dans le cas de plusieurs arguments:

  
public function findByAuteur($auteur_id){
   $state=1;//etat actif

   return $this->findOne('SELECT * FROM auteur where auteur_id=? AND state=?',(int)$auteur_id,(int)$state);
   //ou
   return $this->findOne('SELECT * FROM auteur where auteur_id=? AND state=?',array( (int)$auteur_id,(int)$state ));
   //ou (via driver pdo uniquement)
   return $this->findOne('SELECT * FROM auteur where auteur_id=:auteur AND state=:state',array( 'auteur'=>$auteur_id,'state'=>$state ));

}
    




Faire une jointure

Par exemple une jointure livre/auteur
Ici deux méthodes
- faire une méthode dans l'objet row pour recuperer un enregistrement extérieur

  
class row_article extends abstract_row{
   (...)
   public function findAuteur(){
       return model_auteur::getInstance()->findById($this->auteur_id);
   }
}
    


- faire simplement la jointure dans la requête SQL

  
class model_article extends abstract_model{
   (...)
   public function findById($id){
       return $this->findOne('SELECT article.*,auteur.nom
                                   FROM article
                                       INNER JOIN auteur ON article.auteur_id=auteur.id
                                   WHERE article.id=?'
,$id);
   }
}
    


Vous recuperez ainsi un objet article contenant également le nom de l'auteur

  
$oArticle
=model_article::getIntance()->findById(2);

echo 
$oArticle->titre.' par '.$oArticle->nom;
    




Modifier un enregistrement

Vous souhaitez modifier le titre de l'article qui a l'id 2
Dans le code de votre module:

  
//on recupère l'objet de l'article à modifier
$oArticleToModify=model_Article::getInstance()->findById(2);
//on modifie un de ses champs
$oArticleToModify->titre='Mon nouveau titre';
//on enregistre
$oArticleToModify->save();
    




Ajouter un enregistrement

Vous souhaitez ajouter un article
Dans le code de votre module:

  
//on créé un nouvel article
$oNewArticle= new row_Article;
//on renseigne ses champs
$oNewArticle->titre='Mon nouveau titre';
//on enregistre
$oNewArticle->save();
    




Supprimer un enregistrement

Vous souhaitez supprimer l'article qui a l'id 2
Dans le code de votre module:

  
//on recupère l'objet de l'article à supprimer
$oArticleToDelete=model_Article::getInstance()->findById(2);
//on le supprime
$oArticleToDelete->delete();
    




Automatiser un retraitement sur des enregistrements

Il arrive certaines fois que l'on souhaite reformater une date à la récupération et/ou à l'enregistrement
Par exemple si l'on change le format d'affichage de la date en fonction de la langue ou si l'on utilise un datePicker.

Pour cela, il faut pour modifier les enregistrements lus:
Dans la class row_** de votre table, par exemple ici row_article

  
class row_article extends abstract_row{
  
   
protected $sClassModel='model_article';
   (...)
  
   
//on surcharge le constructeur
   public function __construct($tRow=null){

       //on appel le constructeur normal de la row
       parent::__construct($tRow);

       //on modifie le champ date avant d'initialiser l'objet
       $oDate = new plugin_date($tRow['date'],'d/m/Y');
       $this->date $oDate->toString('Y-m-d');

   }
    



Et pour modifier à la sauvegarde en base de données:

  
class row_article extends abstract_row{
  
   
protected $sClassModel='model_article';
   (...)
  
  
   
public function save(){
       if(!$this->isValid()){
           return false;
       }
      
       
//on formate à la volée
       $oDate = new plugin_date($this->date,'d/m/Y');
       $this->date $oDate->toString('Y-m-d');
      
      
       parent
::save();
       return true;
   }