Dans l’architecture MVC, le modèle représente la partie métier de l’application.
Avec le paradigme orienté objets, le développeur travaille avec des instances de classe ce qui rend le code plus lisible, que de manipuler directement des tableaux associatifs et des enregistrements SQL.
Pour obtenir des objets métiers, à partir d’une bases de données, les deux principales solutions sont le mécanisme du DAO ou celui de l’ORM.
L’ORM désigne un mécanisme logiciel qui assure la correspondance entre le modèle objet d’une application (classes, attributs) et le modèle relationnel d’une base de données (tables, colonnes).
Objectif et intérêt
- Abstraction : le développeur manipule des objets sans écrire de requêtes SQL explicites.
- Productivité : la plupart des opérations CRUD sont générées automatiquement.
- Sécurité : l’ORM prend en charge une part importante de la sécurité contre
les injections SQL en échappant les caractères spéciaux du SQL (
',"et\) grâce aux requêtes préparées, ou à la fonctionPDO::quote; la fonctionaddslashesest déconseillée en raison d’incompatibilités avec certains SGBD.
Limites liées à SQL
- Expressivité : certaines requêtes complexes peuvent nécessiter du SQL natif.
- Performance : l’abstraction peut introduire des requêtes non optimisées.
- Modélisation : le modèle relationnel doit parfois être adapté au modèle objet.
Le paradigme objet diffère de celui du modèle relationnel :
- il n’y a pas de concept d’attribut identifiant dans le modèle objet ;
- les associations entre classes sont représentées par des objets dans le modèle objet et par des clés étrangères dans le modèle relationnel.
Il est donc nécessaire de configurer l’ORM pour effectuer le mappage entre les concepts objets (classe, attribut, association) et relationnels (table, champ, clé primaire, relation et clé étrangère).
Deux approches sont couramment utilisées pour effectuer le mappage : avec des annotations ou en respectant des conventions.
ORM avec annotations
Dans le cas d’un ORM avec annotations, des métadonnées sont ajoutées dans le code pour effectuer le mappage avec les tables :
- nom de la table associée à la classe ;
- nom des champs associés aux attributs ;
- champ clé primaire ;
- type d’association et champ clé étrangère associé à un attribut.
Exemple (cas d’une gestion d’utilisateurs appartenant à un groupe) :
CREATE TABLE groups (id_group INT PRIMARY KEY, name VARCHAR(50));
CREATE TABLE users (id_user INT PRIMARY KEY, login VARCHAR(50), id_group INT REFERENCES groups(id_group));
@Table(name="users") //nom de la table
public class User {
@Id //clé primaire
@Column(name="id_user") //nom du champ
private int id;
private String login; //pas besoin d'annotation (champ de nom identique)
@ManyToOne //many users in one group
@JoinColumn(name="id_group") //clé étrangère
private Group group;
}
@Table(name="groups")
public class Group {
@Id
@Column(name="id_group")
private int id;
private String name;
@OneToMany(mappedBy="group") //one group to many users
//champ de la classe associée (User)
private List<User> users;
}
ORM avec conventions
Dans le cas d’un ORM avec conventions, il n’y a pas (ou moins) d’annotations, mais le développeur doit respecter certaines contraintes ; exemples :
- le nom de la classe doit être identique à celui de la table, ou conversion
minuscules et pluriel (classe
User→ tableusers) ; - le nom des attributs doit être identique au nom des champs, ou conversion
camelCase → snake_case (
firstName→first_name) ; - le champ clé primaire (et l’attribut de l’objet associé) doit s’appeler
id.
Many-To-One / One-To-Many
Un objet A possède un seul objet B, et B une collection d’objets A.
La table A possède une clé étrangère vers la table B.
Du côté de la classe A, l’attribut de type B est en Many-To-One, et il correspond à un champ clé étrangère.
Du côté de la classe B, l’attribut de type Collection<A> est en One-To-Many ; il n’y a pas de champ (multivalué) correspondant dans la table B.
Many‑To‑Many
- Plusieurs objets A sont associés à plusieurs objets B.
- Il y a une table de jointure entre les tables A et B dont la clé primaire est la concaténation des clés étrangères vers les tables A et B.
Chargement paresseux ou anticipé
- Dans le cas du chargement paresseux (lazy loading), l’ORM ne récupère les objets associés que lorsqu’on y accède ; cela présente l’avantage de charger moins de données, mais nécessite plus de requêtes ; exemple :
$user = $em->find(User::class, $userId); //SELECT FROM users…
$user->getGroup() //déclenche SELECT FROM groups…
- Dans le cas du chargement anticipé (eager loading) : la requête comprend des jointures pour aussi charger les objets associés ; attention : lorsque deux classes se référencent mutuellement (comme c’est le cas avec l’exemple : le groupe a des utilisateurs qui ont chacun un groupe avec des utilisateurs…), il est nécessaire d’empêcher le cycle infini.
Migrations et Synchronisations
Lorsqu’une application en production utilise une base de données dans un état A et que des modifications de schéma sont nécessaires pour la nouvelle version B, il est crucial de migrer l’état de la base de données de A vers B sans perdre de données. Cela se fait via des scripts SQL qui ajoutent, modifient ou suppriment des champs et des tables. Chaque version introduisant de tels changements doit avoir un script SQL associé pour assurer une transition fluide.
La synchronisation consiste à maintenir la cohérence entre les modèles relationnels et objets. Les cadriciels peuvent proposer des outils pour effectuer les modifications simultanément ou pour appliquer les changements effectués sur l’un schéma à l’autre.