Skip navigation

Salut à toutes et à tous,

Epsilon est une lettre grec qui signifie en mathématique un élément très petit. Dans 1984, il signifie la plus basse caste de la hiérarchie des Hommes. Entre autres significations pleines de sens

Ce petit billet donc pour vous présenter l’avancée actuelle du projet d’aide à la modélisation de schémas pour sfDoctrine, plug-in de Symfony (cadre applicatif Php5) qui le ponte à Doctrine (Objet-Relational Mapper en Php5).

Cet outil repose, vous l’aurez deviné, fervants lecteurs de mon blog, exclusivement sur des outils open-source que sont Eclipse et ses plug-ins EMF et Epsilon.

Petites explications avant tout : Doctrine est un ORM qui permet de relater un objet en une table relationnel, et vice-versa. Ceci permet d’écrire des logiciels à l’aide du paradigme "objet" tout en utilisant les meilleurs techniques de persistances que sont les Relational DataBase Management Systems (RDBMS ou SGDBR, type MySQL ou PostgreSQL dans l’univers open-source).

Il y a donc des classes, comme on parle de type de choses, puis ces choses à proprement parlé.

Dans le monde de sfDoctrine, on parle donc de Classes et d’Objets, qui sont les instances de classes, de la même manière qu’on parle de "voitures" et de "ma voiture", ou de types de choses et des choses en rapport à ces types. "Typiquement", une "voiture" (la mienne par exemple) est une instance de la classe "Voiture".
Dans les modèles suivants, les boites jaunes représentes donc des types (ou des classes, techniquement) et les liens représentes ces "relations".
On pourra par la suite "instancier" ces types de choses (comme on le verra avec la classe Class qui permettra de définir, en l’instanciant par deux fois, les "Classes" Produit et Categorie-). Il est hyper important de comprendre qu’on parle à différents moments dans différents niveaux d’abstraction. Voyez le contexte du vocable pour pouvoir vous repérer. Si ambiguïté, dites le moi en commentaire, j’expliciterai ou corrigerai au besoin.

sfDoctrine relate ces deux univers (objet et relationnel) en les mixant au sein d’un même type d’objet, appelé Class (qui est différent de celui du paradigme objet, par "extension" abstraite : on saute de niveau d’abstraction, l’un permettant à l’autre d’"exister" -ou plutôt d’être défini comme on l’entend-).

Trêve de bla-bla, voici une capture d’écran d’un simple méta-modèle de sfDoctrine réalisé à titre d’exemple :

Méta-modèle de sfDoctrine, diagrame d'entré

On voit ici qu’un modèle permet de définir des classes, et que les deux sont des choses nommés, elles ont obligatoirement un nom.

Voici maintenant le diagramme du méta-modèle des "columns" qui est partie intégrante du méta-modèle ci-dessus (qu’on peut voir en tant que "package" ou "dossier" nommé "Columns") :

Description de la EClass Column d'une EClass Class

Description de la EClass Column d'une EClass Class

On voit ici qu’une classe possèdes 0 à plusieurs colonnes, et qu’une colonne est elle aussi une chose obligatoirement nommée, en plus de posséder obligatoirement un type qui doit avoir un max de défini.

Et enfin le diagramme des "Relations" :

Méta-modèle des Relations d'une Class

On voit ici qu’une classe peut posséder 0 ou plusieurs relations, relation (en italique, soulignant que c’est "abstrait" -mais à ce niveau-ci d’abstraction-) qui peut être de trois sortes:
une relation de type mono-valuée OneToOne ou un à un, qui permet de dire, par exemple, qu’une voiture n’a qu’un moteur,
une relation de type multi-valué ou un à plusieurs, qui permet de dire, par exemple, qu’une voiture peut avoir plusieurs roues,
une relation de type multi-valué bi-partie ou plusieurs à plusieurs, qui permet de dire, par exemple, qu’un produit peut être lié à plusieurs catégories et qu’une catégorie peut être liée à plusieurs produits.

La big-picture du point de vue du phénomène "Class" :

Big-picture d'un point de vue de Class

Pour finir, et faire comprendre au lecteur ce que permet l’excellentissime plug-in Epsilon, voici une capture "finale" d’un modèle que l’on va créer ensemble ici, pas-à-pas, en usant d’assistance pour créer les colonnes ids, la classe ProduitCategorie ainsi qu’une relation m:m sur deux. Ce sera plus clair par la suite, vous verrez.

