Bonjour,

Voila je rencontre un petit problème avec mon code.

Ce que je fais

J'ai suivi le tutoriel concernant la réalisation de commentaires en POO, ainsi que la formation POO avec la réalisation du blog.
J'aimerai lier les deux systèmes (système de commentaires sur mes articles).
Cependant la structure MVC me pose problème, je suis complètement perdu ... j'ai revu les tutoriels 2 ou 3 fois pourtant ...
Je ne comprends pas à quoi sert concrètement les entités, j'ai du mal à visualiser la séparation avec controller / modèle, bien que j'arrive à comprendre les explications pour passer du procédural au MVC.
Pour l'instant j'ai posé la classe Comments en tant que CommentTable dans le dossier App\Table (bien que je comprennes que ce n'est pas le bon moyen ....je n'arrive pas à faire mieux).

<?php

// To improve: replace email and username by userid (LEFT JOIN ?)
// header location

namespace App\Table;

use Core\Table\Table;

class CommentTable extends Table{

    protected $db;
    private $options = [
      'username_error' => 'Vous n\'avez pas rentré votre pseudo',
      'email_error' => 'Vous n\'avez pas correctement rentré votre email',
      'content_error' => 'Vous n\'avez pas correctement rentré votre commentaire',
      'parent_error' => "Vous essayez de répondre à un commentaire qui n'existe pas"
    ];
    public $errors = [];

    public function __construct($db, $options = []){
        $this->db = $db;
        $this->options = array_merge($this->options, $options);
    }

    public function findAll($ref, $ref_id){
        $comments = $this->query('SELECT * FROM comments WHERE ref_id = ? AND ref = ? ORDER BY created DESC', [$ref_id, $ref]);
        $replies = [];
        foreach($comments as $k => $comment){
            if($comment->parent_id){
                $replies[$comment->parent_id][] = $comment;
                unset($comments[$k]);
            }
        }
        foreach($comments as $k => $comment){
            if(isset($replies[$comment->id])){
               $r = $replies[$comment->id];
                usort($r, [$this,'sortReplies']);
               $comments[$k]->replies = $r;
            }
            else{
                $comments[$k]->replies = [];
            }
        }
        return $comments;
    }

    // inverser l'ordre des commentaires réponses
    public function sortReplies($a, $b){
        $atime = strtotime($a->created);
        $btime = strtotime($b->created);
        return $btime > $atime ? -1 : 1;
    }

    public function redirectComment($id){
        header('Location: index.php?p=posts.view&slug='. $id);
    }

    public function save($ref, $ref_id){

        $errors = [];
        if(empty($_POST['username'])){
            $errors['username'] = $this->options['username_error'];
        }
        if(empty($_POST['email']) || !filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)){
            $errors['email'] = $this->options['email_error'];
        }
        if(empty($_POST['content'])){
            $errors['content'] = $this->options['content_error'];
        }

        if(count($errors) > 0){
            $this->errors = $errors;
            return false;
        }

        // Verification du parent_id
        if(!empty($_POST['parent_id'])){

            $req = $this->query("
                SELECT id FROM comments
                WHERE ref = ? AND ref_id = ? AND id = ? AND parent_id = 0",
                [$ref, $ref_id, $_POST['parent_id']], true);

            // Si le commmentaire auquel on essaye de répondre existe

            if(!$req){
                $this->errors['parent'] = $this->options['parent_error'];
                return false;
            }
        }

        return $this->query("
            INSERT INTO comments
            SET username = ?,
            email = ?, content = ?, created = NOW(), ref = ?, ref_id = ?, parent_id = ?",
            [$_POST['username'], $_POST['email'], $_POST['content'], $ref, $ref_id, $_POST['parent_id']], true);

    }

}

Ce que j'obtiens

Premièrement j'ai du changer if($req->rowCount() <= 0) en if(!$req) car sinon j'avais une erreur, le resultat semble le même mais je le précise on sait jamais.
Lorsque je poste un commentaire, l'erreur est la suivante : Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY000]: General error' in C:\wamp\www\Blog2\core\Database\MysqlDatabase.php on line 78
( ! ) PDOException: SQLSTATE[HY000]: General error in C:\wamp\www\Blog2\core\Database\MysqlDatabase.php on line 78

(La ligne 78 étant : if($one == false){ $data = $req->fetchAll(); } )

Le commentaire se poste bien cependant !!

Voici mon MysqlDatabase.php

<?php

namespace Core\Database;
use \PDO;

class MysqlDatabase extends Database
{
    protected $db_name;
    protected $db_host;
    protected $db_user;
    protected $db_pass;
    protected $pdo;

    public function __construct($db_name, $db_user = 'root', $db_pass ='', $db_host = 'localhost'){

        $this->db_name = $db_name;
        $this->db_host = $db_host;
        $this->db_user = $db_user;
        $this->db_pass = $db_pass;

    }

    // array( PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
    public function getPDO(){

        if($this->pdo === null){
            $pdo = new PDO('mysql:dbname='.$this->db_name.';host='.$this->db_host.'', $this->db_user, $this->db_pass);
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->pdo = $pdo;
        }
        return $this->pdo;
    }

    public function query($statement, $class_name = null, $one = false){
        $req = $this->getPDO()->query($statement);

        if(
            strpos($statement, 'UPDATE') === 0 ||
            strpos($statement, 'INSERT') === 0 ||
            strpos($statement, 'DELETE') === 0
        ){
            return $req;
        }

        // Association fetchClass
        if($class_name === null){$req->setFetchMode(PDO::FETCH_OBJ);}
        else{$req->setFetchMode(PDO::FETCH_CLASS, $class_name);}

        // Choix du fetch
        if($one == false){ $data = $req->fetchAll(); }
        else{ $data = $req->fetch(); }

        return $data;
    }

    public function prepare($statement, $attributes, $class_name = null, $one = false){

        $req = $this->getPDO()->prepare($statement);
        $res = $req->execute($attributes);

        if(
            strpos($statement, 'UPDATE') === 0 ||
            strpos($statement, 'INSERT') === 0 ||
            strpos($statement, 'DELETE') === 0
        ){
            return $res;
        }

          //  var_dump($statement,  $attributes, $class_name, $one);

        // Association fetchClass
        if($class_name === null){$req->setFetchMode(PDO::FETCH_OBJ);}
        else{$req->setFetchMode(PDO::FETCH_CLASS, $class_name);}

        // Choix du fetch
        if($one == false){ $data = $req->fetchAll(); }
        else{ $data = $req->fetch(); }

        return $data;
    }

    public function lastInsertId(){
        return $this->getPDO()->lastInsertId();
    }

}

Ce que je veux

Le problème vient peut être du fait que si je var_dump($statement), j'objtiens 2 $statement :

string '
        SELECT articles.id, articles.titre, articles.contenu, articles.date, categories.titre as categorie, articles.category_id
        FROM articles
        LEFT JOIN categories ON category_id = categories.id
        WHERE articles.id = ?
        ' (length=225)
string '
            INSERT INTO comments
            SET username = ?,
            email = ?, content = ?, created = NOW(), ref = ?, ref_id = ?, parent_id = ?' (length=154)

Ainsi la condition

if(
            strpos($statement, 'UPDATE') === 0 ||
            strpos($statement, 'INSERT') === 0 ||
            strpos($statement, 'DELETE') === 0
        )

n'est pas prise en compte et ca bug ... Je pense (je ne suis sur de rien)... Si c'est cela, je ne vois pas comment arranger le problème :/

Mon fichier show.php est le suivant:

<?php

use App\Table\CommentTable;
use \App;

$comments_cls = new CommentTable(App::getInstance()->getDb());

// soumission d'un commentaire
$errors = false;
 $success = false;

if(isset($_POST['action']) && $_POST['action'] == 'comment'){
   $save = $comments_cls->save('articles', $post->id);

    if($save){

         $comments_cls->redirectComment($post->id);
         $success = true;
    }
    else{
        $errors = $comments_cls->errors;

    }
}

$comments = $comments_cls->findAll('articles', $post->id);
?>

    <h2><?= $post->titre; ?></h2>
    <p><em><?= $post->categorie; ?></em></p>
    <?= nl2br($post->contenu); ?>

    <h2><?php echo count($comments) ?> commentaires</h2>

    <form action="" role="form" method="post" id="comment">
        <div class="row">
            <div class="col-xs-6">
                <div class="form-group">
                    <label>Pseudo</label>
                    <input type="text" class="form-control" name="username">
                </div>
            </div>
            <div class="col-xs-6">
                <div class="form-group">
                    <label>Email</label>
                    <input type="email" class="form-control" name="email">
                </div>
            </div>
            <div class="col-xs-12">
                <div class="form-group">
                    <label>Commentaire</label>
                    <textarea name="content" class="form-control"></textarea>
                </div>
                <button type="submit" class="btn ctn-primary">Envoyer</button>
            </div>
            <input type="hidden" name="parent_id" value="0" id="parent_id">
            <input type="hidden" name="action" value="comment">
        </div>
    </form>

    <?php foreach($comments as $comment): ?>

        <?php require(ELEMENTS . 'comment.php') ?>
        <?php foreach($comment->replies as $comment): ?>
            <div style="margin-left: 100px;">
                <?php require ELEMENTS . 'comment.php'; ?>
            </div>
        <?php endforeach; ?>

    <?php endforeach; ?>

Merci d'avance !

4 réponses


ToToSe
Réponse acceptée

Yo, tu souhaite return ta variable $req et sauter le fetch uniquement si le statement est un INSERT, UPDATE, DELETE ;), ta condition est inversée, il te sufit de la remplacée par :

        strpos($statement, 'UPDATE') !== false ||
        strpos($statement, 'INSERT') !== false ||
        strpos($statement, 'DELETE') !== false

Trop long le code, et trop la flemme de le lire en détail x) ( le meilleur code est toujours le plus simple et le plus facile à lire, petite astuce ).

Le MVC : il se passe les trucs suivants dans la vie d'Internet, des gens demandent des pages à des ordinateurs quelque part et ces ordinateurs renvoient ces pages.
Le design pattern Modèle Vue Controlleur, un grand classique, est une façon possible de faire en sorte que cette magie opère. C'est très simple ( si on reste en surface ) : le Controlleur chapotte tout le merdier, quand tu demande une page, c'est lui qui sait quelle page il faut construire et comment il faut la construire. Pour ça, il va faire deux choses : aller chercher les données que tu veux voir, et les mettre dans une belle mise en forme canon pour que ça te pique pas les yeux et parce que t'es pas une machine. Les données, il va les demander au Modèle, qui se charge de tout ce qui est données. Quand il les a, il les injecte dans la vue, et le Sacro Saint FrameWork renvoie tout ça dans ton navigateur.
Une recommendation fréquente dans l'industrie est la suivante : "Fat Models, Skinny Controllers".
En substance, tes controlleurs ne devraient jamais être très développé ( pour des applications "simple" ). Tes modèles par contre, seront bien chargés. Tout ce qui est requête est écrit à l'intérieur du Modèle, dans des méthodes qui sont appelées par l'extérieur. C'est très important, parce que des requêtes d'après le MVC n'ont pas du tout leur place en dehors du Modèle. Si tel était le cas, on ne serait plus dans un environnement MVC strict, ce qui implique qu'on a moins d'emprise sur le code et qu'on contrôle moins l'application.

Tu peux aussi voir le Modèle comme la représentation en langage serveur de la structure de tes données et de ses interactions, telles qu'elles sont décrites par les possibilités de ton moteur de bdd ( Mysql en l'occurence).

"Le problème vient peut être du fait que si je var_dump($statement), j'objtiens 2 $statement :"

Et tu devrais clairement pas.
Fais l'essai sur une page isolée avec comme seul code de quoi exécuter une requête PDO avec double statement, tu verras bien le résultat.

Tu répète la condition :
"if(
strpos($statement, 'UPDATE') === 0 ||
strpos($statement, 'INSERT') === 0 ||
strpos($statement, 'DELETE') === 0
)"

Pour commencer, la répétition, c'est mal ( l'intérêt du code c'est de ne rien répéter ). Ensuite, en gros ta condition a pour but de vérifier qu'on est forcément dans une requête SELECT. Donc autant être direct ;).

"Premièrement j'ai du changer if($req->rowCount() <= 0) en if(!$req) car sinon j'avais une erreur, le resultat semble le même mais je le précise on sait jamais."
Ces conditions ne sont pas strictement équivalentes. Pour vraiment en finir avec tes bugs, il faut que tu sois rigoureux dans ton nettoyage, ton remplacement ici c'est un pansement, pas une résolution. Il faut que tu comprennes pourquoi la première condition générait une erreur.

Quant à ton erreur fatale, ça nous dit que le statement de la requête sql est archi pourri. Pdo te permet de récupérer des détails sur les erreurs, ça serait bien que tu les partage.

Et suggestion qui tue :

// Choix du fetch
if($one == false){ $data = $req->fetchAll(); }
else{ $data = $req->fetch(); }

Devient :

$data = ( ! $one ) ? $req -> fetchAll() : $req -> fetch();

C'est un ternaire, seuls les élus se servent de ce kung fu.

Kboirel
Auteur

Merci énormement poru vos réponses !!
@Noire Munich Tous tes conseils me seront d'une grande utilité !! Tu as raison sur toute la ligne :p
@ToToSe De même ! le problème venait de là, et peut être résolu comme cela, ou comme tu le l'a conseillé avec un

$statement = trim($statement);

        if(
            strpos($statement, 'UPDATE') === 0 ||
            strpos($statement, 'INSERT') === 0 ||
            strpos($statement, 'DELETE') === 0
        ){
            return $res;
        }

Merci encore !! :)