[RESOLU] Structure DB commande/produits

Eléphanteau du PHP | 12 Messages

08 janv. 2021, 11:59

Bonjour,

Je travaille actuellement sur l'insertion de données d'une commande en base de données.
Néanmoins, j'ai des doutes quant à la structure de celle-ci et j'aimerais avoir des avis.
J'ai 5 tables:

client(id_cli[PK], nom, prenom..)
commande(id[PK], id_client[FK] date, etat)
commande_contenu(id_commande[PK][FK], id_produit[PK][FK], quantite)
produit(id_produit[PK], id_famille[FK] nom, prix ..)
famille(id_fam[PK], nom)

Après avoir fait des recherches j'ai décidé de faire une structure comme ça mais c'est la première fois que j'ai + d'une PK dans une table et je voudrais savoir si il y'aurait plus simple. Je suis un peu perdu avec cette table au moment ou je dois faire mes requêtes ..

La question que je me pose surtout lorsque je fais mes requêtes c'est que dois-je insérer et dans quelle table (l'utilisateur sélectionne juste la quantité du produit et clique sur commander)

Merci à vous !!

Avatar du membre
Administrateur PHPfrance
Administrateur PHPfrance | 9782 Messages

08 janv. 2021, 13:27

Ton schéma de table me semble parfait.
Certaines bases de données permettent aussi de stocker des tableaux (Array ou JSON) comme type de champ, donc tu peux aussi imaginer dans ta table commande, avoir un champ contenu qui contiendrait un tableau des produits avec leur quantité.
Mais en soi ton schéma actuel est bien et est le plus classique.

Quand un client fait une commande, il te faudra dans cette ordre :
- un INSERT dans ta table client (si il n'est pas déjà client) et la récupération de l'ID de ce client que tu vas créer
- un INSERT dans ta table commande
- un INSERT dans ta table commande_contenu pour chaque produit de la commande.
Quand tout le reste a échoué, lisez le mode d'emploi...

Eléphanteau du PHP | 12 Messages

08 janv. 2021, 14:55

Ah merci ça me rassure que mon schéma soit cohérent.
Je vais bosser sur les requêtes.
Une petite question supplémentaire, en gros dans ma recherche de produit on peut cliquer sur 'Commander' a coté de chaque article.
Je me demandais si il existait un moyen en PHP pour qu'à chaque rajout de produit, la commande puisse se mettre à jour sur la même page en laissant l'utilisateur poursuivre ses ajouts, puis cliquer sur valider pour qu'à ce moment là les requêtes s'exécutent.
En gros :

-L'utilisateur cherche un produit -> l'ajoute à sa commande -> voit directement un récapitulatif
-L'utilisateur cherche un deuxième produit -> l'ajoute -> valide sa commande de deux produits

Et tout ceci sur la même page.
Bon désolé c'est un peu long comme explication mais j'essaye d'être clair !

Merci de ton aide en tout cas ça va me permettre d'avancer.

Mammouth du PHP | 2703 Messages

08 janv. 2021, 15:04

sans rechargement de page, php + html ne suffira pas, il faudra du javascript en plus.

Eléphanteau du PHP | 12 Messages

11 janv. 2021, 11:21


Quand un client fait une commande, il te faudra dans cette ordre :
- un INSERT dans ta table client (si il n'est pas déjà client) et la récupération de l'ID de ce client que tu vas créer
- un INSERT dans ta table commande
- un INSERT dans ta table commande_contenu pour chaque produit de la commande.
Bon j'ai essayé de faire des tests mais je galère...
L'utilisateur a seulement à sélectionner la quantité et cliquer sur commander, mais ou dois-je mettre $quantite
<form method ='POST' action = 'recherche_produits.php'>
                                    <center>
                                            <input type="number" step="1" value="qte" name="quantite" min="0" max="20">
                                            <input type="submit" value="Commander" name="commander" class="btn btn-sm btn-primary">
                                    </center>
Donc je veux récupérer la quantité et le produit seléctionné, mais ce que je ne comprends pas c'est comment récupérer l'id_commande dans $commande2 alors que je n'ai pas besoin de l'insérer quand je procède à la requête $commande1.
$quantite=$_POST['quantite'];
$produit=$_POST['id_produit'];
// Requêtes d'ajout d'ouvrages à la commande
if(isset($_POST['commander'])){
    $commander = mysqli_real_escape_string($con, htmlspecialchars($_POST['commander']));

    $commande1="INSERT INTO commande('id_commande','id_client','date','etat_commande')
                VALUES ($_POST['id_commande'],1, 'en attente')";

    $commande2="INSERT INTO commande_contenu('id_commande','id_produit','quantite')
                VALUES ($_POST['id_commande'],$_POST['id_produit'], $_POST['quantite']";
}

Avatar du membre
Modérateur PHPfrance
Modérateur PHPfrance | 10684 Messages

20 janv. 2021, 16:31

Salutations !

Quand l'utilisateur va cliquer sur le bouton commander, ton formulaire va être envoyé avec la méthode post, tu vas donc récupérer la valeur saisie dans le champ "quantite" dans ta variable $_POST['quantite']. Si tu copies cette valeur dans ta variable $quantite, tu peux utiliser l'une ou l'autre de ces variables quand tu en as besoin (et en l'occurrence, tu en as besoin dans ta table commande_contenu pour savoir combien d'articles ont été commandés :))

En l'état, ta requête commande1 a plusieurs problèmes : tu listes les 4 champs de ta table, mais ne passe que 3 valeurs, ça ne plaira pas à MySQL qui aime bien que les choses soient carrées et il faudra lui passer une date ;). Par ailleurs tu tentes de lui passer un id_commande qui ne se trouve pas dans $_POST car ce n'est pas une information envoyée par ton client.

Comme indiqué par @rthur, il faut procéder par étape :
- tu vas créer une nouvelle commande : elle concerne un id_client, elle a été passée à une certaine date et aura un statut initial. Si ton champ id_commande est en AUTO_INCREMENT dans MySQL, alors ces 3 informations suffisent et c'est la base de données qui va se charger de créer le numéro de commande à la volée (si le champ n'est pas en AI, il est fortement recommandé de l'y passer ;)). Résultat, tu envois 3 valeurs en base, MySQL en rajoute une et procède à l'enregistrement.
- une fois la commande créée, comme tu ne connais pas le numéro de la commande généré par MySQL, il faut que tu récupère celle-ci avec mysqli_insert_id() ou un équivalent. Cet id te permettra alors de spécifier le contenu de la commande et surtout de lier ce contenu à la commande de ton client (histoire de pas lui livrer des tabourets alors qu'il a commandé un billet d'avion ;))
- Une fois que tu as le numéro de commande, tu peux donc procéder à l'enregistrement de la commande dans ta table commande_contenu en précisant l'id commande que MySQL a généré, l'id produit que le client a sélectionné et la quantité souhaitée en allant la chercher dans $_POST['quantite'] ou dans $quantite.

Voilà pour le principe général :)

Il faudra par la suite faire attention à la sécurité de ton code en vérifiant systématiquement les données transmises par l'utilisateur avant de les enregistrer en base (vérifier qu'un nombre est bien un nombre, qu'il n'y a pas de caractères spéciaux...). C'est pas urgent, commence par assimiler le fonctionnement, mais ça sera super important de s'y intéresser à un moment donnée :)

Et pour compléter rapidement les propos d'or1, il faudra effectivement utiliser du javascript (de l'ajax en l'occurrence), pour faire appel au serveur sans recharger la page. Les informations ainsi transmises seront stockées dans une variable de session et quand l'utilisateur voudra consulter son panier, tu pourras interroger $_SESSION pour retrouver tout ce que tu auras envoyé au serveur via javascript ;)
Ce n'est pas en améliorant la bougie que l'on a inventé l'ampoule...

Eléphanteau du PHP | 12 Messages

22 janv. 2021, 11:05

Bonjour Ryle,

Tout d'abord merci beaucoup pour ton intervention c'est quand même très complet. :shock:
Depuis j'ai avancé un peu dans ce que je voulais faire.
Bon il me semble bien que niveau injections et failles xss c'est pas top je me suis renseigné que très récemment sur le sujet mais ce n'est pas ma priorité à l'heure actuelle (oui ça devrait sûrement l'être).
Pour le moment je ne m'occupe pas des clients et j'insère un id générique à chaque commande.
Mon insertion dans mes tables commande et commande_contenu fonctionnait bien, puis j'ai décidé de changer ma barre de recherche pour y inclure de l'ajax ( j'ai suivi un petit tuto).
Depuis que j'ai fait ça, l'insertion ne fonctionne plus. Un var_dump me sort pourtant ce que je souhaite. J'ai longtemps cherché l'erreur mais elle reste introuvable pour moi.


ajout_commande.php
<?php

$id_produits = !empty($_POST['id_produit']) ? $_POST['id_produit'] : NULL;
$quantites= !empty($_POST['quantite']) ? $_POST['quantite'] : NULL;

// Requêtes d'ajout d'ouvrages à la commande
if(isset($_POST['commander'])){
    
   

    if($quantites>0){
        // Création de la commande
        $commander = mysqli_real_escape_string($con, htmlspecialchars($_POST['commander']));

        $commande1="INSERT INTO commande(`id_client`,`etat_commande`)
                    VALUES (1, 'en attente')";
        
        //exécution de la requete
        if(!mysqli_query($con, $commande1) ){
            echo("Erreur dans la requete : " . $commande1 . ' <br>Erreur :' . mysqli_error($con));
        }
   
        // récupération de l'id nouvellement inséré en BDD
        $id_commande =  mysqli_insert_id($con);         
        
        // Puis boucle sur le/les produit(s) pour les ajouter dans la commande
        foreach($quantites as $id_produits => $qte ){

            if( !empty( $qte) && !empty($id_produits) ) {
            $commande2="INSERT INTO commande_contenu(`id_commande`,`id_produit`,`quantite`)
                        VALUES ('".$id_commande."', '".$id_produits."', '".$qte."')";
  
                //exécution de la requete 2 avec le même id_commande.
                if(!mysqli_query($con, $commande2) ){
                    echo("Erreur dans la requete : " . $commande2 . ' <br>Erreur :' . mysqli_error($con));
                }
            }
        } 
    }
    // if($quantites==0){
    //     echo"Aucun ouuvrage sélectionné !";
    // }    
   
}


fetch.php
<?php
include '_database.php'; 
$output = '';


if(isset($_GET["query"]))
{
 $search = mysqli_real_escape_string($con, $_GET["query"]);
 $query = "SELECT id_produit,nomProduit,prixProduit,nom 
            FROM produit
            INNER JOIN famille ON  famille.id_famille=produit.id_famille
            WHERE nomProduit LIKE '%".$search."%'
            OR libelleProduit LIKE '%".$search."%' 
            OR prixProduit LIKE '%".$search."%'
            OR famille.nom LIKE '%".$search."%' 
            ";
}

else
{
 $query = "SELECT id_produit,nomProduit,prixProduit,nom 
 FROM produit
 INNER JOIN famille  ON  famille.id_famille=produit.id_famille
 GROUP BY nomProduit
 LIMIT 5";
}


$result = mysqli_query($con, $query);


if(mysqli_num_rows($result) > 0)
{
 $output .= '
   <table class="table table bordered">
    <tr>
     <th>ID</th>
     <th>Nom</th>
     <th>Famille</th>
     <th>Prix en €</th> 
     <th><img src="./images/felche_bas.png" alt="" height="25px"/></th>
    </tr>
 ';
?>

<form method='POST' action='index.php'>
 <center>
    <input type="submit" value="Commander" name="commander" class="btn btn-sm btn-primary">              
</center>


<?php
 while($row = $result->fetch_assoc())
 {
  $output .= '
   <tr>
    <td>'.$row["id_produit"].'</td>
    <td>'.$row["nomProduit"].'</td>
    <td>'.$row["nom"].'</td>
    <td>'.$row["prixProduit"].'</td>                     
    <td><input type="number" step="1" value="qte" name="quantite'.$row['id_produit'].'" min="0" max="20"></td>
    
   </tr>
   
  ';
  
 }
 echo $output;
 
}
else
{
 echo 'Aucun résultat ne correspond à votre recherche';
}

?>
</form>
</table>
<?php
session_start();



$selection ='';
$erreur = false;
$action = (isset($_POST['action'])? $_POST['action']:  (isset($_GET['action'])? $_GET['action']:null ));

?>
<body>

</br>
<form method="GET">
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <a href="index.php"><button class="btn btn-outline-success" type="button">Retour</button></a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo02" aria-controls="navbarTogglerDemo02" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarTogglerDemo02">
            <form class="form-inline my-2 my-lg-0">
                <input type="text" name="search_text" id="search_text" placeholder="Filtrez les ouvrages par identifiant, nom, famille ou prix" class="form-control" />           
            </form>
        </div>
    </nav>
    <div id="result"></div>
</form>

</br>
<script>
$(document).ready(function(){

 load_data();

 function load_data(query)
 {
  $.ajax({
   url:"fetch.php",
   method:"POST",
   data:{query:query},
   success:function(data)
   {
    $('#resultat').html(data);
   }
  });
 }
 $('#search_text').keyup(function(){
  var search = $(this).val();
  if(search != '')
  {
   load_data(search);
  }
  else
  {
    
   load_data();
  }
 });
});
</script>
<?php include('ajout_commande.php'); ?>


Je précise que la connexion à ma base est fonctionnelle, et que les requêtes ne rencontrent aucune erreur sur phpMyAdmin.
Je ne comprends pas...
Désolé pour les gros pavés mais étant donné que je n'ai aucune piste sur l'origine de l'erreur je ne vois pas quoi partager en particulier,

Merci pour votre aide.

Avatar du membre
Modérateur PHPfrance
Modérateur PHPfrance | 10684 Messages

22 janv. 2021, 11:20

A priori tu n'as plus de champ nommé "quantite" dans ton formulaire, mais une liste de champ nommé quantite1 quantite2... :
<input type="number" step="1" value="qte" name="quantite'.$row['id_produit'].'" min="0" max="20">
(à noter que l'attribut value = "qte" pour un champ qui doit être numérique n'est pas cohérente ;))

Il faut donc soit boucler sur ces différents nom de champs, soit ajouter des crochets dans le nom du champ, autour de ton id : name="quantite[' . $row['id_produit'] . ']".

Avec cette syntaxe, tu recevras un tableau associatif dans $_POST['quantite'], dont les indexes seront les différents id produits et les valeurs les quantité saisies associées.
Dans ce cas ton contrôle " if($quantites>0){ " sera à modifier puisque $quantites contiendra un tableau et non une valeur, mais ta boucle foreach($quantites as $id_produits => $qte ) sera quant à elle bien fonctionnelle (et c'est à mon avis dans cette boucle qu'il te faut vérifier si $qte > 0 ;))
Ce n'est pas en améliorant la bougie que l'on a inventé l'ampoule...

Eléphanteau du PHP | 12 Messages

22 janv. 2021, 11:57

Oh merci !!
Le value n'a aucun sens oui.
J'ai tant cherché pour au final 2 crochets en gros ... :-|
Oui du coup le contrôle if($quantites>0){ en tant que tel ne sert à rien actuellement mais je n'arrive pas trouver la bonne syntaxe. Je l'ai supprimé.
J'ai essayé de vérifier si $qte > 0 dans mon foreach mais cela ne m'apporte rien puisque si aucune quantité n'est sélectionnée la requête n'insèrera rien dans tous les cas ? Si j'ai bien compris ta suggestion tout du moins.

Petite dernière question, si je fais un header(Location:"recapitulatifdelacommande") a la fin de if(isset($_POST['commander'])){ , comment récupérer l'id commande ? (l'idée étant de générer un bon de commande)
Merci pour ton aide

EDIT : j'ai cherché et me suis renseigné un peu sur les variables de session et j'ai mit ça sur mon index tout en haut de la page:
<?php 
session_start(); 
if(!isset($_SESSION['commander'])){

    $_SESSION['id_commande'] = $id_commande;

}
?>


Et ça sur la page ou je veux récupérer la variable :
<?php
$id_commande = $_SESSION['id_commande'];
var_dump($id_commande);
?>
mais ça me met null


EDIT : j'ai réussi, j'ai mit la variable de session entre mes deux requêtes d'insertion pour récupérer l'id
// récupération de l'id nouvellement inséré en BDD
    $id_commande =  mysqli_insert_id($con);         
    
    if(!isset($_SESSION['commander'])){

        $_SESSION['id_commande'] = $id_commande;
        
    }