Doctrine build-schema et many to many

27 views
Skip to first unread message

Bruno

unread,
Jul 23, 2009, 9:07:28 AM7/23/09
to Symfony-fr
Bonjour à tous,

Je travail avec Mysql Workbench (anciennement DBDesigner) pour générer
ma base de données.
Une fois la conception terminée j'exporte le schema en SQL et exécute
les requêtes directement dans phpmyadmin pour construire ma base.

Ensuite, lorsque j'exécute la commande doctrine:build-schema, tout se
déroule sans erreur, mais le schéma généré ne respecte pas les
relations many-to-many.

Je prend l'exemple avec une table `joueur` lié à la table `competence`
par une table `joueur_competence`

Dans `joueur_competence`, mes index et clé étrangères sont
parfaitement généré depuis mysql workbench, mais dans mon schéma.yml,
je ne retrouve mes clés étrangères seulement dans JoueurCompetence,
mais plus dans Joueur ni dans Competence

Quelqu'un saurait me dire pourquoi la relation many-to-many disparait
au moment de la génération du schema par doctrine ? J'aimerai pouvoir
continuer de bosser avec mysql workbench sans me taper tous les
schemas à la main !

Merci de votre aide.

Bruno

g...@vandencruche.fr

unread,
Jul 24, 2009, 7:17:37 AM7/24/09
to symfo...@googlegroups.com
Bonjour,
Il faudrait déjà s'assurer que l'importation via PhpMyAdmin s'est correctement déroulé.

Avez vous vérifier la structure de la base Mysql avant de générer le schéma.yml ?

Cdt,

Lélio ML

unread,
Jul 24, 2009, 7:43:08 AM7/24/09
to symfo...@googlegroups.com
Les many2many ne remonteront jamais : ce sont pour la base des many2one
entre différentes tables. (2 many2one et 3 tables pour un many2many)
Il faut que tu les paramètres à la main dans le schema.yml


-----Message d'origine-----
De : symfo...@googlegroups.com [mailto:symfo...@googlegroups.com] De la
part de Bruno
Envoyé : jeudi 23 juillet 2009 15:07
À : Symfony-fr
Objet : [symfony-fr] Doctrine build-schema et many to many

Bruno

unread,
Jul 24, 2009, 11:24:30 AM7/24/09
to Symfony-fr
Bonjour et merci de votre aide,

@g...@vandencruche.fr :
Oui la structure est impeccable, toutes les contraintes de clés
étrangères sont respectés, et tous les contrôles fonctionnent bien,
c'est bien pour ça que je trouve étrange que la génération du schema
ne les reproduise pas.

@Lelio ML :
Effectivement, pour ma table `JoueurCompetence`, ce sont deux
many2one, vers Joueur et Competence qui sont bien reproduit sur le
schema.yml.
En revanche, ce que je n'arrive pas a faire, c'est récupérer toutes
les compétences d'un joueur en faisant Joueur->competence, ce que j'ai
vu fonctionner sur un tutoriel.
Actuellement je suis oblige de faire $joueur->getJoueurCompetence() et
enfin dans ma boucle un $joueurCompetence->getCompetence(), ce qui a
pour effet de me générer une nouvelle requête SQL.
Peut être que je m'y prend mal quelque part ?
Voici l'extrait de mon schema.yml


# Joueur
Joueur:
tableName: joueur
columns:
id:
type: integer(4)
unsigned: 1
primary: true
autoincrement: true
pseudo:
type: string(50)
notnull: true

#Competence
Competence:
tableName: competence
columns:
id:
type: integer(4)
unsigned: 1
primary: true
autoincrement: true
nom:
type: string(250)
default: ''
notnull: true

#JoueurCompetence
JoueurCompetence:
tableName: joueur_competence
columns:
competence_id:
type: integer(4)
unsigned: 1
primary: true
joueur_id:
type: integer(4)
unsigned: 1
primary: true
utilisations:
type: integer(4)
default: '0'
notnull: true
relations:
Joueur:
local: joueur_id
foreign: id
type: one
Competence:
local: competence_id
foreign: id
type: one


Encore merci de votre aide

Bruno

Colin Darie

unread,
Jul 24, 2009, 11:43:47 AM7/24/09
to symfo...@googlegroups.com
C'est normal que $Joueur->Competence ne marche pas car dans ton schema la relation m2m entre Joueur et Competence n'est pas définie. Il faut la rajouter manuellement comme on te l'a suggéré.

Par exemple :

Joueur:
columns:
[...]
relations:
Competences:
class: Competence
refClass: JoueurCompetence
local: joueur_id
foreign: competence_id
foreignAlias: Joueurs


Ainsi tu pourras faire $Joueur->Competences qui retournera une instance de Doctrine_Collection (ou un tableau) avec toutes les compétences. Vice-versa, si t'en as besoin tu pourras faire $Competence->Joueurs qui retournera les joueurs d'une compétence (grâce au foreignAlias).


