Bonjour,

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

Ce que je fais

Décrivez ici votre code ou ce que vous cherchez à faire
J'ai créé une table catégorie qui regouperait les catégories et les sous-catégories. Pour définir les sous-catégories, j'ai ajouté un champs parent_id qui aurait une valeur identique à l'id d'une catégorie principale à laquelle je voudrais la rattacher. Les catégories principales ont un parent_id égal à NULL.
Mon but est de récupérer les sous catégories d'une catégorie quand je clique dessus. Ce qui me pose problème, c'est la requête. Je suis en mvc et en quelque sorte une requête classique comme ci-dessous ne marche pas.

$this->loadModel('Categorie');
    $condition = array('Categorie.online'=>1,'Categorie.parent_id'=>'Categorie.id');
    return $this->Categorie->find(array(
        'conditions' => $condition,
        'fields'=>'Categorie.id','Categorie.slug','Categorie.name'
        ));

Après avoir lu la doc il semble qu'il faille passer par une requête avec auto-jointure qui aurait, selon la doc, une structure un peu comme celle-là après avoir essayer de l'adapter au mvc :

$this->loadModel('Categorie') AS t1;
            $condition = array('t2.online'=>1, 't2.parent_id'=>'t1.id');
            return $this->t1->find(array(
            'conditions' => $condition,
            'fields' => 't2.id AS id_enfant, t2.slug AS slug_enfant, t2.name AS name_enfant',
            'join'  =>  array('categories AS t2'=>'t1.id=t2.parent_id'),
             ));

Ce que je veux

Mon objectif est de récupérer les sous-catégories.
Avec la première méthode, je ne récupère rien. Avec la deuxième méthode j'ai cette erreur

Ce que j'obtiens

 Parse error: syntax error, unexpected 'AS' (T_AS)

Merci d'avance pour votre aide.

32 réponses


C'est quoi ton ORM ?

tikoum
Auteur

C'est quoi un ORM ?

ORM pour Object Relation Mapping est l'ensemble de classe te permettant de faire le parallèle entre ta base de donnée et ton code OOP.
Quel est la classe de l'attribut $this->Categorie Est ce une librairie externe ? si oui laquelle, si non montre nous tes classes. Tu parles aussi de doc dans ton post mais tu ne nous la donne pas. On ne peut deviner quel est ta base, ce que tu as etc...

tikoum
Auteur

J'ai fais mon code en suivant le tuto de grafikart un site en 7 jours et je suis qu'un autodidacte, donc je vais essayer de répondre. Je comprends en te lisant que tu parles de la fonction find qui fait la relation entre la DB et traite la requête. La voici :

public function find($req = array()){   
        $sql = ' SELECT ';

        if(isset($req['fields'])){//on sélectionne le champ fields
            if(is_array($req['fields'])){
                $sql .= implode(', ',$req['fields']);
            }else{
                $sql .= $req['fields'];
            }
        }else{
            $sql .= '*';
        }

    $sql .= ' FROM '.$this->table.' as '.get_class($this).' ';//on renomne la table pour auto-deviner le nom du modèle  

        if(isset($req['join'])){
            foreach ($req['join'] as $k => $v) {
                $sql .= 'LEFT JOIN '.$k.' ON '.$v.' ';
            }
        }

        //construction de la condition
        if(isset($req['conditions'])){
            $sql .= 'WHERE ';
            if(!is_array($req['conditions'])){
                $sql .= $req['conditions'];
            }else{
                $cond = array();
                foreach($req['conditions'] as $k => $v){
                    if(!is_numeric($v)){
                        $v = '"' .addslashes($v).'"';
                    }
                    $cond[] = "$k=$v";
                }
                    $sql .= implode(' AND ',$cond);
            }

        }

        if(isset($req['order'])){
            $sql .= ' ORDER BY '.$req['order'];
        }

        if(isset($req['limit'])){
            $sql .= ' LIMIT '.$req['limit'];
        }
        //print_r($sql);
        $pre = $this->db->prepare($sql);
        $pre->execute();
        return $pre->fetchAll(PDO::FETCH_OBJ);//on récupère la requête ss forme d'objet
    }

Est ce bien cela que tu voulais ?
Perso, je pense que la requête n'est pas adaptée à une auto jointure. Malheureusement, je ne sais pas faire.

tikoum
Auteur

PS: La classe de l'attribut $this->Categorie est CategoriesController
La doc est celle que j'ai pu consulter sur internet (site sqlpro)

Je viens de comprendre ton problème ...

$this->loadModel('Categorie') AS t1; // Retire ici le AS t1
            $condition = array('t2.online'=>1, 't2.parent_id'=>'t1.id');
            return $this->t1->find(array(
            'conditions' => $condition,
            'fields' => 't2.id AS id_enfant, t2.slug AS slug_enfant, t2.name AS name_enfant',
            'join'  =>  array('categories AS t2'=>'t1.id=t2.parent_id'),
             ));

enfin si j'ai bien compris ton code, ça doit ressembler à ça :

<?php

$this->t1->find([
    'conditions' => [
        't2.online' => 1,
        't2.parent_id' => $category_id //id de $this->t1 ?
    ],
    'fields' => 't2.id AS id_enfant, t2.slug AS slug_enfant, t2.name AS name_enfant',
    'join' => [
        'categories AS t2' => 't1.id=t2.parent_id'
    ],
]);
tikoum
Auteur

Merci pour ton retour. J'ai testé ton code.
J'ai les erreurs suivantes :

Notice: Undefined variable: category_id in C:\wamp\www\test.dev\controller\PostsController.php on line 107
 Notice: Undefined property: PostsController::$t1 in C:\wamp\www\test.dev\controller\PostsController.php on line 108
 Fatal error: Call to a member function find() on a non-object in C:\wamp\www\test.dev\controller\PostsController.php on line 108

Je ne comprends pas d'où vient category_id (je n'ai pas un tel champ dans ma table Categorie).

tikoum
Auteur

J'ai réessayé ton code (apparemment j'ai du mal placé quelque chose), voici les erreurs:

Notice: Undefined property: PostsController::$t1 in C:\wamp\www\test.dev\controller\PostsController.php on line 105
 Fatal error: Call to a member function find() on a non-object in C:\wamp\www\test.dev\controller\PostsController.php on line 105

J'ai voulu reprendre ton code, mais en gros ton ORM (maintenant que tu sais ce que c'est :D) doit avoir une méthode type :

$this->Categorie->find

C'est lui qui doit appeler le snippet.

->find([
    'conditions' => [
        't2.online' => 1,//Que ceux qui sont en ligne
        't2.parent_id' => $category_id // l'id de catégorie pour laquelle tu veux les enfants.
    ],
    'fields' => 't2.id AS id_enfant, t2.slug AS slug_enfant, t2.name AS name_enfant', //les champs que tu veux.
    'join' => [
        'categories AS t2' => 't1.id=t2.parent_id' //la jointure, même si elle est pas obligatoire si tu connais déjà l'id de la catégories parents.
    ],
]);

Il ne faut pas simpletmenet copier coller, mais essayer de comprendre.

tikoum
Auteur

Bien sur que j'essaye de comprendre. Néanmoins, je ne m'en sors pas, J'ai initialisé l'objet t1, mais maintenant j'ai les erreurs suivantes :

Notice: Undefined variable: categorie_id in C:\wamp\www\test.dev\controller\PostsController.php on line 109
Warning: PDOStatement::execute(): SQLSTATE[42S22]: Column not found: 1054 Champ 't1.id' inconnu dans on clause in C:\wamp\www\test.dev\core\Model.php on line 100

Il est possible que la méthode find soit incomplète pour traiter l'auto-jointure. J'avais déja tester ma fonction en y implémentant dans les conditions par exemple l'id de la catégorie principale, comme ceci parent_id => 13 et ça fonctionne. Mais comment traiter cela de manière dynamique quand on a plein de catégories et sous-catégories, d'où l'auto jointure.

$category_id correspond à la catégorie que tu veux... Tu m'as dis que tu veux récuperer les sous catégorie quand tu cliques sur une catégorie !
tu as donc quelques choses comme $category_id = $_GET['category']; ?

Sinon essaye juste ce code là :

<?php

/**
 * Fait ta requête qui equivaut à : SELECT * FROM category WHERE online = 1;
 * Tu auras un Array tel que ci-dessous :
 **/
$categories = [
    [
        'id' => 1,
        'parent_id' => null,
        'title' => 'Cat 1'
    ],
    [
        'id' => 2,
        'parent_id' => 1,
        'title' => 'Cat 1-2'
    ],
    [
        'id' => 3,
        'parent_id' => 2,
        'title' => 'Cat 2-3'
    ],
    [
        'id' => 4,
        'parent_id' => 1,
        'title' => 'Cat 1-4'
    ],
        [
        'id' => 5,
        'parent_id' => null,
        'title' => 'Cat 5'
    ],
    [
    'id' => 6,
        'parent_id' => 5,
        'title' => 'Cat 5-6'
    ]
];
/**
 * Fonction servant à construire ton arbre de catégories tel que dump ci-dessous.
**/
function buildSummaryTree($categories,$category_id = null){
    $output = [];
    foreach ($categories as $key => $category) {
        if ($category['parent_id'] == $category_id) {
            $output[$category['id']] = $category;
            $output[$category['id']]['child'] = buildSummaryTree($categories,$category['id']);
        }
    }
    return $output;
}

var_dump(buildSummaryTree($categories));

/**
array(2) {
  [1]=>
  array(4) {
    ["id"]=>
    int(1)
    ["parent_id"]=>
    NULL
    ["title"]=>
    string(5) "Cat 1"
    ["child"]=>
    array(2) {
      [2]=>
      array(4) {
        ["id"]=>
        int(2)
        ["parent_id"]=>
        int(1)
        ["title"]=>
        string(7) "Cat 1-2"
        ["child"]=>
        array(1) {
          [3]=>
          array(4) {
            ["id"]=>
            int(3)
            ["parent_id"]=>
            int(2)
            ["title"]=>
            string(7) "Cat 2-3"
            ["child"]=>
            array(0) {
            }
          }
        }
      }
      [4]=>
      array(4) {
        ["id"]=>
        int(4)
        ["parent_id"]=>
        int(1)
        ["title"]=>
        string(7) "Cat 1-4"
        ["child"]=>
        array(0) {
        }
      }
    }
  }
  [5]=>
  array(4) {
    ["id"]=>
    int(5)
    ["parent_id"]=>
    NULL
    ["title"]=>
    string(5) "Cat 5"
    ["child"]=>
    array(1) {
      [6]=>
      array(4) {
        ["id"]=>
        int(6)
        ["parent_id"]=>
        int(5)
        ["title"]=>
        string(7) "Cat 5-6"
        ["child"]=>
        array(0) {
        }
      }
    }
  }
}
**/
tikoum
Auteur

Je te remercie pour ton message. Je vais regarder cela et te tiens informé.

tikoum
Auteur

"tu as donc quelques choses comme $category_id = $_GET['category']; ?"
Non, je n'ai rien de tel dans mon code.
J'ai porté cette ligne de code dans ma fonction mais j'ai cette erreur

Notice: Undefined index: category in C:\wamp\www\test.dev\controller\PostsController.php on line 106
tikoum
Auteur

J'ai aussi cette erreur récurente:

Warning: PDOStatement::execute(): SQLSTATE[42S22]: Column not found: 1054 Champ 't1.id' inconnu dans on clause in C:\wamp\www\test.dev\core\Model.php on line 100

Le t1 n'est pas reconnu dans la requête qui se trouve dans la fonction find. Je pense qu'il faut l'implémenter pour prendre le cas de l'auto jointure.

As tu simplement essayé d'ecrire ta requête dans PhpMyAdmin ? tout tes soucis semble venir de ton PHP et non de ta requête en elle même.
Décompose tes problèmes en petit problème, sinon tu t'en sortiras jamais.

Si tu as t1.id inconnu, ça veut dire que l'alias que tu lui donnes n'est pas celui là .. Tout simplement.

tikoum
Auteur

Yanis, je te remercie sincèrement d'avoir bien voulu te pencher sur ce problème mais je jettes l'éponge. Un moment donné j'étais vraiment pas loin. Mais là je suis à bout.
En tout cas Merci.

Partage ton code et ne baisse pas les bras :) tu apprends c'est normal d'avoir des difficultés

tikoum
Auteur

Bonjour Yanis,
Voici ma requete sur php et mon erreur (toujours la même d'ailleurs. Si tu peux y jeter un oeil.

SELECT * FROM categories as Categorie WHERE slug="financement" AND id=11 SELECT Post.id, Post.name, Post.slug, Post.content, Category.name as catname FROM posts as Post LEFT JOIN categories as Category ON Post.category_id=Category.id WHERE Post.online=1 AND Post.type="post" AND category_id=11 ORDER BY Post.created DESC LIMIT 0,3 SELECT COUNT(id) as count FROM posts as Post WHERE Post.online=1 AND Post.type="post" AND category_id=11 SELECT t2.id AS id_enfant, t2.slug AS slug_enfant, t2.name AS name_enfant FROM categories as Categorie LEFT JOIN categories AS t2 ON t1.id=t2.parent_id WHERE t2.online=1 AND t2.parent_id="t1.id"

Bien à toi.

là en fait, tu as 3 requètes ;)

SELECT * FROM categories as Categorie WHERE slug="financement" AND id=11 
SELECT Post.id, Post.name, Post.slug, Post.content, Category.name as catname FROM posts as Post LEFT JOIN categories as Category ON Post.category_id=Category.id WHERE Post.online=1 AND Post.type="post" AND category_id=11 ORDER BY Post.created DESC LIMIT 0,3 
SELECT COUNT(id) as count FROM posts as Post WHERE Post.online=1 AND Post.type="post" AND category_id=11 
SELECT t2.id AS id_enfant, t2.slug AS slug_enfant, t2.name AS name_enfant FROM categories as Categorie LEFT JOIN categories AS t2 ON t1.id=t2.parent_id WHERE t2.online=1 AND t2.parent_id="t1.id"

même pas séparé pas des points virgules.

il faut lui lancer une requète pas une requète

@plus

Pierre

tikoum
Auteur

Salut Pierre,

Désolé de répondre si tardivement mais niveau emploi du temps c'était plus que short.
c'était normal que j'avais 3 requêtes car ma fonction faisait plusieurs choses. J'ai donc isolé la requête qui nous intéressais, retesté et e retravaillé le code mais toujours le même problème d'alias. D'ailleurs on peu voir ds la requête ci dessous qu'il donne bien l'alias t2 à categories mais pas à t1.

SELECT t2.id AS id_enfant, t2.slug AS slug_enfant, t2.name AS name_enfant FROM categories as Categorie LEFT JOIN categories AS t2 ON t1.id=t2.parent_id WHERE t2.online=1 AND t2.parent_id=""

Qui plus est le $category_id stipulé dans les conditions :

'conditions' => ['t2.online' => 1,'t2.parent_id' => $category_id],

n'est pas reconnu.

Ta requête est pas correcte :

SELECT 
    t2.id AS id_enfant, 
    t2.slug AS slug_enfant, 
    t2.name AS name_enfant 
FROM categories as Categorie 
LEFT JOIN categories AS t2 
    ON t1.id = t2.parent_id 
WHERE t2.online = 1 AND t2.parent_id = ""

Si on passe le t2.parent_id = "" (Ca, c'est un petit soucis d'algorithmique côté ORM), tu fais une jointure avec comme condition t1.id = t2.parent_id alors que tu n'as déclaré nul part t1, par contre, tu déclares Categorie et t2.

tikoum
Auteur

C'est bien cela le problème ; j'ai bien essayé de l'initilaliser en objet au dessus :

$this->t1 = 'categories';

ça ne fonctionne pas. et quand j'initialise pas, il me retourne:

Fatal error: call a member function find() on a non object

j'ai bien compris que t1 n'était pas déclaré mais je ne vois pas comment la déclarer.

Change juste ta jointure... C'est pas parceque tu appelles ton objet "T1" que ça change l'alias en T1 dans ta requête...

$this->loadModel('Categorie');
$condition = array('t2.online'=>1, 't2.parent_id'=>'Categorie.id');
return $this->Categorie->find(array(
  'conditions' => $condition,
  'fields' => 't2.id AS id_enfant, t2.slug AS slug_enfant, t2.name AS name_enfant',
  'join'  =>  array('categories AS t2'=>'Categorie.id=t2.parent_id'),
));
tikoum
Auteur

Ok Kareylo, je vais tester ta requête dès que je me libère et te tiens informé. Merci

tikoum
Auteur

Non, ça ne me retourne rien. Normalement, la requête devrait me retourner 2 sous-catégories auxquelles j'ai donnée un parent_id égale à 11, mais là rien . Voici ma requête sql:

SELECT t2.id AS id_enfant, t2.slug AS slug_enfant, t2.name AS name_enfant FROM categories as Categorie LEFT JOIN categories AS t2 ON Categorie.id=t2.parent_id WHERE t2.online=1 AND t2.parent_id="Categorie.id" 

Tu cherche la catégorie qui a pour id "Categorie.id" et non un id de catégorie

tikoum
Auteur

Je ne comprends pas. Je précise dans mes conditions que parent_id a une valeur identique à l'id de la catégorie principale (qui elle a un parent_id NULL. Je souhaite que la recherche soit dynamique.

Et si tu fais comme ça ?

$this->loadModel('Categorie');
return $this->Categorie->find(array(
  'conditions' => array('t2.online' => 1),
  'fields' => 't2.id AS id_enfant, t2.slug AS slug_enfant, t2.name AS name_enfant',
  'join' => array('categories AS t2' => 'Categorie.id=t2.parent_id'),
));

sans le ; à la ligne 'conditions' => array('t2.online' => 1);,

tikoum
Auteur

Salut,
Ca me retourne pour chacun des select undefined property : stdClass
Soit il manque quelque chose dans la requête ou c'est la variable $id = $_GET['id'] qui manque quelque part pour reconnaitre l'id qui est appelé.

j'ai trouvé ça sur internet:
SELECT h1.nom AS nom1, h2.nom AS nom2, h3.nom AS nom3, t.nom AS nomType
FROM habilitations AS h1
LEFT JOIN habilitations AS h2 ON h1.idparent = h2.idhabilitation
LEFT JOIN habilitations AS h3 ON h2.idparent=h3.idhabilitation
INNER JOIN type_habilitation AS t ON h1.idtype=t.idtype
WHERE (h1.idhabilitation = 4)

Le problème est que je veux une condition dynamique (car en précisant le numero de l'id, de la catégorie principale, ça fonctionne également dans ma requête

Salut,
J'ai déjà eu ce "problème" il y a quelques années et je l'ai résolu de la façon suivante :

<?php
include("connect.php");
require_once('header_html.php');
define('NL',"\n");
function estParent($idcat) {
    global $connexion;
    $sql="SELECT count(id) as cnt FROM aolv2.categories WHERE id_parent = $idcat";
    $res1=$connexion->query($sql);
    $row1=$res1->fetch(PDO::FETCH_OBJ);
    return $row1->cnt;
}
function affEnfant($idcat) {
    global $connexion;
    global $buffer;
    $sql="SELECT id,libelle FROM aolv2.categories WHERE id_parent = $idcat";
    $res=$connexion->query($sql);
    while( $row=$res->fetch(PDO::FETCH_BOTH)) {
        $ep=estParent($row['id']);
        $buffer.='<ul>'.NL;
        $class=($ep>0) ? ' class="folder"' : '' ;
        $buffer.= '<li><span' .$class . '>'. $row['libelle'] . '</span>'.NL;
        affEnfant($row['id'],$buffer);
        $buffer.='</ul>'.NL;
    }
}
$buffer="";
$sql="SELECT id,libelle FROM aolv2.categories WHERE id_parent = 0 ORDER BY libelle";
$res=$connexion->query($sql);
$buffer='<ul id="browsercat" class="filetree treeview-famfamfam">'.NL;
$buffer .='<li><span class="folder">Arborescence des catégories</span>'.NL;
$buffer .='<ul>'.NL;
while($row=$res->fetch(PDO::FETCH_OBJ)) {
    $buffer .= '<li><span classe="folder">'.$row->libelle.'</span>'.NL;
    affEnfant($row->id,$buffer);
    $buffer .='</li>'.NL;
}
$buffer .='</ul>'.NL;
$buffer .='</li>'.NL;
$buffer .='</ul>'.NL;
echo $buffer;
?>

@plus

Pierre

tikoum
Auteur

Un peu balaise pour moi, mais ça m'a peut être donné une idée que je testerais demain.