Hello,
J'explique le contexte. Le but est de réaliser une fonction de Follow similaire à Twitter : Les users peuvent se suivre les uns les autres.
Pour ce faire, dans la base de données, j'ai opté pour une table d'association pour faire un self join sur la table principale.
1/ La table Principale : "users" (qui contient toutes les données d'un user)
2/ Une table d'association : "users_users" avec les champs suivants:
Par exemple l'user 2 est suivi par les users 7 et 9, nous donne :
id: 11
following_id: 2
follower_id: 7
id: 12
following_id: 2
follower_id: 9
Je parviens à sauvegarder une entrée de ce type dans la BDD via un clic de l'utilisateur sur un bouton "suivre", mais je ne parviens pas à retrouver avec Find() toutes les données. Je m'exmplique :
Voici la relation que j'importe dans mon modèle User
public $hasAndBelongsToMany = array(
'Following' => array(
'className' => 'User',
'joinTable' => 'users_users',
'foreignKey' => 'follower_id',
'associationForeignKey' => 'following_id',
'unique' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
),
'Follower' => array(
'className' => 'User',
'joinTable' => 'users_users',
'foreignKey' => 'following_id',
'associationForeignKey' => 'follower_id',
'unique' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
)
);
Voici maintenant ma requête dans le UserController pour récupérer la liste des followers d'un user :
//on récupère la liste des followers
public function followers($id) {
$this->User->bindModel(array('hasOne'=>array('UsersUsers')));
$d['followers'] = $this->User->UsersUsers->find('list', array(
'conditions' => array('UsersUsers.following_id' => $id),
'fields' => array('UsersUsers.follower_id')
));
debug($d['followers']); die();
}
Et voici le résultat de ce que je récupère en debug :
array(
(int)11 => '7',
(int)12 => '9'
)
Je récupère bien l'id des followers depuis la table users_users (7 et 9).
En revanche je ne parviens pas à récupérer les données associées à ces id (données de la table users).
J'ai essayé le contain mais je n'y suis pas parvenu.
Une idée pour récupérer les données associées au model parent avec bindModel ?
Merci par avance.
Bonjour.
Avec un find('list'), tu ne peux pas récupérer des informations de tables associées dans la requête SQL.
Le find('list') ne permet de récupérer que 2 champs d'une table, sous format : clé » valeur.
find('list', $params) retourne un tableau indexé, pratique pour tous les cas où vous voudriez une liste telle que celles remplissant les champs select.
Le résultat d’un appel à find('list') sera de la forme suivante:
Array
(
//[id] => 'valeurAffichage',
[1] => 'valeurAffichage1',
[2] => 'valeurAffichage2',
[4] => 'valeurAffichage4',
[5] => 'valeurAffichage5',
[6] => 'valeurAffichage6',
[3] => 'valeurAffichage3',
)
Pour plus d'informations : Récupérer vos données » find(‘list’).
Merci de ta réponse Lartak. En effet pour le find('list'), la précision est importante.
Cependant, même avec un find('all') je ne parviens pas à obtenir les données associées de la table User.
Voici la requête et sa réponse.
//on récupère la liste des Auteurs que l'utilisateur suit
$this->User->bindModel(array('hasOne'=>array('UsersUsers')));
$d['followers'] = $this->User->UsersUsers->find('all', array(
'conditions' => array('UsersUsers.following_id' => $id),
//'contain' => array('User'),
'fields' => array('UsersUsers.follower_id'),
//'order' => array('UsersUsers.follower_id DESC'),
// 'recursive' => 1
));
debug($d['followers']); die();
//réponse du debug
array(
(int) 0 => array(
'UsersUsers' => array(
'follower_id' => '1'
)
),
(int) 1 => array(
'UsersUsers' => array(
'follower_id' => '7'
)
)
)
Je pense qu'il y a un problème au niveau de la relation entre la table users_users et la table users.
Je n'avais jamais utilisé le bindModel pour faire une relation entre tables à la volée depuis le controller, je ne dois pas faire le truc correctement et la relation n'est pas la bonne.
Même avec un recursive à 1 ou un comportement containable, je ne récupère pas les données associées de la table users.
Une idée sur bindModel ou est-ce que je dois repenser le système de relation entre ma tables users_users et la table users ?
Je n'aurais pas fais comme toi.
J'aurais créé une table follows avec un model Follow
public $BelongsTo = [
'Follower' => [
'className' => 'User',
'foreignKey' => 'follower_id',
],
'Following' => [
'className' => 'User',
'foreignKey' => 'following_id',
]
];
Dans mon model User j'aurais fais ses relations
public $hasMany = [
'Following' => [
'className' => 'Follow',
'foreignKey' => 'following_id',
]
];
public $BelongsTo = [
'Follower' => [
'className' => 'Follow',
'foreignKey' => 'follower_id',
]
];
Alors je donne un point de vu je sais pas si c'est utile, mais j'avais fais comme ça et ça fonctionnais pas mal de tête, je n'ai plus le code devant moi.
Merci Jean-christophe. Oui je pense que je vais retourner vers une solution plus logique de l'organisation d'un système de follow. Ta solution me paraît plus évidente. Je fais ça ce matin et je fais un petit retour si ça fonctionne comme attendu ;)
Jean-christophe,
J'ai effectivement fait une table Follow , un modèle Follow et les associations que tu indiques.
Je dois mal faire mes associations parce que je ne parviens toujours pas à retrouver les infos de la tables users sur une requête sur la table follow. Voilà la requête et sa réponse. (Je précise que le Modèle User et le modèle Follow ont tous les 2 le comportement Containable.
//on récupère la liste des Auteurs que l'utilisateur suit
$this->loadModel('follow');
$d['followings'] = $this->follow->find('all', array(
'conditions' => array('follow.follower_id' => $this->Auth->user('id')),
// 'fields' => array('follow.following_id'),
'order' => array('follow.following_id DESC')
));
debug($d['followings']); die();
// Le résultat du debug
array(
(int) 0 => array(
'follow' => array(
'id' => '7',
'following_id' => '7',
'follower_id' => '1',
'created' => '2015-04-17 10:40:26'
)
),
(int) 1 => array(
'follow' => array(
'id' => '4',
'following_id' => '2',
'follower_id' => '1',
'created' => '2015-04-17 10:36:34'
)
)
)
Ce que je souhaiterai faire ici, c'est récupérer toutes les infos du User concerné, ici le user qui a l'ID 7 et le User qui a l'ID 2
J'ai tenté le recursive et le contain mais je dois mal l'écrire parce que ça me génère des erreurs.
Une idée pour récupérer les données associées au User ici ?
PS : Pour "Calvador" plus haut :
Merci de la remarque "Calvador". J'en ai rédigé beaucoup d'autres des phrases ... Encore eut-il fallu que tu les lises aussi ... Ravi que ça ait retenu ton attention, pour ma part je ne l'avais pas forcément remarqué. Je penserai à modifier ça à l'occasion si ça trouble quelques petits esprits, mais c'est pas une demi-phrase qui fait un site hein, sinon ce serait trop facile et tout le monde serait Grafikart :) Ca tombe bien, je n'ai pas du tout sa cible, c'est pas tout à fait le même concept non plus, et c'est même pas lancé, donc je suis très loin de GA et de son audience :) Mais revenons à nos moutons "Calvador" et au sujet si tu veux bien ... (sinon tu peux aussi me faire la météo si t'as rien d'autre à faire de tes journées)
Il faut que dans ta requêtes find() tu y passes les modèles associé avec la clé 'contain':
$d['followings'] = $this->follow->find('all', [
'conditions' => ['follow.follower_id' => $this->Auth->user('id')],
// 'fields' =>['follow.following_id'],
'contain' => ['Follower, Following'],
'order' => ['follow.following_id DESC']
]
);
debug($d['followings']); die();
ou alors:
$this->follow->contain(['Follower', 'Following']);
$d['followings'] = $this->follow->find('all', [
'conditions' => ['follow.follower_id' => $this->Auth->user('id')],
// 'fields' =>['follow.following_id'],
'order' => ['follow.following_id DESC']
]
);
Idem pour les 2 solutions avec le contain :) Problème d'association.
Le résultat du debug =>
Warning (512): Model "follow" is not associated with model "Follower" [CORE\Cake\Model\Behavior\ContainableBehavior.php, line 344]
Warning (512): Model "follow" is not associated with model "Following" [CORE\Cake\Model\Behavior\ContainableBehavior.php, line 344]
\app\Controller\UsersController.php (line 115)
array(
(int) 0 => array(
'follow' => array(
'id' => '7',
'following_id' => '7',
'follower_id' => '1',
'created' => '2015-04-17 10:40:26'
)
),
(int) 1 => array(
'follow' => array(
'id' => '4',
'following_id' => '2',
'follower_id' => '1',
'created' => '2015-04-17 10:36:34'
)
)
)
Merci pour tes réponses précisies Jean-christophe. A mon avis, ça se passe dans le modèle User et le modèle Follow. Je vais regarder de plus près et trouver ce problème d'association :) Quand j'aurai la solution, je la posterai ici si ça peut servir à d'autres. Merci en tout cas, ça m'a déjà pas mal éclairé pour avancer.
J'y ai un peu plus réfléchis et je pense que les associations devraient être comme tels:
ET d''ailleurs les conditions peuvent être mises dans les association du Model User:
public $hasMany = [
'Followings' => [
'className' => 'Follow',
'foreignKey' => 'following_id',
'conditions' => [
'follower_id' => $this>User->id
],
'dependent' => true
]
];
public $belongsTo = [
'Followers' => [
'className' => 'Follow',
'foreignKey' => 'follower_id',
'conditions' => [
'following_id' => $this>User->id
],
'dependent' => true
]
];
Le model Follow:
public $belongsTo = [
'Followers' => [
'className' => 'User',
'foreignKey' => 'follower_id'
],
'Followings' => [
'className' => 'User',
'foreignKey' => 'following_id'
]
];
Et donc après dans ton model User essai ça:
$d['followings'] = $this->User->Followings('all');
Idem en réglant les problèmes de convention (Majuscules/miniscules).
Je récapitule tout ce que j'ai pour que ce soit plus clair :
Dans le modèle User
// Le modele User
public $hasMany = array(
'Following' => array(
'className' => 'Follow',
'foreignKey' => 'following_id',
)
);
public $BelongsTo = array(
'Follower' => array(
'className' => 'Follow',
'foreignKey' => 'follower_id',
)
);
Dans le modèle Follow
// Le modele Follow
public $BelongsTo = array(
'Follower' => array(
'className' => 'User',
'foreignKey' => 'follower_id',
),
'Following' => array(
'className' => 'User',
'foreignKey' => 'following_id',
)
);
Ma requête dans le UserController
//on récupère la liste des Auteurs que l'utilisateur suit
$this->loadModel('Follow');
$this->Follow->contain(array('Follower', 'Following'));
$d['followings'] = $this->Follow->find('all', array(
'conditions' => array('Follow.follower_id' => $this->Auth->user('id')),
// 'fields' => array('follow.following_id'),
//'contain' => array('follower, following'),
'order' => array('Follow.following_id DESC')
));
debug($d['followings']); die();
Le résultat du debug
Warning (512): Model "Follow" is not associated with model "Follower" [CORE\Cake\Model\Behavior\ContainableBehavior.php, line 344]
Warning (512): Model "Follow" is not associated with model "Following" [CORE\Cake\Model\Behavior\ContainableBehavior.php, line 344]
\app\Controller\UsersController.php (line 115)
array(
(int) 0 => array(
'Follow' => array(
'id' => '7',
'following_id' => '7',
'follower_id' => '1',
'created' => '2015-04-17 10:40:26'
)
),
(int) 1 => array(
'Follow' => array(
'id' => '4',
'following_id' => '2',
'follower_id' => '1',
'created' => '2015-04-17 10:36:34'
)
)
)
On a ici une bonne vision d'ensemble du code. Je ne parviens pas à détecter ce qui pose problème au niveau des associations.
c'est le contain qui ne fonctionne pas.
Je récupère bien les résultats de la table follows, mais toujours ce problème d'association avec la table users
Premièrement et j'éditerais après si je vois autre chose, mais ce n'est pas $BelongsTo mais $belongsTo la majuscule au B pête ton association ^^ bienvenue dans le milieu des fautes à la con ... mdr
Par contre j'avais édité mon post avant ton dernier post, les associations avec conditions, et le find d'une différente façon, si tu n'avais pas vu mon édition jette un oeil.
Merci beaucoup Jean-christophe. J'ai vu ta solution plus haut qui est top, ce serait bien plus logique et cool pour appeler les Followers et les Followings d'un user :) , Je vais voir aujourd'hui si elle fonctionne.
De mémoire j'avais tenté de faire ça dans un autre projet et les conditions comme celle-ci dans le modèle n'acceptaient pas de variable mais uniquement une valeur définie ...
public $belongsTo = [
'Followers' => [
'className' => 'Follow',
'foreignKey' => 'follower_id',
'conditions' => [
'following_id' => $this>User->id
],
'dependent' => true
]
];
Je vais vérifier ça aujourd'hui et dès que j'ai la solution complète je l'envoie ici. Ce n'est pourtant pas sorcier ce système de follow, mais j'aurai un peu galéré quand même :)
PS n°2 pour "Calvador" qui persiste et signe à parler de tout sauf du sujet :
Non mais "Calvador"... t'es sur un forum qui parle de code... si t'as envie de faire le café des sports, ouvre un site pour ça ... Ici c'est une question liée au code... logique on est sur GA. Donc si tu as des réponses sur le sujet, envoies... sinon va discuter ailleurs. T'as pas d'amis sérieux ? Y a des sites pour ça : copain d'avant, meetic, ... t'as le choix. Donc si tu peux nous lâcher avec tes remarques persos ici, ce serait un peu plus intelligent. Maintenant si tu as une envie irrépréssible de parler avec moi ou me faire des remarques, tu peux m'appeler, mon tel est visible partout sur le web. Au moins ce sera du direct et tu évitera au forum GA toutes tes lignes hors sujet. T'es pas sur ton wall Facebook là enfin je crois pas.
Désolé de cette parenthèse pour les autres mais bon... on en a toujours un comme sur tous les forums... celui qui se pointe, parle de tout sauf du sujet. le boulet, ou le troll au choix.
Alors Jean-Christophe,
après plusieurs essais, il semble que j'ai effectivement un problème de prise en compte de relation du modèle Follow -> vers le modèle User.
Voici ce qui fonctionne, et ce qui ne fonctionne pas.
Ce qui marche : lier le modèle Follow directement au modèle User
class Follow extends AppModel {
// solution1
public $belongsTo = array(
'User' => [
'className' => 'User',
'joinTable' => 'users',
'foreignKey' => 'following_id',
'associationForeignKey' => 'user_id',
],
);
}
// Ici j'obtiens bien les données des Users sur la requête suivante pour récupérer les Followers depuis le controller :
$this->loadModel('Follow');
$this->Follow->contain(array('User'));
$d['followers'] = $this->Follow->find('all', array(
'conditions' => array('Follow.following_id' => $this->Auth->user('id')),
));
Ce qui ne marche pas : lier le modèle Follow avec Following et Follower (en utilisant le className User) :```
class Follow extends AppModel {
// solution2
public $belongsTo = array(
'Following' => array(
'className' => 'User',
'foreignKey' => 'follower_id'
),
'Follower' => array(
'className' => 'User',
'foreignKey' => 'following_id'
)
);
}
Visiblement il y a un problème d'association avec User si je ne le définit pas directement comme dans la 1ère solution
puisqu'ici le debug me renvoi l'erreur (en même temps que les données de la table follow mais sans les données des Users associés) :
Warning (512): Model "Follow" is not associated with model "User" [CORE\Cake\Model\Behavior\ContainableBehavior.php, line 344]
array(
(int) 0 => array(
'Follow' => array(
'id' => '1',
'following_id' => '1',
'follower_id' => '7',
'created' => '2015-04-17 00:00:00'
)
),
(int) 1 => array(
'Follow' => array(
'id' => '3',
'following_id' => '1',
'follower_id' => '6',
'created' => '2015-04-17 00:00:00'
)
),
(int) 2 => array(
'Follow' => array(
'id' => '2',
'following_id' => '1',
'follower_id' => '2',
'created' => '2015-04-17 00:00:00'
)
)
)
J'ai regardé encore sur la Doc mais je ne trouve pas où j'ai bien pu faire une erreur.
Est-ce que Cake prend en compte automatiquement "Following" et "Follower" dans le modèle Follow comme étant des alias du modèle User ? Ou est-ce qu'il faut les déclarer quelque part ?
Sinon je vais moins me prendre la tête et je vais faire 2 tables : followers_users et followings_users et les 2 modèles qui vont avec. Là je sais les associer au modèle User avec la solution 1. Mais je voulais alléger au max avec une seule table. La solution doit bien exister... :)
Mais tu me montre les associations du model Follow mais le problème n'est pas là, il te dis Follow n'est pas associé au model User donc c dans ton model User que le problème survient donne moi ton model User stp. Car ça doit marcher, tu peut même faire des associations sur sois-même
Sur mon modèle User j'avais ça :
public $hasMany = array(
//solution 1
'Follower' => array(
'className' => 'Follow', // le nom de la classe à laquelle on veut lier l'user
'joinTable' => 'follows', // le nom de la table
'foreignKey' => 'user_id', // le nom de la clé étrangère du modèle actuel
'associationForeignKey' => 'following_id', // Le nom de la clé étrangère de l'autre modèle
'unique' => 'keepExisting' // If you do not want records to be deleted
),
'Following' => array(
'className' => 'Follow',
'joinTable' => 'follows',
'foreignKey' => 'user_id',
'associationForeignKey' => 'follower_id',
'unique' => 'keepExisting' // If you do not want records to be deleted
)
Class User
public $hasMany = array(
'Follower' => array(
'className' => 'Follow', // le nom de la classe à laquelle on veut lier l'user
'foreignKey' => 'user_id', // le nom de la clé étrangère du modèle actuel
'conditions' => [ 'following_id' => $this->User->id] //On passe la conditions dans l'association
)
);
public $belongsTo= array(
'Following' => array(
'className' => 'Follow',
'foreignKey' => 'user_id',
'conditions' => [ 'follower_id' => $this->User->id] //On passe la conditions dans l'association
)
)
Ca serais plus juste comme ça.
Après dans ton controller User car je pense que c'est la que fais ta requête:
$this->User->Following->find('all');
//ou
$this->User->Follower->find('all')
Je vois pas pourquoi tu charges le model Follow et que tu requêtes dessus, le but des associations c'est justement éviter ça.
Le hic c'est qu'on ne peut pas mettre de variable dans une condition d'un hasMany (ou d'une autre relation d'ailleurs) dans une class Model
=> https://groups.google.com/forum/#!topic/cake-php/4Uxa8zSuVl0
Oui c'est vrai pour le hasMany, dsl, mais ça change pas grand chose juste ta requête:
$this->User->Follower('all', ['conditions' => $this->Auth->User('id')]);
Et ça fais la même chose
Tu vas quand même pas faire 2 table pour ça? imagine toi que j'ai une table qui fonctionne avec n++ models différents .
Vraiment merci de tes éclairages, tu m'as mis sur la bonne piste en effet. Me reste un point à dépasser et c'est ok.
Voilà ou j'en suis : J'ai une table "follows", un modèle "Follow" et je récupère bien mes followers et mes followings avec les requêtes suivantes depuis mon UserController :
//on récupère la liste des followings de l'utilisateur
$d['followings'] = $this->User->Following->find('all', array(
'conditions' => array('follower_id' => $this->Auth->user('id')),
)
);
$this->Session->write('Auth.User.followings', $d['followings']);
// debug($d['followers']); die();
//on récupère la liste des followers de l'utilisateur
$d['followers'] = $this->User->Follower->find('all', array(
'conditions' => array('following_id' => $this->Auth->user('id')),
)
);
$this->Session->write('Auth.User.followers', $d['followers']);
//debug($d['followers']); die();
Le seul truc c'est que je ne suis pas parvenu à obtenir ce résultat en faisant des liaisons dans les modèles mais en faisant une liaision à la volée avec bindModel dans le UserController juste avant ma requête, et ça donne ça :
$this->loadModel('Follow');
$this->User->Follower->contain(array('User'));
$this->User->Following->contain(array('User'));
//on crée les relations followers/users followings/users à la volée avec bindModel
$this->User->Follower->bindModel(
array('belongsTo' => array(
'User' => [
'className' => 'User',
'joinTable' => 'users',
'foreignKey' => 'follower_id',
]
)), false
);
$this->User->Following->bindModel(
array('belongsTo' => array(
'User' => [
'className' => 'User',
'joinTable' => 'users',
'foreignKey' => 'following_id',
]
)), false
);
Si je parviens à traduire ces relations plutôt dans les modèles, ce serait plus logique.
Là je n'ai rien dans mon modèle Follow et j'ai ça dans mon modèle User :
public $belongsTo= array(
'Following' => array(
'className' => 'Follow',
'foreignKey' => 'id',
'associationForeignKey' => 'Follow.follower_id',
//'unique' => true,
// 'conditions' => [ 'follower_id' => $this->User->id] //On passe la conditions dans l'association
// 'conditions' => array("Follow.follower_id = User.id")
),
);
public $hasMany = array(
'Follower' => array(
'className' => 'Follow', // le nom de la classe à laquelle on veut lier l'user
'foreignKey' => 'id', // le nom de la clé étrangère du modèle actuel
'associationForeignKey' => 'following_id',
// 'unique' => true,
// 'conditions' => ['following_id' => $this->AuthUser->id] //On passe la conditions dans l'association
// 'conditions' => array("Follow.following_id = User.id")
)
);
C'est tes foreignKey qui vont pas, c'est pas id mais following_id ou follower_id et enleve les index associationForeignKey
public $belongsTo= array(
'Following' => array(
'className' => 'Follow',
'foreignKey' => 'follower_id',
//'unique' => true,
// 'conditions' => [ 'follower_id' => $this->User->id] //On passe la conditions dans l'association
// 'conditions' => array("Follow.follower_id = User.id")
),
);
public $hasMany = array(
'Follower' => array(
'className' => 'Follow', // le nom de la classe à laquelle on veut lier l'user
'foreignKey' => 'following_id', // le nom de la clé étrangère du modèle actuel
// 'unique' => true,
// 'conditions' => ['following_id' => $this->AuthUser->id] //On passe la conditions dans l'association
// 'conditions' => array("Follow.following_id = User.id")
)
);
C'est ce que j'avais fini par faire logiquement mais ça ne fonctionne pas, j'ai une erreur sur la requête SQL en faisant ça.
Je verrai ca plus tard. Le système fonctionne parfaitement en utilisant bindModel. J'essaierai de trouver la solution pour lier directement les modèles.
En tout cas un grand merci parce que tes réponses m'ont vraiement aidé à avancer et à coder la fonctionnalité de façon plus logique. Sur les liaisons classiques, je suis OK mais sur le liaisons sur le même modèle j'ai encore des choses à apprendre :)
Me reste un point sur lequel je bute avec le bindModel, c'est le count avec counterCache, pour compter les followers et les followings à chaque nouvelle entrée dans la BDD (quand un user follow un autre user)
Demain je poste ma solution complète ici et je passe ce sujet en résolu parce que ça fait beaucoup :) J'en ouvrirai un autre pour finir sur le sujet, notamment sur le count.
Merci encore ! Réponses au top qui m'ont mis sur la bonne voie.