Image de couverture de l'article 5 astuces pour mieux utiliser Doctrine 2
Retour aux articles

L'agence

WanadevStudio

5 astuces pour mieux utiliser Doctrine 2

Les développeurs Symfony utilisent Doctrine couramment pour bénéficier de l'abstraction et simplifier les manipulations des données dans son projet. En revanche, là où nous allons gagner en temps de développement et d'implémentation, Doctrine peut vite vous en faire perdre, dès que vous vous penchez sur l'optimisation ou que vous sortez des sentiers battus. Avec un peu de retours, nous vous proposons quelques tweaks et cas d'utilisations autour de l'ORM pour vous simplifier la vie.

1) Ignorer certains champs d'une requête

Il parfois nécessaire, dans une requête, de déclarer une donnée du SELECT afin de réaliser un calcul. Dans ce cas, il est impossible de récupérer l'objet hydraté par Doctrine à cause de ce champ supplémentaire. Pour contourner ce problème, Doctrine vous propose d'ignorer certains champs en utilisant le mot-clés "HIDDEN".

$query = $this->getDoctrine()->getEntityManager()->createQuery('SELECT c,  (c.like - c.dislike) AS HIDDEN score FROM Comment c ORDER BY score DESC');

Dans cet exemple, nous allons bien récupérer notre collection de commentaires sans le champ score qui est ignoré pendant l'hydratation.

2) Éviter de charger une entité complète

Très souvent il est nécessaire d'avoir l'entité Doctrine pour effectuer certains traitements. Vous avez certainement le réflexe d'effectuer une requête pour récupérer l'objet complet, mais en fonction des cas, une entité même incomplète est suffisante (suppression, ajout d'une clé étrangère...). Il vous suffit de créer cet objet "vide" par référence.

$post = $this->getDoctrine()->getEntityManager()->getReference('AppBundle:Post', 456);
$topic->setPost($post );

Ici, l'objet "Post" est incomplet mais permet d'être rattaché au "Topic".

3) Optimiser les relations

Si Doctrine semble un peu magique dans sa gestion des relations, il est important, pour des raisons de performance, de préciser la stratégie de récupération des données.

@ManyToOne(targetEntity="Post", fetch="LAZY")

Le mode LAZY est celui par défaut. Les données sont chargées uniquement si nécessaires et l'appel aux données de relations provoque une requête supplémentaire.

@ManyToOne(targetEntity="Post", fetch="EAGER")

Le mode EAGER précharge les données de la relation automatiquement en réalisant la jointure par défaut. Ainsi votre entité sera nettement plus grande mais vous économiserez des requêtes.

@ManyToOne(targetEntity="Post",  fetch="EXTRA_LAZY")

En EXTRA_LAZY, les données ne seront pas entièrement chargées mais vous pourrez toutefois réaliser des opérations du type "contains()", "slice()" ou "count()" sur la collection.

4) Sécurisez vos requêtes

Il est courant d’exécuter directement les requêtes via Doctrine sans se soucier des potentielles erreurs. On utilise généralement la bonne vieille méthode du backup avant l’exécution. Mais le système de transaction est implémenté dans Doctrine et vous permet de sécuriser vos exécutions en offrant la possibilité du rollback.

$em = $this->getDoctrine()->getEntityManager();
try {
    $em->getConnection()->beginTransaction();

    $user = $this->getDoctrine()->getRepository('AppBundle:User')->find($user_id);

    $profile =  $user->getProfile();
    $em->remove($profile);
    $em->remove($user);
    $em->flush();

    $em->getConnection()->commit();
} catch (Exception $e) {
    $em->getConnection()->rollback();
    throw $e;
}

Dans ce cas, si la suppression en base de données provoque une erreur, un rollback est effectué pour revenir à l'état initial.

5) Alléger les traitements par lots

Vous avez peut-être déjà utilisé PHP pour des traitements lourds (migration, importation...). Mettons de côté la pertinence ou non de l’utilisation de PHP à ces fins, et penchons nous sur le comportement des traitements de Doctrine sur des grosses volumétrie.

Tout le monde le sait, PHP a un vilain défaut : une gestion de la mémoire un peu paresseuse. Doctrine n'étant pas avare en mémoire, l’utilisation des deux sur des gros volumes de traitement n'est pas des plus "joyeux".

Par défaut, vous verrez certainement votre script se ralentir au fur et à mesure car Doctrine ne délestera jamais la quantité d'objets qu'il a traités.

Pour freiner un peu cet effet, commencez par désactiver les logs Doctrine :

$this->getDoctrine()->getEntityManager()->getConnection()->getConfiguration()->setSQLLogger(null);

Si possible, utilisez dès que possible la fonction clear() pour détacher et vider les ressources.

Enfin, Doctrine a prévu une solution d'itération sur les collections évitant de charger en mémoire l'ensemble de la collection mais uniquement objet par objet. Il suffit d'appeler la fonction iterate() et de détacher chaque ligne après traitement.

$i = 0;
$q = $em->createQuery('select p from Post p');
foreach ($q->iterate() as $row) {
     ...
     $this->_em->detach($row[0]);
}

6) MAJ du 25/11/2016 - Définir la clé du tableau des résultats

Cette petite astuce est ajoutée par Théo Catherinet, quelques semaines après la première publication de cet article et permet de définir une donnée comme clé utilisé dans vos résultats. Par défaut dans Doctrine, la clé de vos résultats est un entier incrémenté. Il est souvent donc pauvre en information pour réaliser par la suite des traitements métiers ce qui nécessite de faire un traitement supplémentaire à base de array_map ou autre...

Vous devrez donc simplement ajouter en 2ème paramètre de createQueryBuilder le champ à utiliser.

$qb = $this->createQueryBuilder('s', "s.reference")  ;

Dans ce cas, nous utiliserons le champ reference comme clés dans nos résultats :

array:5 [
  101 => array:3 
    "id" => 1
    "name" => "Viper"
    "reference" => "101"
  ]
  102 => array:4 []

Toutes ces petites astuces sont importantes si vous souhaitez optimiser en profondeur les performances de vos applications Symfony. Toutes ces préconisations sont celles que nous suivons quand nous creusons dans les sources d'un projet, ou quand nous auditons techniquement les performances d'un projet Symfony.

Peut-être avez-vous d'autres astuces du même genre ? Des remarques sur celles données plus haut ? Nous serions ravis de poursuivre ce sujet avec vous en commentaire ou bien sur notre Twitter !

Commentaires

Photo de Ahmed auteur du commentaire

Ahmed

Il y a 4 ans

Thank you for the tuto, I have just a small question Does adding --no-debug option relapce the $em->getConnection()->getConfiguration()->setSQLLogger(null);