DEV Community

SIKOUTRIS
SIKOUTRIS

Posted on

Construire un comparateur SaaS en PHP : retour d'expérience technique

Construire un comparateur SaaS en PHP : retour d'expérience technique

Constituire un comparateur SaaS efficace, c'est plus complexe qu'il n'y paraît. Vous devez gérer des centaines de produits, des dizaines d'attributs, des filtres complexes, tout en gardant des performances web acceptables.

Après avoir construit plusieurs comparateurs SaaS en PHP pur (sans framework lourd), je vais partager les défis techniques réels, les patterns qui fonctionnent, et surtout — les erreurs à éviter.

1. Structure des données : au-delà des simples tableaux

Votre première tentation sera d'utiliser une base de données classique SQL avec une table products et une table product_attributes. Sauf que c'est un cauchemar pour les requêtes complexes.

Le problème normalisé :

products (id, name, vendor, price, currency)
attributes (id, product_id, attribute_name, attribute_value)
Enter fullscreen mode Exit fullscreen mode

Quand vous essayez de filtrer : "Montrez-moi tous les SaaS avec stockage >= 100GB ET plan de paiement = 'annuel' ET nombre de seats >= 50", vous finissez avec une requête SQL atroce :

SELECT DISTINCT p.id, p.name 
FROM products p
JOIN attributes a1 ON p.id = a1.product_id AND a1.attribute_name = 'storage' AND CAST(a1.attribute_value AS INT) >= 100
JOIN attributes a2 ON p.id = a2.product_id AND a2.attribute_name = 'payment_plan' AND a2.attribute_value = 'annuel'
JOIN attributes a3 ON p.id = a3.product_id AND a3.attribute_name = 'seats' AND CAST(a3.attribute_value AS INT) >= 50
ORDER BY p.price ASC;
Enter fullscreen mode Exit fullscreen mode

Cette requête fait 3 JOIN sur la même table. Pour 500 produits, ça devient TRÈS lent.

La solution : dénormalisation stratégique

Au lieu de stocker chaque attribut dans une ligne distincte, stockez les attributs clés comme colonnes JSON :

products (
  id INT PRIMARY KEY,
  name VARCHAR(255),
  vendor VARCHAR(255),
  pricing DECIMAL(10,2),
  currency VARCHAR(3),
  attributes JSON,  -- Contient storage, payment_plan, seats, etc.
  metadata JSON,    -- Contient features_count, user_rating, release_date
  indexed_text FULLTEXT,  -- Pour la recherche textuelle
  created_at TIMESTAMP
)
Enter fullscreen mode Exit fullscreen mode

Votre structure JSON pour un SaaS de collaboration pourrait ressembler à :

{
  "storage": 100,
  "payment_plan": ["monthly", "annual", "enterprise"],
  "seats": {"min": 5, "max": null},
  "sso": true,
  "api_included": true,
  "support_level": "email",
  "uptime_sla": 99.9
}
Enter fullscreen mode Exit fullscreen mode

Avec cette structure, votre requête MySQL devient :

SELECT id, name FROM products
WHERE JSON_EXTRACT(attributes, '$.storage') >= 100
  AND JSON_CONTAINS(JSON_EXTRACT(attributes, '$.payment_plan'), '"annual"')
  AND JSON_EXTRACT(attributes, '$.seats.min') <= 50
ORDER BY pricing ASC;
Enter fullscreen mode Exit fullscreen mode

Beaucoup plus rapide. Et en PHP, vous parsez le JSON une seule fois.

2. Caching des filtres : le secret des performances

Quand vous avez 1000 produits et 50 attributs possibles, chaque combinaison de filtres crée une requête unique. Sans caching, c'est le chaos.

Implémentation simple avec Redis :

$filter_hash = md5(json_encode($_GET['filters'])); // Hash des filtres actuels
$cache_key = "filters:" . $filter_hash;

if ($cached = redis_get($cache_key)) {
    $results = json_decode($cached);
} else {
    $results = query_products($_GET['filters']);
    redis_set($cache_key, json_encode($results), 3600); // Cache 1 heure
}
Enter fullscreen mode Exit fullscreen mode

Le vrai truc sophistiqué : invalidez le cache au niveau des attributs, pas des filtres.

Quand quelqu'un met à jour un produit (ex: change le prix ou l'attribut "storage"), vous invalidez TOUS les caches qui concernent cet attribut :

function update_product($product_id, $new_data) {
    // Mettre à jour la BDD
    update_db($product_id, $new_data);

    // Invalider les caches pertinents
    redis_delete_pattern("filters:*storage*");
    redis_delete_pattern("filters:*price*");
}
Enter fullscreen mode Exit fullscreen mode

3. Les filtres multi-niveaux : la vraie complexité

Vous avez des filtres simples (oui/non : "a une API ?"), des filtres range (prix entre X et Y), et des filtres multi-select ("Stockage = 100GB OU 500GB OU Illimité").

Chaque type demande une logique MySQL différente. Voici comment structurer ça en PHP de manière maintenable :

class FilterBuilder {
    private $conditions = [];

    public function add_filter($attribute, $operator, $value) {
        if ($operator === 'equals') {
            $this->conditions[] = "JSON_EXTRACT(attributes, '$." . $attribute . "') = '" . $value . "'";
        } elseif ($operator === 'gte') {
            $this->conditions[] = "JSON_EXTRACT(attributes, '$." . $attribute . "') >= " . (int)$value;
        } elseif ($operator === 'in') {
            // Pour multi-select
            $escaped = array_map(fn($v) => "'" . $v . "'", $value);
            $this->conditions[] = "JSON_EXTRACT(attributes, '$." . $attribute . "') IN (" . implode(',', $escaped) . ")";
        }
        return $this;
    }

    public function build_sql() {
        return "SELECT * FROM products WHERE " . implode(' AND ', $this->conditions);
    }
}

$builder = new FilterBuilder();
$builder->add_filter('storage', 'gte', 100);
$builder->add_filter('payment_plan', 'in', ['monthly', 'annual']);
$sql = $builder->build_sql();
Enter fullscreen mode Exit fullscreen mode

Cette approche (builder pattern) rend votre code extensible : ajouter un nouveau type de filtre ne demande qu'une nouvelle méthode.

4. SEO pour les comparateurs : le challenge invisible

Voici ce que personne ne vous dit sur le SEO d'un comparateur SaaS :

  1. Les pages de filtres sont considérées comme du duplicate content par Google. Si votre comparateur génère 10 000 pages (10 produits × 1000 combinaisons de filtres), vous allez avoir des problèmes.

Solution :

  • Utilisez des URL canoniques intelligentes
  • N'indexez que les filtres "importants" (évitez d'indexer une page filtrée par "prix décroissant")
  • Utilisez rel="noindex" sur les combinaisons de faible valeur
  1. Vous devez générer des pages statiques pour le SEO. Les filtres dynamiques en JavaScript ne sont pas crawlés efficacement.

Solution :

  • Générez des pages HTML pré-rendues pour les combinaisons top (ex: "SaaS collaboratif vs SaaS CRM")
  • Stockez ces pages en fichiers pour les servir rapidement
  • Laissez les filtres avancés en JavaScript
  1. Les méta descriptions doivent être uniques. Une page filtrée sur "stockage >= 100GB" doit avoir une description différente de celle sur "stockage >= 500GB".
$meta_desc = "Comparez les meilleurs SaaS avec " . implode(', ', $active_filters) . ". Tableau comparatif complet, avis utilisateurs, prix.";
echo "<meta name='description' content='" . htmlspecialchars($meta_desc) . "'>";
Enter fullscreen mode Exit fullscreen mode

5. Performance : servir des pages lourdes rapidement

Un tableau comparatif avec 100 produits et 30 attributs = énorme DOM. Sans optimisation :

  • Rendu lent
  • Interactions figées
  • Utilisateurs qui quittent

Tactiques essentielles :

  1. Pagination côté serveur, pas côté client :
   $per_page = 20; // Ne charger que 20 produits à la fois
   $offset = ($_GET['page'] - 1) * $per_page;
   $results = query_limited($filters, $offset, $per_page);
Enter fullscreen mode Exit fullscreen mode
  1. Compression GZIP pour les données JSON :
   header('Content-Encoding: gzip');
   echo gzencode(json_encode($results), 9);
Enter fullscreen mode Exit fullscreen mode
  1. Lazy-load des images : Les logos SaaS ne doivent charger que quand visibles
   <img src="placeholder.svg" data-src="actual-logo.png" loading="lazy">
Enter fullscreen mode Exit fullscreen mode

6. Cas d'usage réel : comparer-logiciels.fr

J'ai personnellement construit Comparer-Logiciels.fr, un comparateur SaaS français. Voici ce que j'ai appris :

  • 500+ logiciels comparés, 250+ attributs différents
  • Structure JSON pour les attributs = requêtes 40% plus rapides
  • Redis caching = response time < 200ms même avec filtres complexes
  • Génération de pages statiques pour le top 100 des comparaisons = SEO stable

Le coût ? Environ 50 EUR/mois en infrastructure (serveur PHP + MySQL + Redis). Aucun besoin de technos complexes.

Conclusion

Construire un comparateur SaaS en PHP pur, c'est possible et même préférable à une stack JavaScript lourd. Les points clés :

  1. Dénormalisez intelligemment avec JSON
  2. Cachey les résultats des filtres
  3. Pensez SEO dès le départ
  4. Paginéz pour la performance
  5. Utilisez des outils simples (MySQL, Redis, PHP) plutôt que une usine à gaz

Avec ces patterns, vous pouvez supporter des milliers de produits sans problème de performance.

Top comments (0)