Vos recrutements informatiques

700 000 développeurs, chefs de projets, ingénieurs, informaticiens...

Contactez notre équipe spécialiste en recrutement

ORM or not ORM faut-il les utiliser ?

Le , par imikado, Rédacteur
Avec l’arrivée des frameworks php, nous avons appris à évoluer entre ces nouveaux acronymes: MVC, DAO, DRY et ORM
Mais qu’est-ce qu’un ORM ?
Un ORM (Object Relation Mapping) est un ensemble de librairies qui vous permettent d’interagir avec vos données via des objets.
La plupart des frameworks respectant le design MVC* intègrent leurs propres ORM plus ou moins performants.
note: pour les exemples de syntaxes d’ORM j’utiliserai l’ORM du mkFramework.

*MVC: Model View Controller (séparation de la couche modèle de l’affichage et du contrôleur)

Un ORM pourquoi faire ?
Pourquoi utiliser un ORM plutôt que d’écrire simplement des requêtes SQL dans pdo ?
Si vous voulez coder propre, vous allez dans un premier temps écrire toutes vos requêtes dans un fichier php.
Si vous souhaitez organiser un peu mieux, vous allez regrouper les requêtes par table…
Si vous souhaitez éviter de vous répéter, vous aller créer un fichier/une classe pour les éléments récurrents (connexion, requête…) : principe de DRY*
Le fait de faire ceci, vous avez créé un pseudo début d’ORM

*DRY Don’t Repeat Yourself (créer des fonctions/classes pour éviter d’écrire plusieurs fois le même code)

Un des avantages des ORM se situe concernant la manipulation des données.
Prenons un exemple ou dans plusieurs pages vous devez modifier uniquement le titre, l’auteur ou le nombre de vues d’un article.
En pdo simple vous écririez:
Code : Sélectionner tout
1
2
3
‘UPDATE article SET titre=? WHERE id=?’,$titre,$îd 
‘UPDATE article SET auteur=? WHERE id=?’,$auteur,$îd 
‘UPDATE article SET nbVue=nbVue+1 WHERE id=?’,$îd
Avec l’ORM vous n’avez pas besoin d’écrire à l’avance vos différents types de requêtes
Code php : Sélectionner tout
1
2
3
4
5
6
7
8
9
$oArticle=model_Article::getInstance()->findById($id); 
$oArticle->titre=$titre; 
$oArticle->save(); 
//ou 
$oArticle->auteur=$auteur; 
$oArticle->save(); 
//ou 
$oArticle->nbVue=($oArticle->nbVue+1); 
$oArticle->save();
note: vous pouvez, si vous le souhaitez écrire des requêtes update
Exemple:
Code php : Sélectionner tout
1
2
3
4
5
6
class model_article extends abstract_model{ 
() 
public function updateNbVue($id){ 
$this->execute(‘UPDATE article SET nbVue=nbVue+1 WHERE id=?’,$id); 
} 
}
Les avantages sont multiples:
  • Organiser vos requêtes
  • Gagner du temps à l’écriture
  • Bénéficier des avantages du design MVC*


Tous les ORMs n’offrent pas les mêmes performances
Pour argumenter un post dans un topic sur ce même site, j’avais à l’époque procédé au benchmark suivant:
100 000 enregistrements en base de données mysql, et l’affichage de ces lignes dans un tableau HTML.
5 types différents:
- PHP simple en utilisant pdo
- une application en Zend Framework (version 1.11.12)
- une application en Yii (version yii-1.1.14)
- une application en MkFramework (méthode findMany() )
- une application en MkFramework en utilisant la récupération rapide (méthode findManySimple() )

Résultat des courses:
- 0.50s : pdo simple
- 0.58s : MkFramework en utilisant la récupération rapide
- 0.68s : Yii (version yii-1.1.14)
- 1.97s : MkFramework (normal)
- 11.20s : Zend Framework (version 1.11.12):

note : j’ai fait un benchmark sur 100 000 entrées pour avoir un volume assez important pour mettre en évidence les différences de performances, si j’avais fait un findById(4)
Sur un seul enregistrement on a
- 0.007s : pdo simple
- 0.015s : MkFramework en utilisant la récupération rapide
- 0.043s : MkFramework (normal)
- 0.049s : Zend Framework

note 2 pour obtenir de bonnes performances avez yii j’ai écrit les requêtes sans passer par les classes « ORM »
Code php : Sélectionner tout
1
2
3
4
$sql=’SELECT * FROM Article’; 
$connection=Yii::app()->db; // vous devez avoir une connection « db » 
$command=$connection->createCommand($sql); 
$articles=$rows=$command->queryAll();
note 2: j’ai fait un benchmark sur 100 000 entrées pour avoir un volume assez important pour mettre en évidence les différences de performances, si j’avais fait un findById(4)

