Bonjour,

Je suis confronté à un problème pour la création de mon site. Je souhaiterais faire une boucle de requêtes (très limitées en nombre pour récupérer une liste de chapitres contenus dans des parties elles mêmes contenues dans un cours.

Je récupère correctement les données de mon cours
Je récupère aussi correctement les données de mes parties
Je ne récupère que les chapitres de ma dernière partie ....

<?php
$course = $this->Course->find('first', array(
                'conditions' =>array('Course.id' => $id_course), //On recherche toutes les informations
                    ));

        $this->loadModel('Part');
        $parts = $this->Part->find('all', array(
                'conditions' =>array('Part.course_id' => $id_course), //On recherche toutes les parties
                    ));

    $this->loadModel('Chapter');
    foreach ($parts AS $part)
    {

        $chapters = $this->Chapter->find('all', array(
                'conditions' =>array('Chapter.part_id' => $part['Part']['id']), //On recherche tous les chapitres
                    ));
    }

        $this->set(compact('course')); // On envoit la liste à la vue.
        $this->set(compact('parts')); // On envoit la liste à la vue.
        $this->set(compact('chapters')); // On envoit la liste à la vue.
?>

Pourriez-vous éclairer ma lanterne SVP ? :)

17 réponses


Pourquoi ne pas utiliser les liaisons de modèle ? Ca semble parfaitement correspondre à ton besoin ;)

// app/Controller/CoursesController.php
$courses = $this->Courses->find('first', array(
    'conditions' => array('Course.id' => $id_course),
    'contain' => array(
        'Part' => array('Chapter')
    )
));

$this->set(compact('courses'));

// app/Model/Course.php
public $hasMany = array('Part');

// app/Model/Part.php
public $hasMany = array('Chapter');

En suite je t'invite à debug la variable "$courses" sur ta vue pour voir ce qu'elle contient ;)
J'en profite pour préciser que tu n'as pas besoin de faire une ligne pour chaque variable envoyé à la vue, il suffit de les mettres les unes à la suite des autres.

$this->set(compact('courses', 'parts', 'chapters'));

En effet ça serait plus utile, seulement je ne sais pas comment intéragir les données récupérées. Aurais-tu un exemple pour afficher le titre d'un chapitre bien précis ? :)

PS: Merci oui, j'avais oublié que je pouvais tout envoyer à la vue d'un seul coup :)

Je t'invite à utiliser la fonction debug sur la variable envoyé à la vue, elle sera constitué de la façon suivante (si je ne fais pas d'erreur):

// app/View/Crouses/view.ctp
<?= debug($courses); ?> // Ce qui va monter un tableau du type:
$courses = array(
    'Course' => array(
        'id' => 1,
        'title' => '...',
        'content' => '...',
        'etc' => '...'
    ),
    'Part' => array(
        0 => array(
            'id' => 56,
            'title' => '...',
            'content' => '...',
            'etc' => '...',
            'Chapter' => array(
                0 => array(
                    'id' => 33,
                    'title' => '...',
                    'content' => '...'
                ),
                1 => array(...) // Dans le cas où il y a plusieurs chapitres associés
            )
        ),
        1 => array(...) // Dans le cas où il y a plusieurs parties associées
    )
);

Merci de ta réponse cependant c'est bien ce que j'avais fait et j'obtiens le cours, les parties mais aucunement les chapitres. Voilà ce que j'obtiens:

array(
    'Course' => array(
        'id' => '47',
        'title' => 'Essai',
        'description' => 'Essai nouveau cours',
        'created' => '2014-12-16 21:39:21',
        'difficulty' => '1',
        'online' => '0',
        'lastUpdate' => '2014-12-16 21:39:21',
        'duration' => '1 semaine',
        'type' => '1'
    ),
    'Part' => array(
        (int) 0 => array(
            'id' => '11',
            'title' => 'Titre de votre première partie.',
            'online' => '0',
            'course_id' => '47',
            'position' => '1'
        ),
        (int) 1 => array(
            'id' => '12',
            'title' => 'Titre de votre première partie.',
            'online' => '0',
            'course_id' => '47',
            'position' => '2'
        ),
        (int) 2 => array(
            'id' => '13',
            'title' => 'Titre de votre première partie.',
            'online' => '0',
            'course_id' => '47',
            'position' => '3'
        )
    )
)

Tu as bien ajouté l'association du modèle Part au modèle Chapter ?

// app/Model/Part.php
public $hasMany = array('Chapter');

Ensuite tu utilises bien comme clé de liaison la colonne "part_id" dans ta table "chapters" ?
Si oui, est-ce que tu as bien des chapitres associés aux parties récupérées ?

J'ai bien ajouté l'association du modèle Part au modèle Chapter
Ensuite, oui j'ai bien part_id dans ma table chapter.
Et oui j'ai bien des chapitres associés aux parties récupérées.

Si j'écris:

$this->loadModel('Part');
    $parts = $this->Part->find('all', array(
    'conditions' => array('Part.course_id' => $id_course),
));

Alors j'obtiens :

array(
    (int) 0 => array(
        'Part' => array(
            'id' => '11',
            'title' => 'Titre de votre première partie.',
            'online' => '0',
            'course_id' => '47',
            'position' => '1'
        ),
        'Chapter' => array(
            (int) 0 => array(
                'id' => '8',
                'content' => 'Ecrivez le contenu de votre tout premier chapitre.',
                'title' => 'Titre de votre premier chapitre',
                'online' => '0',
                'part_id' => '11',
                'position' => '1'
            )
        )
    ),
    (int) 1 => array(
        'Part' => array(
            'id' => '12',
            'title' => 'Titre de votre première partie.',
            'online' => '0',
            'course_id' => '47',
            'position' => '2'
        ),
        'Chapter' => array()
    ),
    (int) 2 => array(
        'Part' => array(
            'id' => '13',
            'title' => 'Titre de votre première partie.',
            'online' => '0',
            'course_id' => '47',
            'position' => '3'
        ),
        'Chapter' => array(
            (int) 0 => array(
                'id' => '9',
                'content' => 'Ecrivez le contenu de votre tout premier chapitre.',
                'title' => 'Titre de votre premier chapitre',
                'online' => '0',
                'part_id' => '13',
                'position' => '1'
            ),
            (int) 1 => array(
                'id' => '10',
                'content' => 'Ecrivez le contenu de votre tout premier chapitre.',
                'title' => 'Titre de votre premier chapitre',
                'online' => '0',
                'part_id' => '13',
                'position' => '2'
            )
        )
    )
)

Pour le coup je n'obtiens pas les informations sur le Cours ( ce qui est normal si on regarde ma requête). J'ai l'impression que CakePHP refuse de descendre plus bas dans la hiérarchie.

Dans ton model AppModel ajoute les choses suivantes:

// app/Model/AppModel.php
public $actsAs = array('Containable');
public $recursive = -1;

En suite c'est-à-dire "J'ai bien ajouté l'association du modèle Part au modèle Chapter.around" ? Chapter.around ?

Hum, c'est encore pire car je n'ai pas accès aux enfants du coup ... (parts pour course et chapters pour part).

Excuse-moi, lorsque j'ai dis "Chapter.around" je voulais dire "Chapter". Le balisage de Grafikart.fr a automatiquement marqué le ".around", je ne sais pas pourquoi.

Pourtant, pour ce qui est du recursive c'est pour éviter par défaut de prendre les enfants (pour pas surcharger inutilement les requêtes, mais si tu précise quels enfants tu veux, ils sont inclus dans la requête...).
Pour la coup je bloque... Théoriquement ça devrait fonctionner, je ne suis plus disponible ce soir mais demain soir je pourrais essayer de t'aider d'avantage (teamviewer, skype) sauf si quelqu'un trouve la solution avant moi.
Vérifie bien que les associations sont faites dans les bons modèles, voici le lien de la documentation en attendant (en français): CakePHP Containable

Très bien, merci de ton aide. Je vais essayer de creuser de mon côté pourvoir si je peux trouver quelque chose ...

Bonne soirée à toi et merci beaucoup d'avoir pris du temps pour te pencher sur mon problème.

Salut,

Je pense que le problème du premier code viens de la boucle foreach; la variable $chapters est écrasée a chaque itération, du coup seuls les chapitres de la dernière partie survivent.
Il faudrait la déclarer avant la boucle, et lui greffer ensuite le resultat de chaque requête :
...

$this->loadModel('Chapter');

$chapters = array();

foreach ($parts AS $part)
{

    $part_chapters = $this->Chapter->find('all', array(
            'conditions' =>array('Chapter.part_id' => $part['Part']['id']), //On recherche tous les chapitres
                ));

    $chapters[] = $part_chapters;
}

...

Salut !

Vous êtes sur la bonne voie !

public $actAs = ('Containable');

Ca c'est bon !

Pour la suite, regarde ce vieux tuto http://www.grafikart.fr/tutoriels/cakephp/containable-behaviour-262, il pourra surement résoudre ton problème !

Oups ! J'avais totalement oublié hier, excuse-moi :D
Sinon, pour résoudre ton problème, une solution très simple, définir la récursivité à la volée juste avant ta requête:

// app/Controller/CoursesController.php
$this->Course->recursive = 2; // Soit une profondeur de 2 (part > chapter)
$courses = $this->Course->find('first', array(...));

Je connaissais cette technique, mais sur l'un de mes anciens projets j'ai plusieurs niveaux de récursivités alors que je n'ai pas eu à modifier ça, peut-être une mise à jour entre temps...

Bonsoir.
Sinon, pour résoudre ton problème, une solution très simple, définir la récursivité à la volée juste avant ta requête:
S'il a mit le recursive à -1, qu'il défini le behavior Containable dans l'AppModel et qu'il utilise correctement la clé contain dans ses requêtes, il n'a nullement besoin de définir un récursive dans ses controllers.

Bonjour,

L'inconvénient avec une telle récursivité, c'est que l'on risque de récupéré plein de données qui ne vont pas servir au final.
Je pense qu'il vaut mieux passer par un contain ! Au moins, c'est toi qui définit qu'est ce que tu veux récupérer, dans quel ordre, tout ça quoi.

Donc comme le disais Lartak11 et Inspirat, ajoute dans ton model Course :

public $actAs = ('Containable');
public $hasMany = ['Part'];

Ainsi que dans le model Part :

public $haMany = ['Chapter'];

Et ensuite tu peux réaliser ta requete :

    $this->Course->contain(
        'Part' => [
            'Chapter' => [
                'fields' => ['id', 'name', 'slug', '....']
            ]
        ]
    );
    $this->Course->findById($id_course);

Après c'est la meilleur chose pour moi, peut être que je me trompe ... :)

Sans offense c'est exactement ce que je lui ai donné tout au long de mes réponses, mais écrit d'une façon différente qui fonctionne tout autant :D

Excuse moi Inspirat, en effet, à la première réponse c'est la même que moi.
Acceptez mes excuses sur ce coup là !