Simple modèle d'exemple

Ici, nous avons définis deux types d’objets que sont "Produit" et "Catégorie", nous avons dit que les deux possèdent une colonne nommée "id" (qui s’inscrit dans une perspective SGBDR donc) et qu’ils sont tous deux liés par une relation "ManyToMany", ou "plusieurs-à-plusieurs" comme dit plus haut (perspective "objet").

Maintenant, pour aider dans la conception de (méta-)modèle, Epsilon fournit tout un ensemble de langages permettant d’écrire des outils de transformation, validation, d’assistance, de mélange, entre autres, qui sont utilisables ensuite par la personne créant un modèle.

Ici, et pour l’instant, j’ai créé trois outils d’assistance :

addIdentifierAsPrimaryKeyToClass pour les EClass Class, qui permet d’ajouter une colonne "id" à une classe en particulier ou à toutes les classes du modèle, si ces colonnes n’existent pas bien sûr (pas de doublon, vérification d’existence par condition),

createManyToManyReferenceClass pour les EClass ManyToMany (spécialisation de la EClass abstraite Relation), qui permet d’aider à créer une classe de cross-référence entre deux classes (sans doublon aussi, évidement) en ayant seulement précisé l’élément relaté par la relation m:m,

setClassPluralAndSingularProperties pour les EClass Class, qui permet de spécifier automatiquement les propriétés "plural" et "singular" qui sont de type EString et qui permettent de préciser les formes plurielle et singulier d’une EClass Class.

Allons dans le détail, et commençons simplement par le dernier qui est le plus simple :

Je créé un fichier modèle de mon méta-modèle (j’ouvre mon méta-modèle puis clique-droit sur l’EClass "Model" et "Create Dynamic Instance", je séléctionne le fichier qui supportera l’instance du modèle)

Première étape : créer une instance du modèle

puis je l’enregistre (n’importe où, peu importe).

Seconde étape : créer une classe d'objet

Vous remarquez que j’ai nommé mon "Model" avec la chaîne de caractère "EspaceDuSommeil".

Une fois que j’ai cliqué, je me retrouve avec un "Model" ayant un "enfant" (child) de type Class qui ne porte pas de nom.
En cliquant dessus la fenêtre du bas nommée "Properties" (que l’on voit dans l’image ci-dessus en bas qui permet de voir "EspaceDuSommeil") affiche les propriétés relatives à cet élément, ainsi il me permet de cliquer sur la propriété "Name" et d’y définir le "nom" de mon objet "Class" (si vous remontez jusqu’à la première image, vous remarquerez la relation d’héritage entre Named et Class, qui fait que Class "is_a" (est un) Named, Named voulant dire qu’il est nommé -j’aurai pu mettre Nameable pour dire qu’il peut être nommé, mais cela dépend des besoins, ici il me faut absolument un nom-). J’y saisi donc le mot "Produit", et voici :

Troisième étape : saisir le nom du type d'objet

On fait de même pour créer un type d’objet nommé "Catégorie", puis on va s’occuper de spécifier les propriétés "plural" et "singular" en cliquant seulement sur un petit bouton, grâce à mon script écrit en EWL, langage définit par le plug-in Epsilon :

Quatrième étape : cliquer sur l'assistant pour automatiquement définir les propriétés "plural" et "singular"

Remarquez que les champs "Plural" et "Singular" de la fenêtre de propriété sont vides. Pour l’instant.

Cinquième étape : se fouler le doigt en cliquant

Et tadam ! Voilà le résultat ! Notez qu’il n’y a qu’ajout d’un "s" final pour créer la forme plurielle. La forme singulière est une copie du nom de la classe, simplement. Après, c’est open-source, donc qui veut modifier le comportement n’a qu’à apprendre et modifier selon ses nécessitées.

Pour comprendre, pour qui voudra, voici le script qui permet de réaliser cela :

wizard setClassPluralAndSingularProperties {

guard : self.isTypeOf(Class) and self.name.size() > 0

title : ‘Set plural and singular properties of ‘ + self.name

do {
self.plural := self.generatePluralProperty();
self.singular := self.generateSingularProperty();
}
}

wizard setModelClassesPluralAndSingularProperties {

guard : self.isTypeOf(Class)

title : ‘Set plural and singular properties of classes (Model-wide)’

do {
for( class in Class.allInstances() )
{
class.plural := class.generatePluralProperty();
class.singular := class.generateSingularProperty();
}
}
}

operation Class generatePluralProperty () : String {
return self.name + ‘s’;
}

operation Class generateSingularProperty () : String {
return self.name;
}

Comme vous l’aurez sûrement remarqué, j’ai codé deux "wizards" (assistants) différents, l’un ne se rapproche qu’à la classe séléctionnée lors du clique droit, l’autre s’occupe de définir ce qu’il y a à définir poru toutes les classes (à l’aide de la boucle for(… in …){}).

Simple, non -je parle aux développeurs ici ;-D- ?

Voyons un peu plus complexe comme script, celui permettant d’ajouter une colonne "id" aux classes et qui s’utilise exactement de la même manière -séléction de la classe en question puis clique-droit->wizards->"add Id… for XXX" ou [..]model-wide":

wizard addIdentifierAsPrimaryKeyToClass {

guard : self.isTypeOf(Class) and not self.column.exists(c|c.name.matches(‘id’))

title : ‘Add an Id column as primary key to ‘ + self.name

do {

self.column.add( self.generateIdPKUnikColumn() );
}
}

wizard addIdentifiersAsPrimaryKeyToClasses {

guard : self.isTypeOf(Class)

title : ‘Add an Id column as primary key to classes (Model-wide)’

do {
for( class in Class.allInstances() )
{
if( not class.column.exists(c|c.name.matches(‘id’) ) )
{
class.column.add( class.generateIdPKUnikColumn() );
}
}
}
}

operation Class generateIdPKUnikColumn () : Column {
var idcolumn : Column;
idcolumn := Column.createInstance();
idcolumn.name := ‘id’;
idcolumn.primary := true;
idcolumn.unique := true;
var idtype : Model!Integer;
idtype := Model!Integer.createInstance();
idtype.max = 20;
idcolumn.type = idtype;
return idcolumn;
}

Et voilà !

Sixième étape : lâcher de clique pour ajout de la colonne 'id' à toutes les classes

Tadam :

Septième étape : apprécier le résultat :-D

Maintenant, pensez au nombre de manipulations gagnés lorsqu’il y a 10,50,100 classes ? :-)
Les affictionados de Doctrine me diront : imbécile, Doctrine n’a pas besoin qu’on spécifie la colonne id ! Oui, certes, mais ce n’est qu’un exemple d’utilisation ;-P

Voyons autrement plus complexe, la création en un clique d’une classe de référence pour une association m:m :

Code:

wizard createManyToManyReferenceClass
{
guard : self.isTypeOf(ManyToMany) and self.relates.isDefined()

title: ‘Create ManyToMany\’s Reference Class between ‘ + self.owner.name + ‘ and ‘ + self.relates.name

do
{
self.owner.definedIn.defines.add( self.createReferenceClass() );
}
}

operation ManyToMany createReferenceClass() : Class {
self.name := self.relates.name;

–define variable container for reference Class of ManyToMany.refClass assigned to both ManyToMany sides
var refClass : Class;
refClass := self.owner.definedIn.defines.selectOne(c|c.name.matches(
self.owner.name.firstToUpperCase() + self.relates.name.firstToUpperCase()
)
);
if( not refClass.isDefined() )
{
refClass := Class.createInstance();
refClass.name := self.owner.name.firstToUpperCase() + self.relates.name.firstToUpperCase();
}

–defines self.owner refClass reference
self.refClass := refClass;

–define variable containers for columns pks
var a : Column;
var b : Column;
a := Column.createInstance();
b := Column.createInstance();
a.name := self.relates.getIdColumnName();
b.name := self.owner.getIdColumnName();
a.primary := true;
b.primary := true;

–define variable containers for OneToMany holders in refClass
var aR : OneToMany;
var bR : OneToMany;
aR := OneToMany.createInstance();
bR := OneToMany.createInstance();
aR.relates := self.relates;
bR.relates := self.owner;
aR.name := self.relates.name;
bR.name := self.owner.name;

–add columns and relations to refClass
refClass.column.add(a); refClass.column.add(b); refClass.relation.add(aR); refClass.relation.add(bR);

–add ManyToMany relation to related class
var rC : ManyToMany;
rC := ManyToMany.createInstance();
rC.owner := self.relates;
rC.name := self.owner.name;
rC.refClass := refClass;
rC.relates := self.owner;
self.relates.relation.add(rC);

return refClass;
}

