Optimisation des agrégations de données complexes sous ThinkPHP via SQL natif

La conception d'API pour des systèmes de notifications communautaires implique souvent l'agrégation de données provenant de multiples entités relationnelles (publications, commentaires, likes, réponses imbriquées, profils utilisateurs). L'utilisation exclusive de l'ORM de ThinkPHP pour ce type de scénario peut conduire à une fragmentation des données et à des problèmes de performance.

Problématique de l'approche ORM classique

Lorsque la logique métier nécessite de croiser plus de cinq tables, l'ORM génère souvent des requêtes multiples et des structures de réponses fragmentées. Dans une implémentation classique, le contrôleur récupère d'abord les identifiants des publications, puis itère dessus pour récupérer les likes et les commentaires, en effectuant des requêtes supplémentaires pour obtenir les détails des utilisateurs et des publications.

// Approche legacy : Requêtes multiples et manipulations de tableaux lourdes
public function getNotificationsLegacy() {
    $targetUser = $_POST['username'];
    $threadModel = M('forum_threads');
    $likeModel = M('forum_likes');
    $commentModel = M('forum_comments');

    $userThreads = $threadModel->field('thread_id')->where(['author' => $targetUser])->order('created_at DESC')->select();
    $formattedLikes = [];
    $formattedComments = [];

    foreach ($userThreads as $thread) {
        $threadId = $thread['thread_id'];
        
        $likes = $likeModel->where(['thread_id' => $threadId])->select();
        $comments = $commentModel->where(['thread_id' => $threadId])->select();

        foreach ($likes as $like) {
            $threadDetails = $threadModel->find($threadId);
            $like['thread_title'] = $threadDetails['title'];
            $formattedLikes[] = $like;
        }
        // Logique similaire et répétitive pour les commentaires...
    }
    
    // Retour d'une structure fragmentée difficile à parser côté client
    return json_encode([
        'status' => 'Y', 
        'data_likes' => $formattedLikes, 
        'data_comments' => $formattedComments
    ]);
}

Cette méthode produit un JSON contenant plusieurs jeux de données distincts avec des schémas différents. Les clients mobiles (iOS, Android) et le web doivent alors implémenter une logique de parsing complexe pour unifier l'affichage dans l'interface utilisateur. De plus, le nombre de requêtes SQL augmente de façon exponentielle (problème N+1).

Solution : Unification via SQL natif

Pour résoudre ce problème, il est préférable de déléguer l'agrégation et le formatage à la base de données elle-même. En utilisant des requêtes SQL natives avec des jointures (LEFT JOIN, INNER JOIN) et des fonctions de chaîne comme CONCAT, nous pouvons aplatir la structure et retourner un modèle de données uniforme.

// Approche optimisée : SQL natif et unification du schéma
public function fetchUnifiedNotifications() {
    $targetUser = $_POST['username'];
    $db = M();
    $aggregatedData = [];

    $threads = M('forum_threads')->field('thread_id')->where(['author' => $targetUser])->order('created_at DESC')->select();

    foreach ($threads as $thread) {
        $threadId = $thread['thread_id'];

        // Requête unifiée pour les likes avec formatage direct
        $sqlLikes = "SELECT l.id, l.thread_id, l.author AS user_id, u.display_name, u.avatar_url, l.created_at,
                     CONCAT(u.display_name, ' a aimé votre sujet') AS title,
                     CONCAT('Like sur : ', t.title) AS content
                     FROM forum_likes AS l
                     LEFT JOIN forum_threads AS t ON l.thread_id = t.thread_id
                     INNER JOIN users AS u ON l.author = u.username
                     WHERE l.thread_id = {$threadId}";

        // Requête unifiée pour les commentaires avec formatage direct
        $sqlComments = "SELECT c.id, c.thread_id, c.author AS user_id, u.display_name, u.avatar_url, c.created_at,
                        c.body AS title,
                        CONCAT('Commentaire sur : ', t.title) AS content
                        FROM forum_comments AS c
                        LEFT JOIN forum_threads AS t ON c.thread_id = t.thread_id
                        INNER JOIN users AS u ON c.author = u.username
                        WHERE c.thread_id = {$threadId}";

        $likesResult = $db->query($sqlLikes);
        $commentsResult = $db->query($sqlComments);

        // Fusion native et efficace des tableaux 2D
        if (is_array($likesResult)) $aggregatedData = array_merge($aggregatedData, $likesResult);
        if (is_array($commentsResult)) $aggregatedData = array_merge($aggregatedData, $commentsResult);
    }

    // Tri chronologique global de l'ensemble des notifications
    usort($aggregatedData, function($itemA, $itemB) {
        return $itemB['created_at'] <=> $itemA['created_at'];
    });

    return json_encode(['code' => 200, 'message' => 'OK', 'payload' => $aggregatedData]);
}

Structure de données résultante

Chaque exécution de requête SQL native retourne un tableau à deux dimensions avec des colonnes strictement identiques. L'utilisation de la fonction native array_merge() permet de concaténer proprement ces résultats, éliminant le besoin de fonctoins personnalisées complexes ou de manipulations d'index manuelles.

Le format JSON final retourné à l'API est désormais totalement aplati et uniforme, ce qui simplifie grandement le rendu côté client :

{
  "code": 200,
  "message": "OK",
  "payload": [
    {
      "id": "27",
      "thread_id": "25",
      "user_id": "user_13760",
      "display_name": "Joker",
      "avatar_url": "uploads/avatars/default.png",
      "created_at": "1465987326",
      "title": "Joker a aimé votre sujet",
      "content": "Like sur : stupid",
      "floor": "0"
    },
    {
      "id": "30",
      "thread_id": "24",
      "user_id": "user_13760",
      "display_name": "Joker",
      "avatar_url": "uploads/avatars/default.png",
      "created_at": "1465981260",
      "title": "Manger des pâtes",
      "content": "Commentaire sur : qwertyuop",
      "floor": "2"
    },
    {
      "id": "25",
      "thread_id": "24",
      "user_id": "user_876D2",
      "display_name": "LNR",
      "avatar_url": "http://qzapp.qlogo.cn/qzapp/1105262825/876D276C/100",
      "created_at": "1465981246",
      "title": "LNR a aimé votre sujet",
      "content": "Like sur : qwertyuop",
      "floor": "0"
    }
  ]
}

Étiquettes: ThinkPHP SQL natif PHP API REST jointures SQL

Publié le 27 juin à 20h05