Bonjour,

[Tutoriel PHP : Développer un site : Jour 43, les routes]

En suivant le tutoriel, je constate que je n'obtiens pas l'affichage des menus de façon ordonnée. En effet, j'ai ceci :
Actualité Ma deuxième page Ma troisième page Ma quatrième page Ma cinquième page Ma première page Ma sixième page

Ma première page n'est pas en premier. J'ai décidé de modifier le fichier Model.php

$order = $this->primaryKey . " ASC"; // l'ordre dans lequel on veut afficher les menus
...
 if (isset($req'conditions'])) {
            $sql .= 'WHERE '; // on rajoute la clause WHERE
            // on vérifie qu'il s'agit bien d'un tableau avant d'effectuer le traitement            
            if (!is_array($req'conditions'])) { // si tel n'est pas le cas alors ...
                $sql .= $req'conditions']. ' GROUP BY ' . $this->primaryKey . ' ORDER BY ' . $order;
            } else {
                $cond = ]; // tableau qui stockera le couple clé=>valeur de la condition
                // pour parcourir l'ensemble des conditions
                foreach ($req'conditions'] as $key => $value) {
                    // pour éviter les injections SQL dans l'url du navigateur
                    if (!is_numeric($value)) { // si la valeur de la condition n'est pas un numérique alors ...
                        // on échappe la chaîne de caractère avec la méthode PD::quote()                        
                        $value = DBConnect::getBDD()->quote($value);
                    }
                    // on place nos valeurs dans le tableau crée pour les stocker
                    $cond] = "$key=$value";
                }
                // implode — Rassemble les éléments d'un tableau en une chaîne
                $sql .= implode(' AND ', $cond) . ' GROUP BY ' . $this->primaryKey . ' ORDER BY ' . $order;
            }
        }

Le problème vient du regroupement des enregistrements, en faisant un GROUP BY sur $this->primaryKey je n'ai plus ma pagination, ce qui est logique puisqu'il n'y a qu'un seul identifiant pour chaque post car il est unique.

Voici le message d'erreur :

ERROR: column "post.post_id" must appear in the GROUP BY clause or be used in an aggregate function at character 9
STATEMENT: SELECT * FROM posts as Post WHERE online=1 AND type='page' GROUP BY created ORDER BY post_id ASC

Peut-être ai-je manqué une partie dans le tutoriel?

Merci d'avance.
Sophonie.

16 réponses


sophonie
Auteur
Réponse acceptée

[Résolu]
Cherchant en vain comment modifer l'affichage de façon ordonnée, j'ai procédé différement de la sorte :

<?php
                        $cles = [2, 3, 4, 5, 1, 6]; // le désordre initiale que je constatais
                        $menu = ]; // pour stocker les couples clés/valeurs
                        for ($i = 0; $i < count($pagesMenu); $i++) {
                            // on rapatrie chaque clé avec sa valeur correspondante
                            $menu$cles$i]] = $pagesMenu$i]->name;
                            ksort($menu); // on trie les clés du tableau
                        }
                        //print_r($menu);
                        ?>
                        <?php foreach ($menu as $post_id => $name): ?>
                            <li>
                                <a href="<?php echo BASE_URL . '/pages/view/' . $post_id; ?>" 
                                   title="<?php echo $name; ?>"><?php echo $name; ?>
                                </a>
                            </li>                            
                        <?php endforeach; ?><strong></strong>
Xtr3me
Réponse acceptée

Un foreach à la place du for était peut être plus judicieux dans le cas futur où tu déciderais de rajouter des pages/articles.

$menu = ];
foreach($pagesMenu as $k => $v){
    $menu$v->id] = $v->name;
}
ksort($menu);

Si j'ai pas fait de bêtise ça devrait fonctionner ^^ . Ainsi tu pourras rajouter des articles plus tard sans avoir à changer ton tableau $clé puisqu'il n'existe plus et tu peux sortir le ksort de la boucle car tu fais du traitement inutile à chaque boucle. Alors qu'en le faisant en dehors de la boucle tu auras le même résultat mais plus optimisé car moins de calculs.