2009/7/24 Bruno <bwin...@gmail.com>:
> --~--~---------~--~----~------------~-------~--~----~
> Vous avez reçu ce message, car vous êtes abonné au groupe Groupe "Symfony-fr" de Google Groupes.
>  Pour transmettre des messages à ce groupe, envoyez un e-mail à
> l'adresse symfo...@googlegroups.com
>  Pour résilier votre abonnement à ce groupe, envoyez un e-mail à
> l'adresse symfony-fr+...@googlegroups.com
>  Pour afficher d'autres options, visitez ce groupe à l'adresse http://groups.google.com/group/symfony-fr?hl=fr
> -~----------~----~----~----~------~----~------~--~---
>
>

signature.asc

Bruno

unread,
Jul 24, 2009, 5:27:47 PM7/24/09
to Symfony-fr
Ok Colin, mais justement, le but de ma question était de savoir si il
n'y avait pas un moyen de rendre cette tache automatique, car sans ça,
je risque de me retaper ce changement à la main à chaque fois que je
veux mettre à jour mon schéma sous Mysql Workbench...
Enfin vous êtes deux à me proposer cette unique solution, je pense que
je n'aurai pas le choix, et c'est bien dommage !

Merci de votre aide, mais si quelqu'un passe par là et a une solution,
je suis toujours preneur !

Bruno

On 24 juil, 17:43, Colin Darie <colinda...@gmail.com> wrote:
> C'est normal que $Joueur->Competence ne marche pas car dans ton schema la relation m2m entre Joueur et Competence n'est pas définie. Il faut la rajouter manuellement comme on te l'a suggéré.
>
> Par exemple :
>
> Joueur:
>   columns:
>   [...]
>   relations:
>     Competences:
>       class: Competence
>       refClass: JoueurCompetence
>       local: joueur_id
>       foreign: competence_id
>       foreignAlias: Joueurs
>
> Ainsi tu pourras faire $Joueur->Competences qui retournera une instance de Doctrine_Collection (ou un tableau) avec toutes les compétences. Vice-versa, si t'en as besoin tu pourras faire $Competence->Joueurs qui retournera les joueurs d'une compétence (grâce au foreignAlias).
>
> 2009/7/24 Bruno <bwinc...@gmail.com>:
>  signature.asc
> < 1 000AfficherTélécharger

Colin Darie

unread,
Jul 25, 2009, 11:42:43 AM7/25/09
to symfo...@googlegroups.com
C'est à mon avis normal que la conversion base > schéma ne conserve pas les relations many-to-many car elle ne fait que retranscrire la structure des tables existantes, et aux yeux de la base le m2m n'est rien d'autre que 2 relations one-to-many.

Si tu souhaites continuer à utiliser MySQL Workbench, pour éviter à chaque fois de rajouter ces relations à la main dans le schéma, et si t'es sous un système linux, tu n'as qu'à créer un simple patch qui contient ces quelques lignes supplémentaires, patch que tu n'auras plus qu'à appliquer simplement après chaque génération du schema. Tu peux même écrire une tache symfony qui lancera doctrine:build-schema puis qui appliquera le patch immédiatement après pour tout faire d'un coup.

Autrement je pense que ça vaut vraiment la peine d'investir du temps pour apprendre à écrire directement et rapidement les schemas en yml, ça évite bien des surprises dans ce genre car là ce sont les m2m qui ne sont pas conservées, mais une autre fois ça sera peut-être autre chose dont tu ne te rendras pas forcément compte (sans parler du fait que ça ne t'écrira jamais le yml pour utiliser des behavior !) Une fois qu'on a l'habitude d'écrire les schemas manuellement, ça va vraiment très vite même pour des très grandes structures (rien n'empêche d'ailleurs de modéliser la base par un outil graphique avant si on le souhaite).


2009/7/24 Bruno <bwin...@gmail.com>:
signature.asc

Bruno

unread,
Jul 26, 2009, 7:16:04 AM7/26/09
to Symfony-fr
Je suis d'accord avec toi sur le principe d'écrire le schéma
directement en .yml, mais je trouvais l'outils Mysql Workbench
vraiment intéressant dans la mesure ou il me permettait d'avoir un
schéma visuel ET une base en parfait harmonie constamment.
Cependant, il est vrai que je peux toujours faire du "reverse
engeneering" pour retrouver mon schema à partir de ma base généré
depuis le .yml...

En attendant vos réponses, j'ai fait quelque chose d'autre, j'aimerai
en connaître ton avis.
En fait mon problème c'était que lorsque je demandais la table relié
en many to many, il me généré un surplus de requête pour obtenir les
infos entrée par entrée, j'ai donc modifié mon fichier
JoueurCompetenceTable.php pour surcharger la requête
getJoueurCompetence() et lui faire renvoyer la liste des compétences
en jointure avec la table joueur_competence.
L'avantage de cela c'est que je contrôle parfaitement mes requêtes et
comme je ne touche pas au fichier *Base.php, je n'ai à le faire qu'une
fois.
L'inconvénient c'est que je vais devoir surcharger cette méthode
chaque fois que j'aurai besoin de récupérer une table d'une relation
many2many, et j'en ai pas mal !

Je vais réfléchir à ta solution à nouveau, et tenter d'écrire mon
schéma directement en .yml.

Merci de tes conseils et de ton temps

On 25 juil, 17:42, Colin Darie <colinda...@gmail.com> wrote:
> C'est à mon avis normal que la conversion base > schéma ne conserve pas les relations many-to-many car elle ne fait que retranscrire la structure des tables existantes, et aux yeux de la base le m2m n'est rien d'autre que 2 relations one-to-many.
>
> Si tu souhaites continuer à utiliser MySQL Workbench, pour éviter à chaque fois de rajouter ces relations à la main dans le schéma, et si t'es sous un système linux, tu n'as qu'à créer un simple patch qui contient ces quelques lignes supplémentaires, patch que tu n'auras plus qu'à appliquer simplement après chaque génération du schema. Tu peux même écrire une tache symfony qui lancera doctrine:build-schema puis qui appliquera le patch immédiatement après pour tout faire d'un coup.
>
> Autrement je pense que ça vaut vraiment la peine d'investir du temps pour apprendre à écrire directement et rapidement les schemas en yml, ça évite bien des surprises dans ce genre car là ce sont les m2m qui ne sont pas conservées, mais une autre fois ça sera peut-être autre chose dont tu ne te rendras pas forcément compte (sans parler du fait que ça ne t'écrira jamais le yml pour utiliser des behavior !) Une fois qu'on a l'habitude d'écrire les schemas manuellement, ça va vraiment très vite même pour des très grandes structures (rien n'empêche d'ailleurs de modéliser la base par un outil graphique avant si on le souhaite).
>
> 2009/7/24 Bruno <bwinc...@gmail.com>:
>  signature.asc
> < 1 000AfficherTélécharger

Colin Darie

unread,
Jul 26, 2009, 2:59:07 PM7/26/09
to symfo...@googlegroups.com
Il en effet primordial d'un point de vue des performances de surcharger les getXXX() pour faire la ou les jointures adéquates lorsque les objets correspondants ont besoin systématiquement de données aggrégées depuis d'autres tables. C'est d'ailleurs valable quel que soit le type de relation.



2009/7/26 Bruno <bwin...@gmail.com>
signature.asc

Bruno

unread,
Jul 26, 2009, 8:20:59 PM7/26/09
to Symfony-fr
Je reviens vers toi pour deux questions.

La première, comment fais-t-on pour faire référence aux champs
contenus dans la table de liaison ? Si on reprend mon exemple, on voit
que ma table `JoueurCompetence` contient un champ `utilisations` qui
permet de stocker le nombre de fois que le joueur a utilisé la
compétence.
Comment fais-t-on pour y faire référence ? Est-ce que $joueur-
>competences->getUtilisations() pourrait marcher ?

Autre question, sûrement un peu bête, mais j'aimerai savoir comment
fais-t-on pour insérer une entrée dans la table de liaison ? Et
toujours en prenant en compte ce champs `utilisations` !

Je te remercie une fois de plus de ton aide :-)

Bruno

On 26 juil, 20:59, Colin Darie <colinda...@gmail.com> wrote:
> Il en effet primordial d'un point de vue des performances de surcharger les getXXX() pour faire la ou les jointures adéquates lorsque les objets correspondants ont besoin systématiquement de données aggrégées depuis d'autres tables. C'est d'ailleurs valable quel que soit le type de relation.
>
> 2009/7/26 Bruno <bwinc...@gmail.com>
>  signature.asc
> < 1 000AfficherTélécharger

Bruno

unread,
Jul 27, 2009, 12:35:34 PM7/27/09
to Symfony-fr
Je répond à ma première question de moi même, pour insérer un champ en
many2many, il suffit de faire dans mon cas :
$joueur = new Joueur();
$joueur->Competences[] = $idCompetence;
$joueur->Competences[] = $idCompetence2;
$joueur->save();

En revanche, je n'ai pas trouvé comment mettre à jour mon champ
`utilisations` contenu dans ma table `JoueurCompetence`... faut-il le
déclarer dans les relations du schema.yml ?

Encore merci par avance

Colin Darie

unread,
Jul 27, 2009, 2:23:37 PM7/27/09
to symfo...@googlegroups.com
Le champ utilisations appartient à l'objet JoueurCompetence, donc il doit être manié à partir cet objet.
Pour arriver à cet objet il y a 2 moyens, à toi de voir celui qui correspond le mieux à ton cas :
- soit tu récupères cet objet comme n'importe quel autre et donc tu peux faire classiquement $JoueurCompetence->utilisations = y;
- soit tu passes par le joueur et là tu peux faire $Joueur->Competences[0]->utilisations = y ou $Joueur->Competences[1]->utilisations = z etc...

Dans ce derniers cas, tous les objets JoueurCompetence sont dans le tableau (en fait l'objet Doctrine_Collection) $Joueur->Competences , mais comme tu le vois c'est pas très pratique de passer par cet tableau car son indexation n'est pas très utilisable.

C'est là qu'intervient le keyword "INDEX BY" à écrire dans la méthode getXXX() que tu as surchagée précédemment. Cela permet de dire à doctrine d'indexer ce tableau par un champ que tu indiques toi : http://www.doctrine-project.org/documentation/manual/1_1/en/dql-doctrine-query-language#indexby-keyword. Donc dans ta jointure tu écriras par exemple
->leftJoin('JoueurCompetence jc INDEX BY jc.competence_id')

Et donc tu pourras faire $Joueur->Competences[13]->utilisations = y pour accéder à l'objet JoueurCompetence ayant 13 comme competence_id.

En revanche attention car dans ce cas, $joueur->Competences[] = $idCompetence aboutira à dés résultats imprévisibles vu que dans ce cas tu ne controles pas l'index du tableau, ça peut donc rentrer en conflit avec le tableau récupéré par INDEX BY.

Le mieux, pour ajouter une compétence à un joueur, c'est de créer le joueur, le sauver, puis créer un objet JoueurCompetence par compétence en assignant à chacun d'entre eux l'id du joueur ajouté et de la compétence associée. C'est la méthode la plus performante et celle recommandée car indépendante des INDEX BY et des collections. cf http://www.doctrine-project.org/documentation/manual/1_1/en/working-with-models#many-to-many-relations

Bon courage

2009/7/27 Bruno <bwin...@gmail.com>:
signature.asc

Bruno

unread,
Jul 29, 2009, 3:48:10 AM7/29/09
to Symfony-fr
Encore une fois merci pour toutes ces informations et le temps que tu
y as consacré !
J'espère que ce sujet servira a beaucoup de monde.

A plus

Bruno

On 27 juil, 20:23, Colin Darie <colinda...@gmail.com> wrote:
> Le champ utilisations appartient à l'objet JoueurCompetence, donc il doit être manié à partir cet objet.
> Pour arriver à cet objet il y a 2 moyens, à toi de voir celui qui correspond le mieux à ton cas :
> - soit tu récupères cet objet comme n'importe quel autre et donc tu peux faire classiquement $JoueurCompetence->utilisations = y;
> - soit tu passes par le joueur et là tu peux faire $Joueur->Competences[0]->utilisations = y ou  $Joueur->Competences[1]->utilisations = z etc...
>
> Dans ce derniers cas, tous les objets JoueurCompetence sont dans le tableau (en fait l'objet Doctrine_Collection) $Joueur->Competences , mais comme tu le vois c'est pas très pratique de passer par cet tableau car son indexation n'est pas très utilisable.
>
> C'est là qu'intervient le keyword "INDEX BY" à écrire dans la méthode getXXX() que tu as surchagée précédemment. Cela permet de dire à doctrine d'indexer ce tableau par un champ que tu indiques toi :http://www.doctrine-project.org/documentation/manual/1_1/en/dql-doctr.... Donc dans ta jointure tu écriras par exemple
> ->leftJoin('JoueurCompetence jc INDEX BY jc.competence_id')
>
> Et donc tu pourras faire  $Joueur->Competences[13]->utilisations = y pour accéder à l'objet JoueurCompetence ayant 13 comme competence_id.
>
> En revanche attention car dans ce cas, $joueur->Competences[] = $idCompetence aboutira à dés résultats imprévisibles vu que dans ce cas tu ne controles pas l'index du tableau, ça peut donc rentrer en conflit avec le tableau récupéré par INDEX BY.
>
> Le mieux, pour ajouter une compétence à un joueur, c'est de créer le joueur, le sauver, puis créer un objet JoueurCompetence par compétence en assignant à chacun d'entre eux l'id du joueur ajouté et de la compétence associée. C'est la méthode la plus performante et celle recommandée car indépendante des INDEX BY et des collections. cfhttp://www.doctrine-project.org/documentation/manual/1_1/en/working-w...
>
> Bon courage
>
> 2009/7/27 Bruno <bwinc...@gmail.com>:
> ...
>
> plus de détails »
>
>  signature.asc
> < 1 000AfficherTélécharger
Reply all
Reply to author
Forward
0 new messages