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 :

PHP OO et SQL : création d'objets "complets" + requête SQL n à 1


Sujet :

Langage PHP

  1. #1
    Nouveau Candidat au Club
    Femme Profil pro
    Technicien réseau
    Inscrit en
    Octobre 2016
    Messages
    3
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Technicien réseau

    Informations forums :
    Inscription : Octobre 2016
    Messages : 3
    Points : 1
    Points
    1
    Par défaut PHP OO et SQL : création d'objets "complets" + requête SQL n à 1
    Bonjour à tous,

    J'ai créé une application pour ma petite entreprise. Celle-ci a pour but de gérer des demandes libellées "jobs" ci-après. Je rencontre un problème que je vais tenter de simplifier au maximum.

    Le code côté serveur est du PHP 5.6 qui réalise des requêtes vers une base de données MySQL 5. Je travaille en orienté objet. La partie client se base sur de l'HTML et du JavaScript. Le PHP n'intervient en rien dans la création de la partie client. Le JavaScript réalise des requêtes REST à mon API (via du JSON). Par exemple, pour voir la liste des "jobs", la requête sera : /api/jobs.php?op=get_all_jobs tandis que pour récupérer toutes les informations d'un job, cela sera : /api/jobs.php?op=get_job&id=3.

    Comme dit précédemment, je travaille en orienté objet. J'ai une classe "Engine" (un singleton) qui est mon point d'entrée pour tout. Je possède différents "sous-moteurs" qui permettent d'effectuer les actions vers la base de données. Par exemple, si j'ai une entité "User", je posséderai également un "sous-moteur" nommé "UsersEngine" qui possèdera des méthodes comme "getAllUsers()", "getUserFromId()", "searchUsersFromName()", "updateUser()", "deleteUser()", "createUser()", etc...

    Au niveau des mes entités, j'ai une classe nommée "Job". Celle-ci contient également d'autres objets comme par exemple des "User" ou encore des "Request". Un "Job" peut être assigné qu'à un seul "User". Voici la conception au niveau de la base de données :

    Table "users" :
    • ID
    • Nom d'utilisateur
    • Prénom
    • Nom


    Table "jobs" :
    • ID
    • Numéro interne
    • ID de l'utilisateur (FK vers la table "users")
    • ...


    Dans un premier temps, pour récupérer tous les "jobs", je faisais quelque chose comme ceci :

    • SELECT sur la table jobs
    • Pour chaque FK, j'appelais le sous-moteur correspondant qui faisait une requête séparée pour créer l'objet (...->usersEngine->getUserFromId($idRecupereDansLeSelect)


    Rapidement, après quelques milliers d'entrées, les temps de chargement sont devenus longs. Je suis donc passé par un SELECT avec jointure. Je récupère toutes les valeurs et je créé directement les différents objets. Par exemple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    $stmt = $this->ser->dbLink->prepare("SELECT j.id, j.num_interne, ...., u.id, u.username, ... FROM jobs AS j JOIN users AS u ON j.user = u.i");
    //je bind les résultats sur des variables et je créé mes objets
    $user = new User($userId, $userUsername, ....);
    $job = new Job($jobId, $jobNumInterne, ..., $user);
    Cela fonctionnait bien. Cependant, il s'est avéré que mes collaborateurs s'échangent les "jobs". Certains s'assignent un "job" pendant 2 mois, n'y touchent pas et le transfèrent à un autre collègue qui traite le "job" en seulement 2 jours (il y a tout un système difficile à expliquer, l'idée n'est pas de revoir le fonctionnement de l'entreprise). Je souhaite donc pouvoir garder un historique facilement accessible des différents "users" assignés à un "job". Voici donc le nouveau design de la base de données :

    Table "users" :
    • ID
    • Nom d'utilisateur
    • Prénom
    • Nom


    Table "jobs" :
    • ID
    • Numéro interne
    • ...


    Table "jobs_users" :
    • ID
    • ID du travail (FK vers la table "jobs")
    • Date de prise en charge
    • ID de l'utilisateur (FK vers la table "users")
    • Date de fin de prise en charge


    Dans le cas présent, impossible pour moi de faire un SELECT avec jointures. Certains "jobs" auront que une ou deux entrées dans "jobs_users" tandis que d'autres pourraient en avoir beaucoup plus. Ma classe "Job" possède différentes méthodes dont : "getListUsers()" qui dépend des données précitées. D'autres méthodes dépendent d'autres tables que je n'ai pas décrites afin de ne pas surcharger cette discussion.

    Vous avez l'état des lieux. J'ai maintenant deux questions à vous poser :

    Question 1 : est-il possible d'écrire une requête SQL correspondant aux tables reprises ci-dessus (avec "jobs_users") afin de prendre en compte la relation 1 à n ?

    Question 2 : est-il obligatoire (= dans les bonnes pratiques) d'instancier complètement un objet ? Dans mon cas, un objet "Job" implique la création de nombreux objets. Dans la quasi majorité des cas, je n'ai besoin de toutes les données d'un "Job" uniquement quand je le récupère par son ID unique, sinon il s'agit de récupérer que certaines informations afin de les lister. J'ai cherché du côté des design patterns sans succès. (Dans ma situation, je ne peux pas faire du lazy loading car j'envoie les données en JSON. Je dois donc savoir au moment de la requête). Je ne trouve pas propre de ne pas compléter parfaitement un objet mais je trouve également bête de gaspiller des ressources pour des données accédées que 5% du temps.

    Je vous remercie pour votre aide et votre expertise.

  2. #2
    Membre émérite

    Profil pro
    Inscrit en
    Mai 2008
    Messages
    1 576
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2008
    Messages : 1 576
    Points : 2 440
    Points
    2 440
    Par défaut
    est-il obligatoire (= dans les bonnes pratiques) d'instancier complètement un objet ?
    Tu peux très bien ne récupérer que les informations nécessaires, c'est même recommandé de ne charger que ce que tu vas utiliser. Par contre, en fonction de ton organisation, il peut être plus pratique d'utiliser une nouvelle classe qui ne contient que ces données en question plutôt qu'une classe Job avec des propriétés incomplètes.

  3. #3
    Nouveau Candidat au Club
    Femme Profil pro
    Technicien réseau
    Inscrit en
    Octobre 2016
    Messages
    3
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Technicien réseau

    Informations forums :
    Inscription : Octobre 2016
    Messages : 3
    Points : 1
    Points
    1
    Par défaut
    Merci pour la réponse.

    C'est ce que j'ai fait. J'ai créé une classe nommée "JobLight" qui hérite de "Job". J'ai surchargé les fonctions en lien avec des données non récupérées pour lancer des exceptions.

  4. #4
    Membre éprouvé
    Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mars 2009
    Messages
    552
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mars 2009
    Messages : 552
    Points : 1 060
    Points
    1 060
    Par défaut
    Bonjour,

    Deux remarques au passage :

    • Job devrait plutôt hériter de JobLight
    • fetchObject peut vous rendre service

  5. #5
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par null0z Voir le message
    Dans le cas présent, impossible pour moi de faire un SELECT avec jointures. Certains "jobs" auront que une ou deux entrées dans "jobs_users" tandis que d'autres pourraient en avoir beaucoup plus. Ma classe "Job" possède différentes méthodes dont : "getListUsers()" qui dépend des données précitées. D'autres méthodes dépendent d'autres tables que je n'ai pas décrites afin de ne pas surcharger cette discussion.

    Question 1 : est-il possible d'écrire une requête SQL correspondant aux tables reprises ci-dessus (avec "jobs_users") afin de prendre en compte la relation 1 à n ?
    Je ne suis pas certain de comprendre la question, tu souhaiterais une seule requete SQL sans jointure pour récupérer des données de plusieurs tables car tu n'as pas la possibilité d'en faire ? Si c'est cela, je crains que ce ne soit pas possible. Il te faudra soit plusieurs requetes simples, soit des jointures. Et aussi, que veux tu que la requete retourne ?

  6. #6
    Nouveau Candidat au Club
    Femme Profil pro
    Technicien réseau
    Inscrit en
    Octobre 2016
    Messages
    3
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Technicien réseau

    Informations forums :
    Inscription : Octobre 2016
    Messages : 3
    Points : 1
    Points
    1
    Par défaut
    Citation Envoyé par bretus Voir le message
    Bonjour,

    Deux remarques au passage :

    • Job devrait plutôt hériter de JobLight
    • fetchObject peut vous rendre service
    Merci, c'est vrai que c'est plus logique.

    Citation Envoyé par Mrsky Voir le message
    Je ne suis pas certain de comprendre la question, tu souhaiterais une seule requete SQL sans jointure pour récupérer des données de plusieurs tables car tu n'as pas la possibilité d'en faire ? Si c'est cela, je crains que ce ne soit pas possible. Il te faudra soit plusieurs requetes simples, soit des jointures. Et aussi, que veux tu que la requete retourne ?
    En fait, ce que j'essayais d'expliquer et que lorsque j'ai une relation n à 1, je risque de me retrouver avec des lignes de résultats du genre :

    Job_ID | Job_Champ1 | Job_Champ2 | ... | PriseEnCharge_ID | PriseEnCharge_Champ1 | ...
    ------------------------------------------------------------------------------------------
    1 toto 123 ... 1 coucou ...
    2 foo 456 ... 2 bonjour ...
    2 foo 456 ... 3 bonsoir ...

    Dans ce cas où le Job n°2 aurait deux "tuples" PriseEnCharge, les informations seraient reprises deux fois. J'aurai trouvé chouette que le serveur "économise" la base passante en envoyant quelque chose du genre :

    Job_ID | Job_Champ1 | Job_Champ2 | ... | PriseEnCharge_ID | PriseEnCharge_Champ1 | ...
    ------------------------------------------------------------------------------------------
    1 toto 123 ... 1 coucou ...
    2 foo 456 ... 2 bonjour ...
    3 bonsoir ...

  7. #7
    Invité
    Invité(e)
    Par défaut
    Le truc c'est que dans ton exemple le plus récent ce n'est pas une relation 1:n mais n:n (un job peut avoir plusieurs users et un user peut avoir plusieurs jobs).

    Si je comprend bien, tu souhaites ensuite avoir en PHP

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    [
    "toto123" => ["coucou"]
    "foo456" => ["bonjour", "bonsoir"]
    ]
    ?

    Et si je comprend toujours bien tu aimerais que ta base de données de sorte ce format directement pour économiser d'avoir des utilisateurs ou des jobs en doublon ? Techniquement c'est possible, mais ça va demander beaucoup plus de ressources à ta base de données de sortir ce format en une seule requête, ce qui est l'inverse de l'effet voulu.

    Tu as plusieurs options :
    - Si tu veux une liste des users et les jobs associés à chacun (possiblement aucun, si tu veux ignorer les users sans jobs remplace le 1er LEFT JOIN par un INNER JOIN),
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    SELECT champs FROM users u LEFT JOIN jobs_users ju ON u.id = ju.id_user INNER JOIN jobs j ON ju.id_job = j.id
    - Si tu veux une liste des jobs et les users associés à chacun (possiblement aucun, idem qu'au dessus).
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    SELECT champs FROM jobs j LEFT JOIN jobs_users ju ON j.id = ju.id_job INNER JOIN users u ON ju.id_user = u.id
    - Si tu veux la liste de toutes les prises en charges avec les users et jobs associés. A priori une entrée est créée dans la table jobs_users lors de la prise en charge d'un job par un user, il ne devrait donc pas y avoir de relations mortes. Si ce n'est pas le cas il faudra utiliser LEFT JOIN et traiter en PHP les cas où id_user/id_job est NULL.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    SELECT champs FROM jobs_users ju INNER JOIN users u ON ju.id_user = u.id INNER JOIN jobs j ON ju.id_job = j.id
    Ce sont des requêtes simples et quelques milliers de lignes c'est une petite table donc ça ne devrait pas poser de problème. J'utilise ce genre de requêtes sur des tables de plus de 50millions de lignes par exemple et ça passe nickel et j'ai un petit serveur. Si tu as des soucis de performances, tu as souvent 2 pistes principales :
    - Evite autant que possible les appels à la base de données dans des boucles comme foreach ou while. C'est BEAUCOUP mieux de termes de performance de faire une ou deux grosses requêtes puis d'exploiter les retours en PHP par la suite.
    - Il faut indexer ta base de données. Par exemple dans ce cas précis, les champs id_user et id_job de a table jobs_users doivent être indexés (je suppose que les champs id sont des clés primaires donc il y a déjà un index dessus).


    En bonus, si tu utilises PDO, tu peux faire un truc du genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $data = $pdo->query("SELECT user_name, autres_champs FROM users u LEFT JOIN jobs_users ju ON u.id = ju.id_user INNER JOIN jobs j ON ju.id_job = j.id")->fetchAll(PDO::FETCH_GROUP);
    Ce qui devrait donner qqch du genre :
    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
    [
    "toto123" => [
      [
        "autre_champ1" => "val1",
        "autre_champ2" => "val2",
        "autre_champ3" => "val3"
      ],
      [
        "autre_champ1" => "val4",
        "autre_champ2" => "val5",
        "autre_champ3" => "val6"
      ]
    ],
    "foobar456" => [
      [
        "autre_champ1" => "val7",
        "autre_champ2" => "val8",
        "autre_champ3" => "val9"
      ],
      [
        "autre_champ1" => "val10",
        "autre_champ2" => "val11",
        "autre_champ3" => "val12"
      ]
    ]
    ]
    FETCH_GROUP créé un tableau associatif avec la 1ère valeur du SELECT comme valeur de regroupement.

Discussions similaires

  1. Réponses: 3
    Dernier message: 06/09/2010, 16h22
  2. Requête SQL + création de colonne
    Par stef51 dans le forum Langage SQL
    Réponses: 2
    Dernier message: 12/06/2007, 14h45
  3. Réponses: 3
    Dernier message: 11/01/2006, 18h35

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