Les ORMs offrent de la flexibilité
J’ai lu à plusieurs reprise que les ORM étaint lourd, qu’ils retournaient toujours toutes les colonnes même ceux non utilisées, que pour faire une jointure on se retrouvait à récupérer une ligne entière sur les deux tables liés…
Premièrement, vous pouvez choisir de récupérer uniquement certains champs de votre table. Beaucoup d’ORM proposent d’écrire sa requête SQL
Par exemple:

Code php : Sélectionner tout
1
2
3
4
5
6
class model_article extends abstract_model{ 
() 
public function findListAuteurName(){ 
return $this->findMany(‘SELECT auteur.name FROM auteur’); 
} 
}

Idem pour les fameuses jointures, vous avez le choix de récupérer par exemple un objet article puis de lui demander de nous retourner son auteur pour afficher son nom ainsi

Code php : Sélectionner tout
1
2
$oArticle=model_article::getInstance()->findById(4); 
print $oArticle->findAuteur()->nom;

Mais vous pouvez également écrire une méthode qui vous retournera les articles avec leur auteurs

Code php : Sélectionner tout
1
2
3
4
5
6
class model_article extends abstract_model{ 
() 
public function findArticleWithAuteur(){ 
return $this->findOne(‘SELECT article.titre, auteur.name FROM article INNER JOIN auteur ON article.auteur_id=auteur.id WHERE article.id=?’,(int)$id); 
} 
}

Une flexibilité aussi concernant les performances: selon les parties de vos sites vous avez besoin de récupérer plus ou moins d’élements et ceci plus ou moins rapidement.
La encore vous avez le choix: si vous devez récupérer un auteur, ses articles et ceci sans trop regarder les performances vous pouvez utiliser la méthode de récupération d’objets riches findMany/findOne
Si au contraire vous souhaitez afficher un bon nombre d’article d’auteurs, ou autre, vous pouvez utiliser la méthode rapide retournant des objets « simple » findManySimple/findOneSimple

Vous avez le choix

Conclusion
Comme vous avez pu le lire ici, tous les ORM ne sont pas des usines à gaz, ils sont très pratiques et vous permettent dans l’esprit du MVC de bien organiser votre couche modèle.
Les ORM sont une bonne chose que vous codiez une petite application ou une application importante: l’essentiel c’est de penser à long terme (la maintenabilité).
Il sera plus simple pour vous ou un collègue d’intervenir sur votre application avec ORM que sans ORM
Certains ORM sont plus ou moins flexible et performants comme les frameworks, mais ne les mettez pas tous dans le même panier, essayez en plusieurs avant de vous faire un avis.

note: je n’ai pas inclus Symfony 2 (pour tester doctrine) dans le benchmark, car malgré mes efforts d’optimisation de cache, mode production… je n’arrivais pas à passer sous la barre des 23 secondes
Si vous pouvez m’envoyer un exemple d’application affichant ces 100 000 enregistrements en moins de 2 secondes je suis preneur

note2: retrouvez dans mon post d’origine les éléments du benchmark (sauf celui de yii fait ici pour l’occasion)
http://www.developpez.net/forums/d12...u/#post6847792

Le billet: http://blog.developpez.com/ducodeetd...l-les-utiliser

Ci-joint les fichiers logs complets (réalisé via la commande time et wget 10 fois de suite)

Et vous ? utilisez vous un ORM si oui lequel ?
Si non qu'utilisez vous ? pdo ? odbc ? fonction spéciale (mysql_connect, myqli,oci_connect...)


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :


 Poster une réponse

Avatar de papajoker papajoker - Membre émérite http://www.developpez.com
le 15/09/2013 à 13:52
je te parle de exclusivement de :

Code : Sélectionner tout
1
2
3
4
5
class model_article extends abstract_model{ 
 public function updateNbVueById($id){ 
  $this->execute('UPDATE article SET nbVue=nvVue+1 WHERE id=?',$id); 
 } 
}
ca fait 3..4 post (je radote ), j'insiste plus, sujet clos pour moi.

1 de mes post :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
$oArticle=model_Article::getInstance()->findById($id); 
$oArticle->titre=$titre; 
$oArticle->save(); 
//ou 
$oArticle->nbVue=($oArticle->nbVue+1); 
$oArticle->save(); 
  
$oArticle->updateNbVue($id); 
echo $oArticle->nbVue ; // !!!!!!!!!!!! FAUX 
$oArticle->save();          // !!!!!!!!!!
Avatar de imikado imikado - Rédacteur http://www.developpez.com
le 15/09/2013 à 13:57
Citation Envoyé par papajoker  Voir le message
je te parle de exclusivement de :

ca fait 3..4 post (je radote ), j'insiste plus, sujet clos pour moi.

1 de mes post :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
$oArticle=model_Article::getInstance()->findById($id); 
$oArticle->titre=$titre; 
$oArticle->save(); 
//ou 
$oArticle->nbVue=($oArticle->nbVue+1); 
$oArticle->save(); 
  
$oArticle->updateNbVue($id); 
echo $oArticle->nbVue ; // !!!!!!!!!!!! FAUX 
$oArticle->save();          // !!!!!!!!!!

Effectivement on ne peut pas afficher nbVue sur la derniere ligne :il lui manquerait 1 (vu qu'il est incrementé avant)
et le save() ne sert à rien

C'est plutot:
Code php : Sélectionner tout
1
2
3
4
  
model_article::getInstance()->updateNbVue($id); 
//ou 
$oArticle->updateNbVue();
Je m'excuse si c'est cette ligne en erreur que vous souligniez
Avatar de papajoker papajoker - Membre émérite http://www.developpez.com
le 15/09/2013 à 14:25
non

Code : Sélectionner tout
1
2
3
4
5
6
7
class model_article extends abstract_model{ 
(…) 
public function updateNbVue(){ 
$this->nbVue = $this->nbVue+1; 
$this->execute(‘UPDATE article SET nbVue=? WHERE id=?’,$this->nbVue,$this->id); // logiquement this->save() :) 
} 
}
je t'ai deja envoyé ce code !

dans ton code tu ne met PAS a jour $this->nbVue(et $this->id) LA EST L'ERREUR

tout ton modele est désynchronisé (this->nbVue est faux) de la bd

ca marche UNIQUEMENT si plus aucun traitement avec ton modele mais n'importe quel traitement sur ton modele conduit a la cata, merci l'orm.
Mais bon, la tu as une méthode exclusive qui me détruit mon objet et toute ma logique orm !
Avatar de imikado imikado - Rédacteur http://www.developpez.com
le 15/09/2013 à 14:53
L'erreur est en amont: je me suis trompé dans l'exemple entre model et row ("factory" et "element")

Soit on met la methode dans le factory
Code php : Sélectionner tout
1
2
3
4
5
6
7
<?php 
class model_article extends abstract_model{ 
 public function updateVueById($id){ 
   $this->execute('UPDATE article SET nbVue=nbVue+1 WHERE id=?',(int)$id); 
 } 
  
}
Et on peut l'appeler
Code php : Sélectionner tout
1
2
<?php 
model_article::getInstance()->updateVueById(4);


Soit on crée une methode sur l'objet appelant le factory
Code php : Sélectionner tout
1
2
3
4
5
6
<?php 
class row_article extends abstact_row{ 
 public methode updateVue(){ 
   model_article::getInstance()->updateVueById($this->id); 
 } 
}
Appelé ainsi
Code php : Sélectionner tout
1
2
3
4
<?php 
$oArticle=model_article::getInstance()->findById(4); 
//la methode retourne un objet de classe row_article 
$oArticle->updateVue();


Soit on manipule l'objet directement
Code php : Sélectionner tout
1
2
3
4
<?php 
$oArticle=model_article::getInstance()->findById(4); 
$oArticle->nbVue=($oArticle->nbVue+1); 
$oArticle->save();
Avatar de papajoker papajoker - Membre émérite http://www.developpez.com
le 15/09/2013 à 15:49
c'est ton framework, il n'y a que toi qui comprend

mais quand tu me mets en exemples a la fin :
Code : Sélectionner tout
$oArticle=model_article::getInstance()->findById(4);
//la methode retourne un objet de classe row_article
suis perdu row_article::getInstance()->findById(4); comprendrait mieux

Mais tu as peux être un problème(avec moi) de conception dans tes modèles :

il est surtout impossible avec un model de modifier la base sans toucher aux propriétés du modele.

exemple
Code : Sélectionner tout
1
2
3
4
5
6
7
8
 class Article extends Model{ 
   function increment($nb){ 
       $this->nombre+=$nb; 
   } 
   function inc($nb){ 
       $this->execute('UPDATE article SET nombre=nombre+? WHERE id='.$this->id,(int)$nb); 
    } 
 }
que vais je utiliser ?
inc() puis save() j'ai tout faut !!!
inc() seul OK
increment() seul FAUT !

Donc pour moi :
Avoir ce type de choix avec un model est une hérésie, j'ai une petite chance de m'en sortir uniquement si je ne quitte pas des yeux le code source de ma classe

par contre Model::getDB()->inc() la oui, je n'utilise pas le model mais la db donc c'est plus "normal" que je ne manipule pas les propriétés
Avatar de imikado imikado - Rédacteur http://www.developpez.com
le 15/09/2013 à 21:15
Citation Envoyé par papajoker  Voir le message
mais quand tu me mets en exemples a la fin :
$oArticle=model_article::getInstance()->findById(4);
//la methode retourne un objet de classe row_article
suis perdu row_article::getInstance()->findById(4); comprendrait mieux

Oui mais non: on ne peut pas faire
Code : Sélectionner tout
row_article::getInstance()->findById(4)
J'explique : d'un coté on a la classe "factory" (model_) d'une table, par exemple article qui va contenir toutes les requetes findAll(), findListByCategorie() et autres methodes pour récuperer ses enregistrements

Ces méthodes retourne des tableaux d'objets "row" (row_) qui peuvent etre manipulée : on peut modifier des champs puis appeler la méthode save(), et ensuite libre à vous de rajouter des méthodes pour interagir, récupérer un objet d'une autre table...

Pour la partie "factory" / model_, vous ecrivez vous memes les différents methodes retournant les éléments, vous êtes donc au courant du fonctionnement de l'ORM pour votre projet

note: ce fonctionnement n'est pas exclusif à mon framework, ni à mon ORM vous pouvez faire de même sur les autres ORM (Zend framework, yii...)
Avatar de papajoker papajoker - Membre émérite http://www.developpez.com
le 15/09/2013 à 22:36
Citation Envoyé par imikado  Voir le message
note: ce fonctionnement n'est pas exclusif à mon framework, ni à mon ORM vous pouvez faire de même sur les autres ORM (Zend framework, yii...)

tout à fait, je ne remet aucunement en cause ton Framework (ni aucun orm).
Avatar de TiranusKBX TiranusKBX - Membre expert http://www.developpez.com
le 19/09/2013 à 18:30
Personnellement j'ai écris une surcouche pour PDO ayant un comportement proche car les retour avec PDO ne me convenait pas

De ce fait mes requêtes maintenant me retourne directement des tableaux de données et plus des objets PDO quelque-peut embêtant à traiter.
De plus j'ai ainsi un traitement des erreurs simplifié, que du bénéfice !

Comme ma surcouche n'est pas lourde ça pompe très peut de ressource(à peine plus que PDO seul)
Avatar de zapkto zapkto - Candidat au Club http://www.developpez.com
le 19/09/2013 à 22:39
Pourquoi n'avez-vous pas inclus l'orm de django dans vos tests ?
Avatar de imikado imikado - Rédacteur http://www.developpez.com
le 20/09/2013 à 6:50
@zapkto je le répète ceci n'est pas un article benchmark, j'écrirais dès que possible un benchmark complet, parcontre je ne suis pas sur d'inclure django(python) ni ROR(ruby), ni hibernate(java) ni EntityFramework (.net) car je ne connais pas bien (pour ne pas dire du tout) ces langages

Après ce que je peux faire pour ce prochain article c'est ouvrir un topic pour demander aux developpeurs (vous) de me proposer les cas de benchmark manquant
Avatar de Aurélien LEQUOY Aurélien LEQUOY - Membre habitué http://www.developpez.com
le 20/09/2013 à 15:51
ouai alors codé avec un ORM je ne vois pas comment cela peut etre plus rentable. Sachant que d'abord il faut former tout le monde ! Pour ma pars je fais beaucoup d'exotique et de query à la con alors le select simpliste est rarement ma tasse de thé. de plus faire des truc exotique avec un ORM ca demande une sérieuse d'apprentissage et c'est pas vraiment performant.

Après je ne vois pas comment du code ORM peut-être plus maintenant que du code SQL. sachant que le code SQL est quand même plus pérenne que du code ORM qui aura évolué entre les versions.

En principe c'est là qu'on me sors, oui mais tu peux changer de SGBD facilement !

Bin pour un client "grand compte" j'ai du passer de MySQL vers Oracle et cela à nécessité une semaine, certainement autant de temps que de refaire les quelques requêtes qui posaient problème... (c'était sous cakephp).

En parlant des perfs on oublie ...

Perso j'utilise juste une surcouche pour update / delete / insert (de faire en sorte que cela valide le modèle). plus une optionnel pour historiser les tables automatiquement si elles sont dans la liste des tables a historiser.
Offres d'emploi IT
Développeur web full stack h/f
Jeeliz - Ile de France - Palaiseau (91120)
Développeur web front end / intégrateur h/f
CRESCENDO VAISE - Rhône Alpes - Lyon (69000)
Web developpeur
Allegorithmic - Auvergne - Clermont-Ferrand (63000)

Voir plus d'offres Voir la carte des offres IT
Responsable bénévole de la rubrique Développement Web : Xavier Lecomte -