Peux-tu nous donner les ids de chaque article ?
Pour ton erreur ta table s'appelle bien posts ? Car dans le tutoriel les données sont cherchées dans la table ayant le même nom que le model mais en minuscules et au pluriel.
Or ici le préfixe de la colonne "post_id" est "post" ce qui laisse sous-entendre que la recherche n'est pas faite dans la bonne table.
Le GROUP BY sert à tout ce qui est statistiques par exemple savoir le nombre de bananes disponible dans un sac et dans un autre. Ce qui te renverras 10 bananes pour l'un et 5 pour l'autre par exemple.

sophonie
Auteur

Les articles ont les id 7 et 8
La table s'appelle posts (avec un s) et en minuscule.
Je viens juste de modifier le nom de la clé primaire en posts_id eu lieu de post_id car c'est plus logique en effet.

La recherche est bien faite dans la bonne table, le problème vient de l'aggrégation du GROUP BY.

Je parlais du préfixe pas de la colonne. Ce qui donnait posts.post_id et pour l'erreur tu as que deux id alors que tu as 4/5 articles ? L'id doit être en auto incrément. Essayes d'entourer la clé primaire dans le group by par une fonction agrégat. Genre:

" GROUP BY COUNT(".$this->primarykey.")

Sinon mets le COUNT dans le select.

sophonie
Auteur

J'ai probablement mal formulé ma réponse, voici ce que j'ai en Base :
6 enregistrements de type page -> 1 à 6 (qui sont les menus)
2 enregistrements de type post -> 7 et 8 (qui sont les articles)
les id sont de type SERIAL (PostgreSQL) -> AUTO INCRÉMENTE + SEQUENCE
En mettant une fonction d'aggrégat dans le GROUP BY, j'ai le message suivant :

ERROR: aggregates not allowed in GROUP BY clause at character 85
STATEMENT: SELECT * FROM posts as Post WHERE online=1 AND posts_id=2 AND type='page' GROUP BY COUNT(posts_id) ORDER BY posts_id ASC
ERROR: aggregates not allowed in GROUP BY clause at character 70
STATEMENT: SELECT * FROM posts as Post WHERE online=1 AND type='page' GROUP BY COUNT(posts_id) ORDER BY posts_id ASC

Ok essayes de mettre la fonction d'agrégat dans le SELECT:

"SELECT COUNT(*) "

par exemple.
Le code fonctionne sans le group by ? Je n'ai pas regardé cette partie du tutoriel donc je ne sais pas ce que fait Grafikart (Jonathan).
Je regarderai si le problème persiste ^^ . Parce que d'une j'utilise pas PostgreSQL et puis de deux je ne sais pas pourquoi le GROUP BY est utilisé dans ce cas.

Une chose que tu peux faire si t'as quelque chose de similaire à phpmyadmin c'est tester la requête directement sur la table et voir si rien qu'en SQL ça fonctionne et quelles lignes sont retournées.

sophonie
Auteur

J'ai mis la fonction d'aggrégat dans le SELECT et je n'ai pas de message d'erreur ... mais sans le GROUP BY par contre l'affichage des menus n'est pas dans l'ordre.
sous pgAdmin3 la requête suivante :

SELECT COUNT(posts_id) FROM posts WHERE type = 'post' GROUP BY posts_id ORDER BY posts_id;

me renvoi bien 2 résultats.

Ok et l'ordre est respecté ? Je vais regarder le tutoriel demain pour l'ordre. Normalement avec un order by post_id si c'est incrémenté il devrait pas y avoir de problème.

sophonie
Auteur

Je suis d'accord avec toi Xtr3me mais il semblerai (et je vais continuer à chercher sur le web) qu'un ORDER BY ne peut se faire sans un GROUP BY au préalable.

J'ai pas de GROUP BY et mon ORDER BY fonctionne très bien ^^ .
Le GROUP BY te sert juste à regrouper les données à l'aide d'un champ par exemple regrouper toutes les chambres d'un hôtel par un étage, tu récupères ainsi le nombre de chambre par étage.
Tandis que le ORDER BY comme son nom l'indique permet d'ordonner les données soit dans un ordre croissant soit dans un ordre décroissant. ASC étant l'ordre croissant tout va bien et DESC l'ordre décroissant.
A première vue aucun problème sur la requête SQL mis à part le GROUP BY qui nécessite une fonction d'agrégat dans la requête sql.

Pourrais-tu nous passer le code de ton menu ou ce qui en est lié ? Pour voir si le problème ne vient pas de là. Tu peux aussi dans le controller faire un debug/print_r du résultat pour voir les données retournées à la vue.

sophonie
Auteur

En effet, le ORDER BY n'a pas besoin forcément d'un GROUP BY par contre dans le cas de figure de mon code (dû moins celui de Grafikart), je ne peux pas faire différemment.
En faisant un

debug($page);

dans index.php, j'obtiens ceci :

/mod/www/vignoble/view/posts/index.php ligne 22
/mod/www/vignoble/core/Controller.php ligne 47
/mod/www/vignoble/core/Dispatcher.php ligne 41
/mod/www/vignoble/webroot/index.php ligne 25

Voici le code du menu :

<!DOCTYPE html>
<html>
    <head>         
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />        
        <title><?php echo isset($title_for_layout) ? $title_for_layout : 'Le Blog des vignerons'; ?></title>         
        <link rel="stylesheet" href=" http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css">
    </head>
    <body>
        <div class="topbar">
            <div class="topbar-inner">
                <div class="container">
                    <h3><a href="#">Le Blog des vignerons</a></h3>
                    <ul class="nav">
                        <?php $pagesMenu = $this->request('Pages', 'getMenu'); ?>
                        <li><a href="<?php echo BASE_URL. '/posts'; ?>">Actualité</a></li>
                        <?php foreach ($pagesMenu as $p): ?>
                            <li>
                                <a href="<?php echo BASE_URL . '/pages/view/' . $p->posts_id; ?>" 
                                   title="<?php echo $p->name; ?>"><?php echo $p->name; ?>
                                </a>
                            </li>
                        <?php endforeach; ?>                            
                    </ul>
                </div><!-- container -->
            </div><!-- topbar-inner -->
        </div><!-- topbar -->            
        <div class="container" style="padding-top: 60px; ">
            <div class="hero-unit">
                <?php echo $content_for_layout; ?>
            </div><!-- container -->
        </div>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
    </body>        
</html>

Je viens de me faire opérer désolé de ne pas avoir répondu plus tôt mais par politesse je réponds tout de même ;) .
Le debug() ne renvoie rien ce qui est étrange. Mets un debug($this->pagesMenu); ou debug($pagesMenu); devant le foreach de ta vue histoire de voir toutes les données retournées par cette objet/tableau.
A première vue aucun problème du côté de ta vue je n'ai pas suivi cette partie du tutoriel étant donné qu'elle m'était inutile j'ai procédé autrement.

Je regarderai le tutoriel si le debug(); ne nous apprends toujours rien ^^ .

Voilà pour toi quelques exemples de GROUP BY avec PostgreSQL si jamais ça peut t'être utile.

sophonie
Auteur

Voici ce que j'obtiens :

/mod/www/vignoble/view/layout/default.php ligne 16
/mod/www/vignoble/core/Controller.php ligne 49
/mod/www/vignoble/core/Dispatcher.php ligne 41
/mod/www/vignoble/webroot/index.php ligne 25

Etrange car aucune donnée n'est affichée par le debug() hors si le titre s'affiche bien il y a déjà cette donnée qui passe. Ta fonction debug() est bien codée ?
Sinon remplace-la par un print_r();

sophonie
Auteur
function debug($var) {
    if (Conf::$debug > 0) {
        $debug = debug_backtrace();
        echo '<p><a href="#" onclick="$(this).parent().next(\'ol\').slideToggle(); return false;">
        <strong>' . $debug[0]'file'] . '</strong> 
            ligne ' . $debug[0]'line'] . '</a></p>';
        echo '<ol>';
        foreach ($debug as $k => $v) {
            if ($k > 0) {
                echo '<li><strong>' . $v'file'] . '</strong> 
            ligne ' . $v'line'] . '</li>';
            }
        }
        echo '</ol>';
        echo '<pre>';
        print_r($var);
        echo '</pre>';
    }
}

un print_r affiche la même chose.