Bonjour à tous,

Alors dans la suite de ma découverte CakePHP (2.7) j'ai quelques questions concernant les modèles. Après une après midi de recherche, je me résigne à venir demander l'aide.

La première étant comment puis-je charger un modèle dans une fonction static ? Là ou on ne peut pas faire de $this. (Grr). J'ai bien essayé en remplacant $this-> par self:: mais devant mon nom de modèle ça ne fonctionne pas.

La seconde, j'ai un formulaire d'édition qui traite plusieurs modèles, j'ai bien préciser la table dans le helper form mais parcontre j'ai trouvé que saveAssociated qui en fait n'est pas vraiment adapté puisque il n'y a pas forcément de relation entre les modèles que j'édite. Est ce qu'il y a une solution que j'ai raté ?

En vous remerciant.

Bryan

17 réponses


connected
Réponse acceptée

Alors, on va essayer de prendre les choses dans l'ordre.
Tu mélanges controller et model, du coup petit rappel simpliste :

  • Controller c'est pour gérer la partie logique de ton code.
  • Model c'est pour gérer l'accès aux données.

En partant sur ce principe, il te fait créer un model pour accéder à tes compétences et le joindre aux catégories.

  • categories hasMany competences (skills en anglais) => dans ton Model Category
  • competences belongsTo categories => dans ton Model Competence

Enfin dernière chose sur les Models, il faut que tu rajoutes le Behavior Containable dans Category :

public $actsAs = array('Containable');

Du coup quand tout ça est "ok", il te suffit de faire dans ton controller :

$this->set('Categories', $this->Category->find('all',  array('contain' => 'Competence')));

Et dans ta vue tu gardes ta boucle pour $Categories, et pour chaque $category tu auras un tableau Competence ($category['Competence']), il te suffit de faire un foreach dessus pour avoir tes competences associées à la catégorie.

Désolé je n'ai pas pu me relire, il y a certainement des erreurs.
Je reste dispo pour t'aider :)

Bon courage

connected
Réponse acceptée

Re,

bon pour t'aider j'ai reproduit ce que je pouvais. Je pense arriver au résultat que tu souhaites :

Mon schéma de base de données :

// `bryou16`.`categories`
$categories = array(
  array('id' => '1','name' => 'batiment'),
  array('id' => '2','name' => 'informatique')
);

// `bryou16`.`skills`
$skills = array(
  array('id' => '1','name' => 'peinture','category_id' => '1'),
  array('id' => '2','name' => 'soudure','category_id' => '1'),
  array('id' => '3','name' => 'traitement de texte','category_id' => '2'),
  array('id' => '4','name' => 'programmation','category_id' => '2')
);

// `bryou16`.`skills_users`
$skills_users = array(
  array('id' => '1','skill_id' => '1','user_id' => '1'),
  array('id' => '4','skill_id' => '3','user_id' => '2'),
  array('id' => '5','skill_id' => '1','user_id' => '2')
);

// `bryou16`.`users`
$users = array(
  array('id' => '1','username' => 'user1'),
  array('id' => '2','username' => 'user2')
);

Les Models :

<?php 

// app/Controller/Skill.php

App::uses('AppModel', 'Model');
/**
* Skill
*/
class Skill extends AppModel
{
  public $actsAs = array('Containable');

  public $belongsTo = array('Category');
  public $hasAndBelongsToMany = array('User');  

}

?>
<?php 
// app/Controller/Category.php

App::uses('AppModel', 'Model');
/**
* Category
*/
class Category extends AppModel
{
  public $actsAs = array('Containable');

  public $hasMany = array('Skill');

}

?>

Le Controller :

<?php
// app/Controller/InformationsController.php

App::uses('AppController', 'Controller');

class InformationsController extends AppController {

  public function edit(){
    $id = 2; // A remplacer par ton $this->Auth->user('id')
    $this->loadModel('Category');
    $this->loadModel('SkillsUser');

    // On récupère toutes les compétences de l'utilisateur donné
    $associations = $this->SkillsUser->find('all', array(
                                  'conditions' => array('user_id' => 2),
                                  'fields'     => array('skill_id')
                                      ));
    // On applati le resultat pour n'avoir qu'un tableau et pouvoir faire une recherche dedans plus tard (dans la vue)
    $associations = Hash::extract($associations, '{n}.SkillsUser.skill_id');

    // On récupère les categories avec un pour chaque catégorie les compétences
    $data = $this->Category->find('all', array(
                          'contain' => 'Skill'
                          ));

    // On transmet les variables à la vue
    $this->set(compact('data', 'associations'));
  }

}
?>

Et enfin la View :

<!-- // app/View/Informations/edit.ctp -->
<select multiple name="competences[]" data-placeholder="Ajouter ou supprimer des compétences" class="chosen-select" multiple style="width:100%;height:400px" tabindex="4" >

<?php foreach ($data as $d): ?>

  <optgroup label="<?= $d['Category']['name']; ?>">

  <?php if (!empty($d['Skill'])): $skills = $d['Skill']?>   

    <?php foreach($skills as $skill): ?>
      <?php $selected = in_array($skill['id'], $associations) ? ' selected' : ''; ?>
      <option value="<?php echo $skill['id']; ?>" <?= $selected; ?>>
        <?php echo $skill['name']; ?>
      </option>
   <?php endforeach ?>

  <?php endif ?>

  </optgroup>
<?php endforeach ?>

Pour respecter la norme de Cake
J'ai renommé ta table "competences" en "skills" et "skills" en "skills_user". Tu peux retrouver cette information dans la documentation de Cake au niveau des associations.

J'ai essayé de commenter, mais peut-être que tout ne sera pas lisible. Ce qui me donne à l'écran :

Voilà, c'est une base à toi d'adapter :)

connected
Réponse acceptée

Bonjour,

Comme je le disais dans mon précédent message, ta démarche est la bonne. Je n'ai pas eu le temps de regarder, voici la solution de facilité :

foreach($this->request->data['SkillsUser'] as $k => $v){
  $this->request->data['SkillsUser'][$k]['user_id'] = $id;
}

if($this->SkillsUser->save($this->request->data)){
  // Confirmation
  $this->Session->setFlash(__('Votre profil à bien été mis à jour.')); 
  return $this->redirect(array('action' => 'my_informations'));
}

qui remplace :

if ($this->User->saveAssociated($this->request->data)) 
            {
                // Confirmation
                $this->Session->setFlash(__('Votre profil à bien été mis à jour.'));
                debug($this->request->data);
                die();
                return $this->redirect(array('action' => 'my_informations'));
            }

Bonjour,

  • Est-ce que tu peux donner les bouts de code correspondants ? Dans quel cas et quel but utilises-tu une méthode static et dans quel fichier (Controller ?) ?
  • Si les Model ne sont pas liés tu n'as pas besoin de faire de saveAssociated. Il faut traiter les deux Models séparement.

FirstController.php

public function add() 
{
    $this->loadModel('Second');
    if ($this->request->is('post')) {
        $this->First->create();
        $this->Second->create();
        if ($this->First->save($this->request->data) && $this->Second->save($this->request->data)) {
            $this->Session->setFlash(__('Your post has been saved.'));
            return $this->redirect(array('action' => 'index'));
        }
    }
}

dans ta vue :

echo $this->Form->create('First');
// tralalala
echo $this->Form->end('Fini');

echo $this->Form->create('Second');
// tralalala
echo $this->Form->end('Fini');

Edit : petite coquille dans le code

bryou16
Auteur

Bonjour,

Merci pour ta réponse :)

Alors en fait c'est un peu "con", je récupère un liste de catégorie et dans chaque catégorie il y a une liste de compétence. Du coup je voudrais récuperer cette liste qui correspond à l'id de la catégorie. Et du coup je ferais appel à cette fonction static qui ferais une requete en fonction de l'id de la catégorie passé en argument.

Tu comprends ?

A moins qu'il y ai la possibilité de faire ça directement dans une requete ?

Pour ce qui est du formulaire j'ai bien pensé à faire de cette façon mais ça m'oblige à avoir deux validations. Il y a moyen dans le même genre de faire une validation avec plusieurs modèles ?

Merci en tout cas.

Bryan.

Ta liste de catégories et la liste de compétence sont stockées dans la base de données ?
Si oui, tout ce fait par les Models et donc pas besoin de méthode static selon moi. Dans le cas contraire est-ce que tu as un petit bout de code qui m'en dira plus, et même dans ce cas je ne vois toujours pourquoi pas tu as besoin d'une méthode static.

A tes Models tu peux rajouter des méthodes que tu peux réutiliser par la suite, au cas où tu l'aurais oublié dans ta réfléxion lol

Concernant le formulaire, si tu m'éclaire sur ton cas on pourra peut-être trouver une alternatives plus simple :)

bryou16
Auteur

En fait l'histoire de la fonction et des deux formulaires c'est liée puisque c'est pour éditer un profil qui contient les infos de l'utilisateur avec ses compétences.

Voilà le controller :

public function edit(){

        $id = $this->Auth->user(['id']);

        // Chargement du modèle User
        $this->loadModel('User');

        // Chargement du modèle Competence
        $this->loadModel('Category');

        $this->request->data['User']['id']= $id;

        $user = $this->User->findById($id);

        if (!$user) {
            throw new NotFoundException(__('Utilisateur Invalide'));
        }

        if ($this->request->is(array('post', 'put'))) {
            $this->User->id = $id;
            if ($this->User->save($this->request->data)) {
                $this->Session->setFlash(__('Votre profil à bien été mis à jour.'));
                return $this->redirect(array('action' => 'my_informations'));
            }
            $this->Session->setFlash(__('Erreur de mise à jour.'));
        }

        // Déclaration de la variable User
        $this->set('User',$user;

        // Déclaration de la variable Categories
        $this->set('Categories',$this->Category->find('all'));

    }

Puis après dans ma vue je fait un foreach de $Categories

<select multiple name="competences[]" data-placeholder="Ajouter ou supprimer des compétences" class="chosen-select" multiple style="width:100%;" tabindex="4">

                    <?php foreach ($Categories as $category): ?>

                       <optgroup label="<?= $category['Category']['name']; ?>">
                          <?php

                          $Competences = InformationsController::ListeCompetences($category['Category']['id']);

                          foreach($Competences as $competence)
                           {

                              $Liste = InformationsController::CompetencesIntermittent($Infos_user['id'],$competence['id']);

                              if (count($Liste) > 0) {
                                ?>
                                <option value="<?php echo $competence['id']; ?>" selected><?php echo $competence['nom']; ?></option>
                                <?php
                              }
                              else
                              {
                                ?>
                                <option value="<?php echo $competence['id']; ?>"><?php echo $competence['nom']; ?></option>
                                <?php
                              }
                           }

                          ?>
                       </optgroup>

                    <?php endforeach ?>

                  </select>

Avec ma function static je récupère donc les compétences de la catégorie. Ainsi à chaque compétence je vérifie encore avec un fonction static si cette compétence est présente dans la bdd de l'utilisateur.

J'avoue que je ne sais pas trop comment m'y prendre la.

bryou16
Auteur

Bonjour,

Ahhh, Effectivement j'ai fait la relation dans un sens mais pas dans l'autre ! Alors en rajoutant le HasMany j'ai bien mon tableau Competence ($category['competence']). Pour le Behavior Containable si j'ai bien compris c'est pour me permettre de récuperer seulement la table Competence au lieu de récupèrer toutes les associations ? C'est bien ça ? Comme si je faisait directement fields dans ma requete ?

Alors une fois que j'ai ma liste des compétences dans les catégories, je compare la compétence avec la table skills qui elle gère l'attribution des utilisateurs aux compétences :

id - user_id - competence_id

Est ce que le faire du coup dans une fonction static c'est bien appropriée ?

Et donc pour la validation de mon formulaire, je change les informations de la table users mais aussi celle de skills (Attribution des compétences par utilisateur).

Merci beaucoup pour ton aide.

Oui, le comportement Containable te permet de limiter le nombre de requêtes et t'éviter de charger l'ensemble de tes associations à chaque fois.
Par contre, j'ai besoin d'une précision avant de répondre aux autres points : le code que tu as montré plus haut avec la méthode edit, se trouve bien dans le controller UsersController ?

PS : est-ce que tu peux aussi me donner rapidement, sans tous les champs, la structure des tables utilisées. J'ai juste besoin du nom de chaque table avec leurs clé primaire et clé étrangère. Pour bien cerné ton but. Merci :)

bryou16
Auteur

Alors non, mon edit se trouve dans InformationsController c'est pour ça que je load les modèles. Ca me semblait plus propre de gérer ça dans un contrôleur à part. Et il me semble que c'est un des problèmes, je n'arrive pas à charger un modèle dans ma fonction static.

Pour mes tables:

/**
** Users ***
**/

'id' 'username' etc ..

/**
** Categories ***
**/

'id' 'name'

/**
** Competences ***
**/

'id' 'category_id' 'name'

/**
** Skills ***
**/

'id' 'user_id' 'competence_id'

J'espere que je n'ai rien oublié :)

bryou16
Auteur

Un grand merci ! C'est en fait la fonction in_array que je ne connaissait pas et qui est parfaitement adapté ! J'ai fait une adaptation pour mon code et c'est parfait !

Ca m'a permis de découvrir les container et in_array.

Pour ce qui est d'enregistrer dans les deux modèles je ne peux pas faire deux fois save ?

Je croît que je ne vais pas avoir le choix d'avoir deux submit ?

Tu n'as qu'un model à sauvegarder normalement, à moins que je n'ai pas tout compris (ce qui est possible lol).
Pour moi tu dois juste sauvegarder dans ta table "skills" (selon ton schéma de BDD) pour associer un user à une compétence.

L'idéal c'est de passer par le Controller de users, et de faire la liaison. Tu peux tout valider en une fois normalement, il faut juste rajouter dans le Model User (selon mon schéma de BDD)

public $hasAndBelongsToMany = array('Skill')
bryou16
Auteur

En fait, quand je sauvegarde, je supprime toutes les entrées de la table SkillsUser où user_id est égal à celui de l'utilisateur. Et ensuite problème, puisque j'édite ma table user mais j'insert des entrées dans ma table SkillsUser.

Je n'ai pas d'erreur, voici mon contrôleur :

$id = $this->Auth->user(['id']);

        // Chargement du modèle User
        $this->loadModel('User');

        // Chargement du modèle Competence
        $this->loadModel('Category');

        // Chargement du modèle SkillsUser
        $this->loadModel('SkillsUser');

        // Chargement du modèle Skill
        $this->loadModel('Skill');

        $this->request->data['User']['id']= $id;

        $user = $this->User->findById($id);

        if (!$user) {
            throw new NotFoundException(__('Utilisateur Invalide'));
        }

        if ($this->request->is(array('post', 'put'))) {

            $this->User->id = $id;

            $this->SkillsUser->deleteAll(array('user_id'    =>  $id));

            if ($this->User->saveAssociated($this->request->data)) 
            {
                // Confirmation
                $this->Session->setFlash(__('Votre profil à bien été mis à jour.'));
                debug($this->request->data);
                die();
                return $this->redirect(array('action' => 'my_informations'));
            }

            $this->Session->setFlash(__('Erreur de mise à jour.'));
        }

        // On récupère toutes les compétences de l'utilisateur donné
        $associations = $this->SkillsUser->find('all', array(
                                      'conditions' => array('user_id' => $id),
                                      'fields'     => array('skill_id')
                                          ));

        // On applati le resultat pour n'avoir qu'un tableau et pouvoir faire une recherche dedans plus tard (dans la vue)
        $associations = Hash::extract($associations, '{n}.SkillsUser.skill_id');

        // On récupère les categories avec un pour chaque catégorie les compétences
        $datas = $this->Category->find('all', array(
                              'contain' => 'Skill'
                              ));

        // On transmet les variables à la vue
        $this->set(compact('datas', 'associations'));

        // Déclaration de la variable User
        $this->set('User',$user);

après mon post je fait un debug() et voilà le résultat :

/app/Controller/InformationsController.php (line 105)
array(
    'SkillsUser' => array(
        (int) 0 => array(
            'skill_id' => '177'
        ),
        (int) 1 => array(
            'skill_id' => '178'
        )
    ),
    'User' => array(
        'adresse' => 'blabla',
        'adresse_complement' => 'blabla',
        'code_postal' => '33100',
        'ville' => 'Bordeaux',
        'telephone_fixe' => '00-00-00-00-00',
        'telephone_mobile' => '00-00-00-00-00',
        'id' => '2'
    )
)

Est ce que c'est parce qu'il faut que j'insère user_id dans le tableau SkillsUser ?

Mais dans ce cas comment je remplace les chiffres ?

$this->request->data['SkillsUser'][?]['user_id']= $id;

Merci :)

La démarche est la bonne, je ne peux pas regarder maintenant. Je te fais un retour demain :)

bryou16
Auteur

Merci Beaucoup connected pour toute ton aide ! Je poste un autre sujet pour mon problème authentification :)

bryou16
Auteur

Connected je peux avoir ton mail ? Je voudrais te remercier pour ton aide.

Bonjour,

Je n’ai pas échoué 700 fois, je n’ai même pas échoué une seule fois. J’ai réussi en prouvant qu’il y avait 700 solutions qui ne marchaient pas.

Ca marche pour l'auhtentification, j'y jetterai un oeil des que j'ai du temps :)

++