operation Class getIdColumnName() : String {
return self.name.toLowerCase() + ‘_id’;
}

operation Class getPluralName() : String {
return self.name + ‘s’;
}

Création d'une relation m:m entre Produit et Catégorie

Je définie comme nécessaire la propriété "relates" qui me permet d’appeler par la suite le script d’automation :

Définition de la classe relaté par la propriété m:m de la classe Produit comme étant Catégorie

Puis j’appel le script qui s’occupe de tout définir pour moi, pour autant que cela me convienne :

Dernière étape : cliqueeerrrrr

Appréciez VOTRE travail :

La classe de cross-référence est créée automatiquement, la classe cross-référencée Categorie est automatiquement modifiée en conséquence ainsi que la classe Produit et leurs relations m:m en regard

La m:m de Categorie/Produit automatiquement créée

La classe de cross-référence entre Produit et Categorie automatiquement créée et ses propriétés

Tiens, j’ai oublié de coder l’ajout des formes plurielles et singulières, qu’à cela ne tienne, on modifie le code et c’est repartie !
En modifiant le script, je me rend compte que j’ai oublié de faire pas mal de checks sur l’existence d’élements, ce qui implique que si on exécute deux fois cet assistant, il crééra d’autant de classes de référence, c’est un comportement non désiré : hop on modifie en conséquence :

wizard createManyToManyReferenceClass
{
guard : self.isTypeOf(ManyToMany) and self.relates.isDefined()

title: ‘Create ManyToMany\’s Reference Class between ‘ + self.owner.name + ‘ and ‘ + self.relates.name

do
{
self.owner.definedIn.defines.add( self.createReferenceClass() );
}
}

operation ManyToMany createReferenceClass() : Class {
self.name := self.relates.name;

–define variable container for reference Class of ManyToMany.refClass assigned to both ManyToMany sides
var refClass : Class;
refClass := self.owner.definedIn.defines.selectOne(c|c.name.matches(
self.owner.name.firstToUpperCase() + self.relates.name.firstToUpperCase()
)
);
if( not refClass.isDefined() )
{
refClass := Class.createInstance();
refClass.name := self.owner.name.firstToUpperCase() + self.relates.name.firstToUpperCase();
}

–set plural and singular forms for refClass
refClass.plural := self.owner.getPluralName() + self.relates.getPluralName();
refClass.singular := self.owner.name + self.relates.name();

–defines self.owner refClass reference
self.refClass := refClass;

–define variable containers for columns pks
var a : Column;
var b : Column;
var aToAdd: Boolean;
var bToAdd: Boolean;

aToAdd = false; bToAdd = false;
–try to get existing columns
a := refClass.column.selectOne(c|c.name.matches(self.relates.getIdColumnName()));
b := refClass.column.selectOne(c|c.name.matches(self.owner.getIdColumnName()));

–if none exist, create them and mark watchdogs for further add
if( not a.isDefined())
{
a := Column.createInstance();
a.name := self.relates.getIdColumnName();
aToAdd := true;
}
if( not b.isDefined())
{
b := Column.createInstance();
b.name := self.owner.getIdColumnName();
bToAdd  := true;
}
a.primary := true;
b.primary := true;

–define variable containers for OneToMany holders in refClass
var aR : OneToOne;
var bR : OneToOne;
var aRtoAdd: Boolean;
var bRtoAdd: Boolean;
aRtoAdd := false; aRtoAdd := false;
–try to get existing relations
aR := refClass.relation.selectOne(r|r.name.matches(self.relates.name));
bR := refClass.relation.selectOne(r|r.name.matches(self.owner.name));

–if none exists, create them and mark watchdogs for further add
if( not aR.isDefined()){
aR := OneToOne.createInstance();
aR.name := self.relates.name;
aRtoAdd := true;
}
if( not bR.isDefined()){
bR := OneToOne.createInstance();
bR.name := self.owner.name;
bRtoAdd := true;
}
aR.relates := self.relates;
bR.relates := self.owner;

–add columns and relations to refClass only if watchdog says to do so
if(aToAdd){ refClass.column.add(a); }
if(bToAdd){ refClass.column.add(b); }
–relations
if(aRtoAdd){ refClass.relation.add(aR); }
if(bRtoAdd){ refClass.relation.add(bR); }

–add ManyToMany relation to related class
var rC : ManyToMany;
var rCtoAdd: Boolean;
rCtoAdd := false;
rC := self.relates.relation.selectOne(r|r.name.matches(self.owner.name));
if(not rC.isDefined())
{
rC := ManyToMany.createInstance();
rC.name := self.owner.name;
rCtoAdd := true;
}
rC.owner := self.relates;
rC.refClass := refClass;
rC.relates := self.owner;

–add relation to refClass only if needed
if(rCtoAdd){ self.relates.relation.add(rC); }

return refClass;
}

operation Class getIdColumnName() : String {
return self.name.toLowerCase() + ‘_id’;
}

operation Class getPluralName() : String {
return self.name + ‘s’;
}

j’ai mis en gras autant que j’ai pu les différences.

Et voilà, on aura beau lancer plusieurs fois de suite l’assistant, il ne crééra pas plusieurs classes de références poluantes ;-)

On finira avec une capture d’écran de la classe de cross-référence :

OneToOne relation de la classe de référence entre celle-ci et Produit

"Et voilà !", il ne reste plus qu’à écrire un transformateur de modèle à modèle (EGL, Epsilon Generative Language)!

Mais avant ça, il sera bien utile d’écrire un validateur de modèle (EVL, Epsilon Validation Language) !

Comme vous l’aurez compris, le tout pourra être écrit à l’aide du plug-in Epsilon !

Alors, le développement dirigé par les modèles, C’EST PAS DE LA BALLE ? :-D

Le prochain billet ("Les Epsilons endoctrinés génèrent pour se libérer") portera sur la génération de code : transformer le modèle dans sa syntaxe abstraite en un fichier concret ("./config/doctrine/schema.yml"), qui, on le verra, sera utilisé par sfDoctrine/Symfony pour générer des classes Php5 de liaison Objet-Table Relationnel, mais aussi, dans la même perspective, qui permettra de générer des outils de gestion type CRUD en quelques lignes de commandes (voir symfony-project.org et son tutoriel Jobeet pour Doctrine).

Enfin je tiens à remercier particulièrement d’abord les "teams" Eclipse, EMF et GMF, entre autres et surtout Dimitris Kolovos qui est l’initiateur de l’outil Epsilon. Sans quoi tout ça ne serai possible !

N.B.: la réalisation du méta-modèle, du modèle d’exemple, des scripts et de ce billet ont nécessités un peu plus de 4h.
Pensez à relativiser le temps pris lorsqu’on connait l’outil, que le transformateur modèle->code (schema.yml), le validateur de modèle et autres assistants seront réalisés quant à la réalisation d’un schéma complexe pour sfDoctrine…
Relativiser tout ça quant à l’écriture d’applications web via l’ingénierie par les modèles (perte de temps dans la résolution de bugs, d’écriture du code en suivant les règles de coding, etc). C’est tout simplement monstrueux comme capitalisation (je parle bien d’un capital HUMAIN, pas spéculatif).

A bon entendeur ;-)

(copyleft until credits) Texte et images Stéphane Erard.

About these ads

4 Commentaires

  1. Intéressant ton tutoriel sur la création d’un méta-modèle. Cette approche est plutôt rare surtout dans le développement web.

    Cependant, dans la définition d’un schema YAML, on y défini les modèles et leurs relations dans une syntaxe plutôt abstraite : ‘hasMany’, ‘hasOne’.
    Cette définition n’est-elle pas déjà une sorte de ‘métamodelage’ ?

  2. Salut !

    Cette approche est rare "à notre niveau", mais dans l’industrie lourde type BMW, EADS, Alcatel, c’est loin d’être rare, c’est même de plus en plus courant (voir Product Line Management qui permet de créer des voitures par options, réalisé à l’aide de tels outils, entre autres exemples concrets).

    Je travail justement à rendre ça "populaire" en aidant à le faire comprendre. D’où ce billet. Après, si on ne comprend pas, on doit poser des questions. Ceux qui ne posent pas de question, je ne pourrai rien pour eux :-( -Internet, pas face-à-face, etc ^^)

    Le schéma YAML, c’est la même "chose" mais dans une "représentation" différente. Le YAML est par définition permissif et est une représentation possible différente d’un fichier XML par exemple, mais l’inconvénient est qu’on perd la définition de la structuration. Bref YAML c’est bien pour les petits besoins, XML c’est mieux lorsqu’on a besoin de structuration, de validation, de transformation, etc.
    Et je suis en plein dans ce besoin.
    Mais ça ne m’empêche pas, dans une perspective "cible" (target) de créer un générateur de code pour cette cible précise.

    Ici je voulais non pas souligner la création de méta-modèles mais plutôt l’outillage offert par une telle pratique. Concernant les méta-modèles, il y a d’autres billets sur ce blog même ;-)

    Voici ce que je voulais surtout souligner (cas particulier "à généraliser") :
    La création "à la main" de la classe "ProduitCategorie" et tout ce qui y est nécessaire (deux relations m:m dans Produit ET Catégorie, colonnes, relations dans ProduitCategorie, etc) est une niche à erreurs, pendant que le réaliser de manière "semi-automatique" (créer Produit, créer sa relation ManyToMany puis définir la classe que cette relation relate, pour enfin appeler l’assistant, c’est ce que j’appelle ici semi-automatique) permet d’amoindrir ce facteur de risque.

    Par la suite, ce modèle étant une "représentation" "abstraite" (abstract syntax, ou grammaire), il peut être "représenté" de différentes manières par transformation en code (concret syntax, ou syntaxe :-), et donc générer le fichier "schema.yml" utilisé par sfDoctrine pour générer les artefacts en ligne de commande, qui est une cible technologique particulière (imagine qu’on puisse écrire ce schéma en xml, ou encore un JSON, etc, il ne manquera plus qu’à écrire un nouveau transformateur modèle vers code particulier à l’occasion).

    Enfin, une telle pratique permet d’éviter toute erreur d’écriture, car on peut aussi, avant la transformation en code, demander une validation du modèle (tout en apportant des indices quant à la résolution de ces dernières; ce qui évite d’exécuter un processus php inutilement, entre autres va-et-viens entre différents logiciels, parfois), sans parler que les "coding guide-lines" sont directement implémentés dans les transformateurs : un réel gain à tous les étages de l’écriture de code, donc. Une réelle capitalisation de la connaissance (modèle; auto-documenté, etc) et du savoir-faire (écriture du code) : know-what et know-how :-)

    Pour finir, parce qu’il le faut hein, avec cet outillage (EMF, GMF, EuGeNia d’Epsilon) on peut par la suite créer un éditeur sfDoctrine graphique type boite-relation assez simplement (la plupart du bouzin est "généré" ^^ il ne manque plus qu’à configurer; comme celui qui me permet de réaliser les modèles avec les boites jaunes par exemple) en lieu et place du type "hiérarchique" (celui qu’on voit principalement, qui représente la structure xml "en brut") qui est l’éditeur standard, suffisant pour le développement du méta-modèle.

    J’ai un bon livre à te prêter là dessus, si ça t’intéresse (celui de Markus Volter -entre autres auteurs- créateur d’openArchitectureWare, le premier outil du genre que j’ai appris à utiliser il y a de ça 3 ans et qui explique plutôt bien les principes du MDSD entre autres MDA et SoftwareFactories -Microsoft side-).

  3. Avec plaisir pour le ou les bouquins.

    On voit autour d’une mousse un de ces soirs?

    • Avec plaisir ! Mercredi soir ou jeudi soir ?
      Tu travail en ce moment ?
      Parce que le mieux reste de voir ça devant un ordi ^^

      En bouquin j’peux t’en prêter deux ou trois, enfin tu verras ma p’tite biblio à ce sujet dans un autre billet qu’est sur ce blog ;-) On verra ça !

      à plus !


One Trackback/Pingback

  1. [...] Stéphane Erard mes passions pour la vie ? conception, développement, compréhension du phénomène Humain, entre autres :-) « Les Epsilons endoctrinés [...]

Laisser un commentaire

Choisissez une méthode de connexion pour poster votre commentaire:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

Suivre

Recevez les nouvelles publications par mail.

%d bloggers like this: