Image de couverture : Atomic Query Construction : arrêtez d'écrire vos requêtes n'importe où dans vos projets PHP/Symfony
tech

Atomic Query Construction : arrêtez d'écrire vos requêtes n'importe où dans vos projets PHP/Symfony

23 March 2026
6 min de lecture
2 vues
Sébastien Muler

Atomic Query Construction : arrêtez d'écrire vos requêtes n'importe où dans vos projets PHP/Symfony

Vous avez probablement déjà vécu cette situation : un contrôleur récupère les utilisateurs actifs d'une certaine façon, un service fait la même chose mais oublie une condition, et un job en arrière-plan reconstruit la requête à sa manière. Résultat ? Trois définitions différentes de la même donnée, et personne ne sait laquelle est « la bonne » jusqu'au jour où un bug surgit en production.

C'est précisément le problème que l'Atomic Query Construction (AQC) cherche à résoudre. Bien que ce patron ait été formalisé dans l'écosystème Laravel par Raheel Shan sur dev.to, ses principes s'appliquent parfaitement à des projets PHP/Symfony et méritent toute l'attention des développeurs soucieux de la qualité de leur code.


Le vrai problème : la dérive, pas la duplication

On répète souvent le principe DRY (Don't Repeat Yourself), mais ce n'est pas tant la duplication de code qui pose problème que la divergence silencieuse entre des requêtes censées exprimer la même intention.

Prenons un exemple concret en Symfony avec Doctrine :

// Dans un contrôleur
$users = $this->userRepository->findBy(['active' => true]);

// Dans un service métier
$users = $em->getRepository(User::class)
    ->createQueryBuilder('u')
    ->where('u.active = :active')
    ->andWhere('u.emailVerifiedAt IS NOT NULL')
    ->setParameter('active', true)
    ->getQuery()
    ->getResult();

// Dans une commande console
$users = $em->getRepository(User::class)
    ->createQueryBuilder('u')
    ->where('u.active = :active')
    ->andWhere('u.role = :role')
    ->setParameter('active', true)
    ->setParameter('role', 'admin')
    ->getQuery()
    ->getResult();

Ces trois extraits ne sont pas une duplication : ce sont trois définitions concurrentes de la notion d'« utilisateur actif ». Votre système se comporte différemment selon le fichier que vous ouvrez. C'est cela qui génère des bugs difficiles à tracer et une maintenance coûteuse.


Le principe AQC : une seule couche possède toutes les requêtes

L'idée centrale de l'Atomic Query Construction est simple et radicale :

Toute requête base de données doit vivre dans une seule couche dédiée.

Cette couche expose des unités atomiques — des blocs de requête réutilisables, nommés et testables indépendamment. On ne réécrit plus la logique de filtrage partout ; on l'assemble à partir de ces briques.

En Symfony, le QueryBuilder de Doctrine se prête très bien à ce pattern, notamment via des Repository enrichis ou des classes de type QueryFactory.

Mise en pratique avec Symfony et Doctrine

Voici comment structurer une telle couche dans un projet Symfony :

// src/Repository/UserRepository.php

class UserRepository extends ServiceEntityRepository
{
    // Brique atomique : filtre "actif"
    public function applyActive(QueryBuilder $qb, string $alias = 'u'): QueryBuilder
    {
        return $qb->andWhere("$alias.active = :active")
                  ->setParameter('active', true);
    }

    // Brique atomique : filtre "email vérifié"
    public function applyEmailVerified(QueryBuilder $qb, string $alias = 'u'): QueryBuilder
    {
        return $qb->andWhere("$alias.emailVerifiedAt IS NOT NULL");
    }

    // Brique atomique : filtre par rôle
    public function applyRole(QueryBuilder $qb, string $role, string $alias = 'u'): QueryBuilder
    {
        return $qb->andWhere("$alias.role = :role")
                  ->setParameter('role', $role);
    }

    // Assemblage : utilisateurs actifs et vérifiés
    public function findActiveVerifiedUsers(): array
    {
        $qb = $this->createQueryBuilder('u');
        $this->applyActive($qb);
        $this->applyEmailVerified($qb);
        return $qb->getQuery()->getResult();
    }

    // Assemblage : administrateurs actifs
    public function findActiveAdmins(): array
    {
        $qb = $this->createQueryBuilder('u');
        $this->applyActive($qb);
        $this->applyRole($qb, 'admin');
        return $qb->getQuery()->getResult();
    }
}

Désormais, si la définition d'un « utilisateur actif » change (ajout d'un champ banned, par exemple), vous modifiez un seul endroit : la méthode applyActive. Tous les assemblages qui l'utilisent sont automatiquement mis à jour.


Les bénéfices concrets pour votre projet

✅ Cohérence garantie

Une seule source de vérité pour chaque règle métier liée à la donnée. Fini les bugs où deux parties de l'application ne voient pas les mêmes utilisateurs.

✅ Testabilité améliorée

Chaque brique atomique peut être testée isolément. Vous validez que applyActive produit bien le bon fragment SQL sans avoir à tester toute une chaîne applicative.

// Test unitaire d'une brique atomique
public function testApplyActiveAddsCorrectCondition(): void
{
    $qb = $this->createMock(QueryBuilder::class);
    $qb->expects($this->once())
       ->method('andWhere')
       ->with('u.active = :active')
       ->willReturnSelf();

    $this->userRepository->applyActive($qb);
}

✅ Lisibilité du code métier

Les contrôleurs et services expriment une intention métier claire sans se noyer dans des détails de construction de requête. Le code devient auto-documenté.

✅ Maintenance simplifiée

Quand une règle métier évolue, le changement est localisé. Le risque d'oublier une occurrence est éliminé par conception, pas par discipline.


Aller plus loin : les Criteria et les Specifications

Pour les projets Symfony de plus grande envergure, l'AQC peut être combiné avec le patron Specification ou les Criteria Doctrine. Chaque spécification encapsule une règle métier et peut être combinée avec d'autres via des opérateurs logiques (AndSpecification, OrSpecification).

Cette approche pousse le concept encore plus loin en rendant les règles de filtrage portables : elles peuvent être utilisées aussi bien pour filtrer une collection en mémoire que pour construire une requête SQL.


Conclusion

L'Atomic Query Construction n'est pas une révolution architecturale complexe à mettre en place. C'est une discipline simple : centraliser la logique de requête dans des briques nommées, réutilisables et testables, plutôt que de la laisser se disperser dans toute la base de code.

Que vous travailliez sur Laravel comme dans l'article source de Raheel Shan, ou sur des projets PHP/Symfony comme chez MulerTech, le constat est le même : la dérive des requêtes est l'une des sources de bugs les plus insidieuses et les plus coûteuses à corriger.

Adopter l'AQC, c'est investir quelques minutes d'organisation aujourd'hui pour éviter des heures de débogage demain. C'est l'une de ces bonnes habitudes qui, une fois intégrée dans votre workflow, transforme durablement la qualité et la maintenabilité de vos projets.

Partager cet article