IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage PHP Discussion :

POO, sql et optimisation : mauvais ménage ?


Sujet :

Langage PHP

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Avril 2010
    Messages
    39
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 39
    Points : 63
    Points
    63
    Par défaut POO, sql et optimisation : mauvais ménage ?
    Bonjour,

    J'ai eu l'occasion d'intervenir, pendant un stage, sur une application existante en PHP associant POO et base de données.

    En gros (en simplifiant), on a une classe par table, chaque classe représentant une table. Par exemple, on a les tables News(id_news, titre, contenu, #id_categorie) et Categorie(id_categorie, libelle).

    On se retrouve donc avec des classes de ce type :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class News{
    	private $id_news;
    	private $titre;
    	private $contenu;
    	private $id_categorie;
    	// méthodes habituelles
    	public static function getTousLesArticles()
    	// fait une requête dans la base de données,
    	// récupère tous les articles
    	// instancie chaque objet, le stocke dans un tableau
    	// retourne une collection d'articles (=tableau)
    }
    class Categorie{
    	private $id_categorie;
    	private $libelle;
    	// méthodes habituelles
            // le constructeur prend en paramètre l'id de la catégorie et renseigne les attributs à partir d'une requête sql
    }
    A côté, on a notre page afficher_articles.php, avec un truc du genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    $maCollection = News::getTousLesArticles();
    foreach($maCollection as $monArticle){
    	$maCategorie = new Categorie($monArticle->getIdCategorie());
            echo $monArticle->getTitre().' - rubrique : '.$maRubrique->getLibelle();
    }
    Ce qui me choque : le nombre de requêtes qui seront effectuées dans notre fichier "afficher_articles.php" : on aura, en effet, nbArticles*1 requête pour récupérer les catégories de chaque article ! Et encore, mon cas est simplifié, car dans ce que j'ai pu voir, on avait parfois l'instanciation de bien plus d'objets (dans cet exemple, on avait par exemple l'instanciation de l'auteur... soit x requêtes de plus).

    En procédural, on se serait contenté d'une seule requête avec jointure...

    Comment procédez-vous sur des cas comme ça ?

    Bonne soirée.

  2. #2
    Expert éminent sénior
    Avatar de rawsrc
    Homme Profil pro
    Dev indep
    Inscrit en
    Mars 2004
    Messages
    6 142
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Dev indep

    Informations forums :
    Inscription : Mars 2004
    Messages : 6 142
    Points : 16 545
    Points
    16 545
    Billets dans le blog
    12
    Par défaut
    Salut floriann,

    Ai première vue je dirais un peu comme toi : c'est vachement lourd pour rien.
    Je pense que la fonction getTousLesArticles() doive se charger de récupérer en une seule passe tous les articles et les catégories correspondantes.
    En sortie tu récupère un tableau associatif et le tour est joué.

    Donc je serais toi, je modifierai la requête sous jacente de getTousLesArticles().

  3. #3
    Membre éclairé
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    625
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 625
    Points : 822
    Points
    822
    Par défaut
    Hello,

    Question de logique. Une catégorie est un ensemble d'éléments et ces éléments sont des articles. C'est un peu l'inverse qui est implémenté ici.

    Il serait donc plus logique d'avoir quelque chose de ce genre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $mesArticles = $maCategorie->getArticles();
    Maintenant, pour la suite de ton questionnement des classes du style Catégorie, News, Auteur ont elles "besoin" d'un rapport quelconque avec le système d'enregistrement ? Si tu implémentes tes classes pour qu'elles tapent dans une DB X et que demain tu dois réutiliser le même système pour taper dans une DB Y ou un fichier plat, tu réécris tes classes ?

    Idéalement l'accès aux données devrait se faire de l'"extérieur" de ce genre de classe. Ce qui te permettrait de limiter le nombre de requêtes de la même manière qu'en procédural.

  4. #4
    Expert éminent
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Points : 7 762
    Points
    7 762
    Par défaut
    Mon grain de sel: utilise des adaptateur
    http://badger.developpez.com/tutorie...ns/adaptateur/

    Le but du jeu c'est d'avoir des objets modèles indifférents au type de média qu'ils utilisent.

    Donc en d'autres termes, quand tu vas construire ta couche modèle, il faut voir ça comme une pile: les objets de la couche supérieure ne savent pas comment travaillent les objets de la couche en dessous d'eux et ainsi de suite, chacun s'occupe de ce qu'il à a faire et délègue ce qu'il ne sait pas faire à ses copains du dessous.

    Si tu ne veux pas te lancer la dedans, tu as de très bon projets qui te mâchent le travail: les ORM (Object Relationnal Mapper) qui sont des couches d'abstraction à destination des SGBD relationnelles (99.99% des usages de PHP) capables de comprendre la structure de ton modèle de données relationnel et de te construire les objets qui vont bien. On citera les 2 gagnants du moment:
    - Propel http://www.propelorm.org/
    - Doctrine http://www.doctrine-project.org/

    Après y'a toujours la solution "je-suis-un-goret-qui-mets-des-requêtes-sql-dans-des-vues" mais vu que tu a l'air de montrer un intérêt pour la POO en PHP je crois comprendre que tu n'en est pas

    @Petibidon
    Pourfendeur de singletons en croisade
    Arise my minions, let's __destruct his instances !

  5. #5
    Modérateur
    Avatar de grunk
    Homme Profil pro
    Lead dév - Architecte
    Inscrit en
    Août 2003
    Messages
    6 692
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Côte d'Or (Bourgogne)

    Informations professionnelles :
    Activité : Lead dév - Architecte
    Secteur : Industrie

    Informations forums :
    Inscription : Août 2003
    Messages : 6 692
    Points : 20 241
    Points
    20 241
    Par défaut
    La solution de Benjamin Delespierre est la plus en adéquation avec ton code , mais nécessitera pas mal de modification pour un gain en performance pas fulgurant. Les ORM quoi que l'on en dise restent très lent comparé à une simple requête bien conçue.

    Je suis plus partisan d'avoir une méthode qui retourne tous tes articles avec les jointures adéquates pour avoir toutes les infos. Surtout que dans ton cas si le but est d'afficher le nom de la catégorie associée ça ne vaut peut être pas la peine de créer un objet, faire une requête et appeler une méthode pour chaque élément.

  6. #6
    Membre du Club
    Profil pro
    Inscrit en
    Avril 2010
    Messages
    39
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 39
    Points : 63
    Points
    63
    Par défaut
    Tout d'abord, merci d'avoir pris le temps de me répondre

    @Petibidon
    D'accord avec toi sur le manque de logique de l'emplacement de la méthode Mais même avec cette organisation, on en reviendra de toute façon au même point, car on aura toujours besoin d'un $monUtilisateur = new Utilisateur($monArticle->getIdUtilisateur()); pour avoir les informations que chaque utilisateur
    Par contre, tu soulèves un point intéressant sur la séparation DB/classes (voir fin de mon message)

    @Benjamin Delespierre
    J'ai jeté un coup d'œil sur les adaptateurs mais j'avoue que ça reste très flou pour moi, surtout pour l'implémenter dans une problématique de base de données et d'économie de requêtes. Si tu avais un exemple, ça serait super !

    @grunk
    Dans le code auquel je fais référence, le fonctionnement est parfois comme décrit dans mon premier message, et parfois comme ce que tu préconises (une méthode qui retourne toutes les informations de la requête). J'avoue que ça permet d'économiser les requêtes, mais je trouve que c'est ne pas respecter l'esprit de la POO... et que ça diminue un peu l'intérêt d'utiliser de la POO selon moi.

    ...

    Cet après-midi, j'ai un peu réfléchi à la façon dont j'aurais personnellement construit le code (je rappelle que le code présenté ci-dessus n'est pas de moi ; j'avais juste dû intervenir dessus et ça m'avait donné envie de réfléchir sur la "bonne" façon de faire...).
    L'idée est de se servir des associations. Ainsi, on rajoutera un objet "Categorie" et un objet "Utilisateur" dans notre classe News. Les constructeurs ne chargent plus les données depuis la base de données :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class News{
    	private $id_news;
    	private $titre;
    	private $contenu;
    	private $id_utilisateur;
    	private $id_categorie;
    	private $utilisateur;
    	private $categorie;
    	// accesseurs
    	// mutateurs
    	function __construst($unId_news, $unTitre, $unContenu, $unId_utilisateur, $unId_categorie, $unUtilisateur = null, $uneCategorie = null)
    	// ...
    }
     
    class Categorie{
    	private $id_categorie;
    	private $libelle;
    	// accesseurs
    	// mutateurs
    	function __construct($unId_categorie, $unLibelle)
    	// ...
    }
    Je rajoute la classe Utilisateur et entreprise pour que mon exemple soit plus compréhensible :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Utilisateur{
    	private $id_utilisateur;
    	private $pseudo;
    	private $id_entreprise; // un utilisateur appartient à une entreprise
    	private $entreprise // comme sur les autres classes, j'ajoute un objet entreprise
    	//...
    }
    class Entreprise{
    	private $id_entreprise;
    	private $nom_entreprise;
    	//...
    }
    A côté, on ajoute une classe ManagerArticle, par exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
     
    class Manager{
    	// renvoit une collection d'objets News construite depuis la BD
    	public static function getLesArticles(){
    		$requete = mysql_query('SELECT id_news, titre, contenu, utilisateur.id_utilisateur, categorie.id_categorie, libelle, pseudo, entreprise.id_entreprise, nom_entreprise FROM news INNER JOIN categorie ON categorie.id_categorie=news.id_categorie
    INNER JOIN utilisateur ON utilisateur.id_utilisateur=news.id_utilisateur
    INNER JOIN entreprise ON entreprise.id_entreprise=utilisateur.id_entreprise') or die(mysql_error());
    		$collectionArticles = array();
    		while($mesDonnees = mysql_fetch_object($requete)){
    			$entreprise = new clEntreprise($mesDonnees->id_entreprise, $mesDonnees->nom_entreprise);
    			$utilisateur = new clUtilisateur($mesDonnees->id_utilisateur, $mesDonnees->pseudo, $mesDonnees->id_entreprise, $entreprise);
    			$collectionAnomalies[] = new Article($mesDonnes->id_news, $mesDonnes->titre, $mesDonnes->contenu, $mesDonnes->id_utilisateur, $mesDonnes->id_categorie, $utilisateur);
    		}
    		return $collectionArticles;
    	}
     
    	// renvoit un objet News correspondant à la news demandée en paramètre
    	public static function getUneNews($unId){
    		$requete = mysql_query('SELECT id_news, titre, contenu, utilisateur.id_utilisateur, categorie.id_categorie, libelle, pseudo, entreprise.id_entreprise, nom_entreprise FROM news INNER JOIN categorie ON categorie.id_categorie=news.id_categorie
    INNER JOIN utilisateur ON utilisateur.id_utilisateur=news.id_utilisateur
    INNER JOIN entreprise ON entreprise.id_entreprise=utilisateur.id_entreprise
    WHERE id_news='.$unId);
    		$mesDonnees = mysql_fetch_object($requete)){
    		$entreprise = new clEntreprise($mesDonnees->id_entreprise, $mesDonnees->nom_entreprise);
    		$utilisateur = new clUtilisateur($mesDonnees->id_utilisateur, $mesDonnees->pseudo, $mesDonnees->id_entreprise, $entreprise);
    		return new Article($mesDonnes->id_news, $mesDonnes->titre, $mesDonnes->contenu, $mesDonnes->id_utilisateur, $mesDonnes->id_categorie, $utilisateur);
    }
    // veuillez excuser l'absence de contrôles poussés, et les éventuelles erreurs, j'ai fait ça en rédigeant ce message
    // je ne me suis pas occupé des Catégories dans ces exemples, c'était déjà assez parlant
    Avec cette méthode, on garde un nombre requêtes identique à ce qu'on avait en procédural et on manipule vraiment des objets.

    Néanmoins, plusieurs points me laissent perplexe :
    - Si on décide de changer de constructeur demain, il faudra modifier toutes ces méthodes statiques... Sur un gros projet, ça pourrait devenir complexe.
    - Je trouve qu'il y a une certaine redondance dans l'instanciation des objets. Demain, si je crée une classe Message, où chaque "message" sera posté par un Utilisateur, on aura encore le même travail à faire sur l'instanciation d'Entreprise, puis d'Utilisateur, puis de Message...
    - Je me dis aussi que ça risque de devenir compliqué quand on aura X objets à instancier, et qu'il faudra remonter toute la hiérarchie (là, on a déjà entreprise -> utilisateur -> news... on peut imaginer des cas plus complexe où ça risque de devenir une usine à gaz ?)
    - On a un structure des classes légèrement différente du modèle relationnel de la BD puisqu'on ajoute les objets correspond aux différents ID (id_catégorie, id_utilisateur...).

    Bref, qu'en pensez-vous ?

    J'espère avoir été un minimum clair.

    Bonne soirée et merci d'avancer pour vos avis toujours très intéressants.

  7. #7
    Expert éminent sénior

    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    7 920
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 7 920
    Points : 10 727
    Points
    10 727
    Par défaut
    avec PDO tu pourrais faire du FETCH_CLASS direct meme avec un FETCH_ALL

  8. #8
    Expert éminent
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Points : 7 762
    Points
    7 762
    Par défaut
    Heu... Un exemple d'utilisation des adaptateur ? Vu que je suis en train de le réaliser ça va être assez tendax là...

    Y'en avait pas dans la doc que je t'ai filé ??

    Je peux te fournir quelques signatures si ça peut t'aider :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    interface Adaptable { ... }
    interface Adapter extends Adaptabe { ... }
     
    class MySQLReader extends PDOStatement implements Adapter{ .... }
    class FileReader extends File implements Adapter { ... }
     
    class MyModelClass implements Adaptable {
       public function attach (Adapter $a) { ... }
       public function refresh () { $this->_adapter->read($config); }
       ....
    }
    On a besoin de deux interfaces de base, Adaptable, pour les classes pouvant recevoir un (ou plusieurs) adaptateurs, et Adapter pour les classe d'adaptateur (on notera qu'un adaptateur est en fait également un objet adaptable - le cas d'usage c'est un parseur de fichier: on a une classe qui s'occupe de lire le fichier et une autre qui s'occupe de parser son contenu)

    Ensuite on crées un Adaptateur de bdd et un adaptateur de fichier, à toi de voir en fonction de tes besoins comment implémenter tout ça.
    Et au final on a une class MyModelClass qui peut se voir attacher un adaptateur pour effectuer ses lectures / écritures. Cette classe ne devra porter aucune information relative au média qu'elle utilise sinon l'adaptateur pour ce média.

  9. #9
    Membre du Club
    Profil pro
    Inscrit en
    Avril 2010
    Messages
    39
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 39
    Points : 63
    Points
    63
    Par défaut
    Hum, c'est vague pour un débutant en POO comme moi. Faut que je réfléchisse à tout ça.

    Sinon, personne n'a de remarques/avis sur la proposition de solution que j'ai faite ?

Discussions similaires

  1. Réponses: 0
    Dernier message: 08/08/2007, 00h26
  2. Réponses: 17
    Dernier message: 20/07/2006, 13h38
  3. Réponses: 1
    Dernier message: 06/06/2006, 16h51
  4. SQL Server - optimisation
    Par cyril68 dans le forum MS SQL Server
    Réponses: 3
    Dernier message: 16/03/2006, 14h21
  5. [SQL - procStock ] optimisation du code (éviter les boucles)
    Par luimême dans le forum MS SQL Server
    Réponses: 2
    Dernier message: 06/10/2005, 17h22

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo