INSERT INTO en POO

Le problème exposé dans ce sujet a été résolu.

Bonjour, je fait appel à votre aide svp, car je bloque sur une fonction que j'essaye de créer.

J'essaye de coder une fonction qui me permettrai d'ajouter un article (dans la page nouvel_article.php) avec une requête INSERT INTO préparée, directement comme ceci par exemple:

1
Article::insertInto('articles', ['titre','description','contenu_article'], [$titre, $description, $contenu_article]);

Voici le code de ma fonction dans ma class :

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?
public static function insertInto($table, $champs=[], $values=[]) {

    $db = self::getPDO();

    // pour séparer les différents champs par des virgules
    $valeurChamps = implode(", ", $champs);

    // pour incrémenter :drapeaux pour requete préparées
    $valeurDrapeau = '';
    foreach($champs as $d) {
        $valeurDrapeau .= ' :'.$d.'';
    }

    // ********** requete **********
    $statement = "INSERT INTO ".$table."(".$valeurChamps.")
              VALUES(".$valeurDrapeau.")";

    $requete = $db->prepare($statement);

    echo $statement.'<br>';
    // ça m'affiche: INSERT INTO articles(titre, description, contenu_article) VALUES(titre :description :contenu_article)
    // donc jusqu'à la je pense que c'est bon

    // Boucle pour incrémenter les bindValue avec les valeurs de la requete préparée
    // Je Pense que c'est ici que ça bloque
    foreach($champs as $b) {
        if(is_string($valeurChamps)) { $pdo_param = PDO::PARAM_STR; } else { $pdo_param = PDO::PARAM_INT; }
        $requete->bindValue(':'.$valeurChamps, $valeurDrapeau, $pdo_param);
    }

    echo $valeurChamps.' - '.$valeurDrapeau.'<br>';
    // m'affiche: titre, description, contenu_article - :titre :description :contenu_article


    $result = $requete->execute();
    // ********** /requete **********

    return $result;

    // fermer
    $requete->closeCursor();

}

Wamp m’écris ceci comme erreur: "Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens in C:\wamp\www…"

Merci d'avance

+0 -0
1
2
3
<?php
    // ça m'affiche: INSERT INTO articles(titre, description, contenu_article) VALUES(titre :description :contenu_article)
    // donc jusqu'à la je pense que c'est bon

Justement pas : ne vois-tu pas qu'il manque un : devant le titre du VALUES ? ^^ Du coup, tu n'as que 2 champs, dans lesquels tu essayes de faire rentrer 3 valeurs, d'où l'erreur de Wamp.

En outre, je crois qu'il y a un problème avec ce passage.

1
2
3
4
5
<?php
    foreach($champs as $b) {
        if(is_string($valeurChamps)) { $pdo_param = PDO::PARAM_STR; } else { $pdo_param = PDO::PARAM_INT; }
        $requete->bindValue(':'.$valeurChamps, $valeurDrapeau, $pdo_param);
    }

$valeurChamps, c'est la chaîne de tous les paramètres reliés par des virgules (via la fonction implode() un peu plus haut) : il te faut utiliser $b pour que ça marche.

+0 -0

$valeurDrapeau .= ' :'.$d.''; : pas de virgules entre les différentes valeurs (enfin, marqueurs, ici) ? Le plus simple, AMHA, pour le gérer c'est de passer par un tableau puis de faire un implode (comme tu l'as fait pour les noms des colonnes).

C'est effectivement $b qu'il faut binder pour le nom (inutile de rajouter les deux points, ils sont facultatifs).

Et closeCursor, placé, après return, ne sera jamais exécuté bien qu'on n'ait pas besoin de le faire avec INSERT.

Notes que si les noms des champs viennent de l'extérieur, tu te retrouves théoriquement avec des injections.

+1 -0

Bonjour, merci pour vos réponses. Du coup j'ai refait mon code, et il marche. Dans le traitement où il y a le formulaire je met ceci:

1
2
<?php
Article::insertInto('table', ['champs' => $valeur, 'champs2' => $valeur2, ...]);

Et dans ma class je met ceci:

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php
/**
* Méthode insertInto
* Pour les requetes préparées SQL INSERT INTO
*/
public static function insertInto($table, $data=[]) {

    $db = self::getPDO();

    $champs = '';
    $marqueurs = '';

      // Boucle pour :
      // _incrémenter les champs (ex: titre, contenu...)
      // _incrémenter marqueurs pour la req préparée
    foreach($data as $key => $value) {
        $champs .= ''.$key.', ';
        $marqueurs .= ':'.$key.', ';
    }

        // Enlever dernière virgule
    $champs = rtrim($champs, ', ');
    $marqueurs = rtrim($marqueurs, ', ');

    // ********** requete **********
    $statement = "INSERT INTO ".$table."(".$champs.")
                  VALUES(".$marqueurs.")";
    $requete = $db->prepare($statement);

    // Boucle pour incrémenter les bindValue avec les marqueurs de la requete préparée, avec les valeurs
    foreach($data as $key => $value) {
        if(is_string($value)) { $pdo_param = PDO::PARAM_STR; } else { $pdo_param = PDO::PARAM_INT; }
        $requete->bindValue(':'.$key, $value, $pdo_param);
    }

    $result = $requete->execute();
    // ********** /requete **********

    // fermer
    $requete->closeCursor();

    return $result;

}

Notes que si les noms des champs viennent de l'extérieur, tu te retrouves théoriquement avec des injections.

Merci de me l'avoir dit. Et peut tu m'expliquer pourquoi STP? Je débute en PHP POO. et vu que la je fait une requete préparée, je ne voit pas en quoi ce code est + risqué que d'y faire en procédural. Merci

+0 -0

vu que la je fait une requete préparée, je ne voit pas en quoi ce code est + risqué que d'y faire en procédural

Ca n'a aucun rapport que ce soit OO ou procédural (mysqli peut être utilisé de ces 2 façons et interface les requêtes préparées). Il y a injection parce que tu construis dynamiquement ta requête préparée alors qu'elles ne sont pas faites pour ; la requête, valeurs exclues, devrait être connue à l'avance. Or ce n'est pas le cas ici : tu injectes tes noms de table et colonnes directement dans la requête, ce qui constitue l'injection SQL si jamais ces éléments venaient à provenir de $_POST ou autre.

Il n'y aurait pas potentiellement injection si, au lieu d'être injecté directement, ils avaient fait l'objet de marqueurs, ce qui est malheureusement impossible : on ne peut pas substituer des identifiants SQL (noms de table, colonne, bdd) par des marqueurs. Dès lors, soit il faut hardcoder ses requêtes préparées, là, au moins, on respecte vraiment leur principe ; soit il faut protéger l'identifiant soi-même (du coup, le préparé perd un peu de son intérêt).

Avec MySQL et le sql_mode standard, ça donne :

1
'`' . str_replace('`', '``', $identifiant) . '`'
+0 -0

Ok merci de me l'avoir dit. sais tu comment je peut résoudre ce problème stp? (la j'ai du mal à te suivre, car j'ai très peu d'expérience en PHP).

Par exemple, peut tu me dire si cette requête (que j'avais fait en procédural) est bien faite (sans risque d'injections):

1
2
3
4
<?php
$requete = $db->prepare("INSERT INTO categories_articles(nom) VALUES(:nom)");
$requete->bindValue(':nom', $nom, PDO::PARAM_STR);
$requete->execute();

Merci beaucoup.

ok merci. je ne comprend toujours pas pourquoi ma method en POO serai moins sécurisée que ceci par exemple :

1
2
3
4
5
6
<?php
$statement = "INSERT INTO categories(nom, url) VALUES(:nom, :url)";
$requete = $db->prepare($statement);
$requete->bindValue(':nom', $nom, PDO::PARAM_STR);
$requete->bindValue(':url', $url, PDO::PARAM_STR);
$requete->execute();

dans cette exemple, en poo faudrait faire ceci où il y a le traitement (en POST) :

1
2
3
<?php
CategorieArticle::insertInto('categories', ['nom'=>$nom, 'url'=>$url])
// (dans cette exemple, le $statement est dans le method)

Si j'echo le $statement, dans le 2 cas, ça me donne exactement la même chose :

1
INSERT INTO categories_articles(nom, nom) VALUES(:nom, :url)

et en POO avec ma method, la requête est bien préparée aussi. Merci

+0 -0

Hello,

Le problème vient de cette ligne : $statement = "INSERT INTO ".$table."(".$champs.") VALUES(".$marqueurs.")";.

Pour l'instant, tu t'en sers correctement, c'est-à-dire que c'est toujours toi qui écris ce que vaut \$table et \$champs. Mais le jour où tu voudras faire un truc dynamique, tu auras un risque d'injection SQL.

Exemple : CategorieArticle::insertInto($_POST['TypeDeCategorie'], ['nom'=>$nom, 'url'=>$url])

Ce que dit vibrice, c'est donc soit de hardcoder les requêtes au niveau des tables et des colonnes utilisées (c'est-à-dire, coder une requête qui insère uniquement le nom de la table categories, une requête qui insère uniquement l'url, une requête qui insère les deux à la fois…), soit d'échapper les apostrophes dans tes variables $table et \$champ.

Perso, à première vue j'aurais plutôt hardcodé les champs autorisés, puis utilisé une fonction qui vérifie que ton insertInto() n'utilise que des champs autorisés.

à OK, je croyais que ce que voulais dire vibrice était au niveaux des $marqueurs. Et effectivement pour le moment c'est moi qui écrit ce que vaut $table et

$$champs. Faut effectivement que je prévoit le jour où $$
table et $champs seront traité via POST ou GET. Merci

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte