Skip navigation

Hello everybody,

Please consider this post as a BETA post (lot of things are missing here) !

 

I’m currently developing some plugins in symfony to help manage infrastructures, platforms and softwares deployments.

It’s about these so-called "IaaS", "PaaS" and "SaaS" :) I’m sure you know those.

The goal of these plugins are to help developers create infrastructures (virtual machines), platforms (applications installed on infrastructure computers, according to their roles) easily, then deploy softwares on these platforms.

When using symfony, usually you start by creating the root project folder, its vhost for web-server, the fake dns in /etc/hosts, then generating project, and coding.

What these plugins are offering is a way to isolate project developments within infrastructures and virtual machines.

So you can have one project developed using a LAMP stack with php 5.2 while another project is developed with a LAMP stack but with php 5.3.

You’ll be able to "type" these differences and start new projects from them easily, by declaring what you want within yaml files.

Plugins will offer symfony tasks for the dutty at hand, and will come a Diem interface too (packaged as a web-app).

Here i’ll explain how I plan to offer IaaS, PaaS and, later, SaaS declarations, using my plugins.

Let first focus on Iaas part.

First, let’s talk about the IaaS thing, by introducing to you its actual meta-model (within my implementation, so to speak) :

So let say I want to create a "typical" infrastructure, I’ll start creating an infrastructure type.

I’ll use YAML to let users declare their infrastructures and infrastructure types :

(as WordPress is silly enough to not let us integrate plugins, here you’ll have a .. text which means [space][space] -_- )

Declaring Infrastructure Types :

—————————— infrastructure_types.yml start

project_php_dev:

..appsrv[0,*]:

….system:

……iso: /var/isos/ubuntu-server-x64-10.10.iso

….apache:

……install: sudo apt-get install apache2-mpm-prefork

……pre-install: /var/scripts/apache2/pre-install-dev.sh

……post-install: /var/scripts/apache2/post-install-dev.sh

….php:

……install: sudo apt-get install php5 php5-cli php5-dev php-pear

……pre-install: /var/scripts/php5/pre-install-dev.sh

……post-install: /var/scripts/php5/post-install-dev.sh

….mysql:

……install: sudo apt-get install mysql-server-5.1

……pre-install: /var/scripts/mysql5-1/pre-install-dev.sh

……post-install: /var/scripts/mysql5-1/post-install-dev.sh

—————————— infrastructure_types.yml end

Here I have declared an infrastructure type named "project_php_dev".

This infrastructure type needs at least one computer of type "appsrv", and can have as many as needed.

These computers typed as "appsrv" will have three applications, typed as "apache", "php" and "mysql". Those are application types, not application instances, as we are declaring a type.

We simply say that "this type of computer will have those types of applications".

There is also a system entry in this computer type, so we can configure the used iso. I’ll add more options so we can "clone" things, and run scripts on post-clone (to change hostname, and such).

Also I’ll add a nesting of infrastructures, so I’ll be able to declare a "php_project" infra which will have [0, *] infras, like "dev", "prod", "test", and so on.

So let see now an infrastructure.yml file :

—————————— myInfrastructure.yml start

myInfra[project_php_dev]:

..computer00[appsrv]:

..computer01[appsrv]:

….apache:

……post-install: /var/infras/%%INFRA%%/%%COMPUTER%%/%%APP%%/post-install.sh

—————————— myInfrastructure.yml end

#Here I have overloaded the post-install script for the myInfra/computer01/apache application.

I hope you’ve got the spirit of the plugins I want to develop. When finished, I’ll give applications to generate yml files, using EMF things (from Eclipse, Acceleo, Epsilon).

I have written this to gather your thoughts on this. What are yours in seeing this ? Please give us some comments ! :)

To be clear, I need to develop these plugins to ease my project-launching, and to offer it to web-agencies for example, or within mid-sized enterprises (open-sourced).

Do you have any input to give ? Com’on !
Thank you for reading anyway !

Stéphane

Salut à toutes et à tous,

Ce petit billet pour vous annoncer que je travail actuellement sur le projet epsilonTools qui permet de générer des assistants et autres éléments opérationnels du monde Epsilon pour un méta-modèle Ecore. Cet outil me permet d’ailleurs de réaliser (en concurrence) le projet "basic-eclipse-modeling-tools" qui est en fait un ensemble d’outils Epsilon (wizards, opérations, vérifications, etc) pour EMF (merci la réflexivité).

En fait ce projet est important car il va me permettre de "vite" développer des "applications" "proto" à partir de méta-modèles, le tout en s’intégrant dans l’application Eclipse. Le fait est qu’avec ces outils je peux facilement écrire du code qui se concentre sur le problème du domaine. Bien sûr Eclipse n’est pas le top pour faire du "managment" genre "crud" mais ça permet de tester facilement les modèles d’un point de vue objet avant implémentation en php par exemple.

A l’heure actuel, le projet permet de générer à partir du méta-modèle Ecore.ecore beaucoup d’éléments.
En fait pour chaque EClass d’un méta-modèle, il y a un fichier "wizards" contenant les assistants et un fichier "opération" contenant les opérations.

HODM :

http://www.dailymotion.com/playlist/xkom0_guil50cents_l-h-o-d-m-helmutcall/1

The Signs :

http://www.dailymotion.com/playlist/xzbdr_ghettozaine_the-signs/1

Zeitgeist :
http://www.thezeitgeistmovement.com (deux docus de 2x2h)

Qui dirige vraiment la France :

Qu’en pensez-vous ?

Salut à toutes et à tous,

Ce petit billet pour expliquer comment faire un plugin Epsilon-EWL depuis Eclipse (galileo ici, mais doit être pareil pour ganymede).

Donc, on créer un projet plugin, on le nomme, on créé un fichier "test.ewl" dans lequel on y écrit le code définissant le wizard.

Ensuite, on va ouvrir le fichier MANIFEST.MF dans le répertoire META-INF du répertoire représentant le plugin que l’on créé.

Dedans, on y voir plusieurs onglets (en bas), on clique sur "Dépendances" puis on "Add" trois dépendances:

org.eclipse.epsilon.dependancies
org.eclipse.epsilon.ewl.emf
org.eclipse.epsilon.ewl.gmf

Ensuite on clique sur l’onglet "Extensions" puis on "Add" org.eclipse.epsilon.ewl.emf.wizards, là il va apparaitre une hiérarchie avec dedans "(wizard)". Cliquez-dessus pour voir ses "Details" apparaitrent à droite. Dans namespaceURI, mettez-y le NSURI du méta-modèle utilisé par votre wizard, puis dans file séléctionnez le fichier .ewl que vous venez de créer.

Ensuite il faut "exporter" le projet afin qu’il soit "déployable".

Là on fait un simple clique-droit sur le projet (le répertoire représentant le plugin donc) puis "export">Plugins Development>Deployable plugin and fragment et "Next", là il faut modifier l’emplacement destination et allez dans "Option" puis décochez tout sauf "Allow for binary…".

Ceci va vous créer un répertoire (j’ai essayé en JAR mais ça ne marchait pas), répertoire qu’il faudra alors copier dans le répertoire plugins de votre eclipse.

Et voilà, vous avez installé votre plugin (vous pouvez vérifier en allant dans le menu Help>About Eclipse>Cliquer sur "Installation Details".

Salut à toutes et à tous,

Ce petit billet pour vour présenter non plus le méta-modèle de Symfony-1.2, mais ses assistants virtuels :-D

Dans Symfony, lorsqu’on veut créer un Projet, on créé un nouveau répertoire puis on tape la commande en ligne "symfony generate:project myProjectName". Avec Symfony Model Editor, on créé un nouveau modèle, on clique-droit dessus, puis Wizards et enfin "Create a new project with its basic structure", ce qui va créer un nouveau élément "Project" avec sa structure par défaut. Voyez par vous même :

Assistant à la création d'un nouveau Projet

Assistant à la création d'un nouveau Projet

Cet assistant ne vous demande qu’une chose : le nom du projet que vous désirez créer :

Assistant à la création d'un nouveau Projet : demande du nom

Assistant à la création d'un nouveau Projet : demande du nom

Et voici :

Assistant création de projet, résultat

Assistant création de projet, résultat

J’ai créé un en deux-trois cliques ce qui nécessiterai 3*6 cliques environ ! =D Sans parler du reste… :-D

Maintenant, j’ai réalisé un autre assistant permettant de peupler l’élément Configurations avec toutes les configurations par défaut, lorsqu’on créé un nouveau projet dans Symfony :

Assistant création configuration de base d'un projet

Assistant création configuration de base d'un projet

Je ne l’ai pas mis ici faute de temps, mais pour chaque élément (dsn, username, password, etc), l’assistant demande à l’utilisateur une saisie clavier lui permettant de renseigner à la création les valeurs qu’il désire !

Petit exemple, voici le code permettant de demander à l’utilisateur de saisir le ‘dsn’ :

var doctrineParamDSN : Model!symfony::Abstract::Configuration::Property;
doctrineParamDSN = new Model!symfony::Abstract::Configuration::Property;
var dsn : String; dsn = ‘\" + UserInput.prompt(‘Please enter the DSN for all:doctrine:param:dsn’, ‘mysql:host=srv001;dbname=eds_frontend’) + ‘\";
doctrineParamDSN.setProperties(‘dsn’, dsn, false);
doctrineParam.properties.add( doctrineParamDSN );

Et voilà l’écran de saisie :

Assistant création Configurations de base d'un projet, demande de saisie du DSN

Assistant création Configurations de base d'un projet, demande de saisie du DSN

Pour finir ce petit billet, je dirai qu’Epsilon est un vrai miracle pour qui veut créer des applications d’aide au développement d’applications.

Une fois que j’aurai fini les assistants de base, je m’attaquerai au générateur à proprement parler. D’ici une semaine environ :-)

à bientôt !

Salut à toutes et à tous,

Ce petit billet pour vous faire état de l’avancé du méta-modèle de Symfony-1.2.

Il est quasiment fini, il inclut pour l’instant le méta-modèle de sfDoctrine mais pas celui de sfPropel.

Il inclut tous les éléments que peut nécessité Symfony pour fonctionner.

Voici une petite capture d’écran pour les plus curieux :

Ce modèle explicite ce qu'il est possible de réaliser à l'aide du méta-modèle Symfony-1.2

Ce modèle explicite ce qu'il est possible de réaliser à l'aide du méta-modèle Symfony-1.2

Un petit "zoom" sur les configurations, pour exemple :

Modèle de Symfony d'exemple  et ses Configurations

Il me faut maintenant créer des assistants pour remplacer les commandes en ligne tels que generate:project, generate:app, etc.
Ceci se fera par l’adjonction de modèles par défaut qui auront des valeurs modifiés selon les saisies utilisateurs (grâce aux assistants Epsilon), ce qui facilitera la modification des générations "par défaut", chose déjà possible en allant trifouiller dans le répertoire d’installation du framework en modifiant les fichiers.
Alors bien sûr, il ne s’agit pas pour l’instant de pouvoir modifier TOUT et n’importe quoi, seulement les choses nécessaires. D’autant plus que ce n’est qu’un début, et que tout sera fourni en "open-source", libre et gratuit : il vous appartiendra de modifier le comportement de l’applicatif que je vous livrerai selon vos propres besoins !

Pour référence, voici la structure d’un projet Symfony tel quel, fonctionnel (il n’y a pas tout d’afficher) :

Structure du répertoire d'un projet Symfony qui fonctionne

à bientôt !

Salut à toutes et à tous,

Ce petit billet pour vous introduire à la transformation de modèle à modèle.

D’abord concernant le titre, une génération de code depuis un modèle est une autre sorte de transformation.

Il y a deux sortes de transformations à la base des modèles : transformation de modèle à modèle et transformation de modèle à texte.

Concernant le texte, il y a aussi des transformations possibles (à l’aide de l’outil ANTLR, notamment) : transformation de code à modèle et transformation de code à code.

Il est très utile de savoir manier les deux pour créer un outil intégrant la notion de "round-trip model-code", permettant à l’utilisateur de réaliser des modifications en n’importe quel point de ses artefacts et de les répercuter là ou nécessaire : on modifie le code brut, on veut modifier le modèle en conséquence; on modifie le modèle, on veut modifier le code en conséquence. Tout ça au sein d’un même outil ! Oui c’est ça le futur du développement logiciel LIBRE et GRATUIT ! C’est déjà en route, et depuis de nombreuses années, notamment gràce à Eclipse et ANTLR (qui est du reste LE meilleur outil de création de logiciels de syntax parsing à l’aide de grammaire).

Revenons-en à nos mêêêêêêh :

Nous allons ici nous asseoir sur le modéleur graphique de modèle eCore fourni avec les Ecore Tools (plug-in Eclipse) pour pouvoir modéliser des modèles sfDoctrine. En effet, et de manière simple, nous pouvons réaliser un "mapping" ou une "corrélation" entre ces deux univers.

Une EClass peut être vu comme une Class. Un Attribut peut être vu comme une Column et une Relation peut être vu comme une Relation. Bizarre ? Non, il en est ainsi dans TOUS les domaines : c’est l’analogie. Faire des analogies, c’est ça ! C’est "relativiser" aussi, car une chose distincte en un endroit peut être abstraite en un autre endroit !

Pour que ça marche, il faut réaliser un "launcher", comme dit dans le dernier billet, je vous laisse découvrir par vous même comment réaliser des launchers pour exécuter tout ça, ça vous mettra dans le bain ! Pour les autres, qui ne voudront prendre le temps, ça effectuera un trie séléctif quant à votre intuition sur ces phénomènes. Si vous sentez que c’est une bonne chose qui va dans le bon sens, n’attendez pas : FONCEZ !

Voici donc un script simple et assez idiot qui permet de transformer un Ecore!EPackage (comprendre un EPackage du méta-modèle ECore) en un sfDoctrine!Model (je vous laisse ;-) :

rule ePackage2Model
transform i : ecore!EPackage
to o : sfDoctrine!Model {

o.name = i.name;

if(i.eClassifiers.isDefined() and i.eClassifiers.size()>0){
for( eclass in i.eClassifiers.select(e|e.isTypeOf(EClass)) ){
var class = sfDoctrine!Class.createInstance();
class.name = eclass.name;
o.defines.add(class);
}
}

}

Comme dit plus haut, c’est simple et idiot, car il n’y a pas de gardes-fous, il n’y a pas d’abstraction, il n’y a pas de création des colonnes ni des relations, bref, c’est simple voir tordu ! Mais c’est pour vous donner le squelette d’application du bouzin, pas pour vous mâcher le travail !

Le mâchage se fait par l’implémentation que je réalise d’outils pour Symfony, sfDoctrine, etc. Non que s’en soit assez, c’est juste énorme comme boulot pour un seul homme ! Bien sûr, il y a bien plus gros ! :-)

Je ne suis pas là pour vous faire un cours en profondeur, seul vous le pouvez ! Il y a pour ça de nombreux outils à votre disposition : livre (EpsilonBook disponible sur http://eclipse.org/gmt/epsilon), les newsgroups, etc. Cependant, si vous êtes dans cette perspective, je peux vous aider, comme Dimitris du reste (sur les newsgroups accessibles depuis la page d’accueil d’Epsilon !).

Le plus important reste de bien comprendre les choses mises en jeu, le reste n’est que formalisation (comment écrire une règle de transformation, comment en faire une abstraction et ce qui en découle en terme d’implémentation, etc).

J’arrête donc là les petits tutoriaux de mise en perspective de ces outils pour revenir vers ce qui m’est le plus important dans ce domaine qu’est mon métier : réaliser tout l’outillage nécessaire pour pouvoir réaliser des applications web à l’aide du cadre applicatif Symfony et de son plug-in sfDoctrine au travers de modèles par leur transformation en code.

Pour ceux qui connaissent Symfony, cela implique de réaliser tous les générateurs (symfony generate:project, generate:app, generate:module, etc), car je ne peux me contenter de générer un simple script du genre "php symfony generate:app $arg1 $arg2 $arg3" car je n’aurai alors aucune visibilité en terme de modulation de toute la profondeur que permet Symfony (en terme de configurabilité, etc) réalisé sur un modèle, donc pas de vérificabilité, pas de tracabilité, rien. Réduisant le projet à un mini-tool particulier alors qu’il se veut être plus englobant.

Plus englobant, oui, parce qu’au travers de la technologie EMF, tous les méta-modèles peuvent se lier les uns aux autres, même si ce n’est pas encore réalisé.

Par exemple, de manière abstraite, Apache défini un modèle, un langage, qui est réalisé de différentes manières : httpd.conf, etc. Cela peut aussi changer selon les systèmes d’exploitation, qu’il y ait des nécessités de configuration particulières.

Bref, il faut donc pouvoir lier les problèmes de configuration particuliers avec les instances de systèmes d’exploitation qui les supportent, pour pouvoir en valider la cohérence et ainsi être à même de générer du code dont on est sûr qu’il va fonctionner (oui, car on "debug" aussi des modèles et des génération et des transformations, bref, dans un domaine comme dans l’autre tout se répète ! Patterns ! ).

Amusez-vous bien !

… a désactivé mon compte. J’écris ce billet pour mes ami(e)s qui connaissent l’adresse de ce blog et qui pourront, je l’espère, rendre compte à mes autres ami(e)s de cela. J’ai fais une demande de réactivation comme ils le proposent. On verra bien.

à plus !

Salut à toutes et à tous,

Ce petit billet pour vous présenter la transformation d’un modèle en code, ou plus simplement, la génération de code au travers d’un modèle.

Dans l’état de l’art du développement logiciel, on fait abstraction du code en créant des "templates", ou en bon vieux français, des "patrons" ou encore des "modèles" :-)
Cela nécessite donc qu’un template se réfère, dans sa terminologie, à un modèle pour injecter les données.

Je vais ici utiliser le méta-modèle défini dans mon précédent billet ainsi que le modèle et l’outillage réalisé à titre d’exemple.

L’objectif est d’utiliser ce modèle

Simple modèle d'exemple

Pour générer un fichier YAML correspondant à ceci (dans sa forme et non ses données):

Produit:
..columns:
….name: { type: string(255), notnull: true, unique: true }
..relations:
….Categories: { class: Categorie, foreignAlias: Produits, refClass: ProduitCategorie, local: produit_id, foreign: categorie_id }

Categorie:
..columns:
….name: { type: string(255), notnull: true, unique: true }
..relations:
…. Produit: { class:Produit, foreignAlias: Categories, refClass: ProduitCategorie, local: categorie_id, foreign: produit_id }

ProduitCategorie:
..columns:
….produit_id: { type: integer(20), primary: true }
….categorie_id: { type: integer(20), primary: true }
..relations:
….Produit: { local: produit_id, foreign: id, foreignAlias: Produits }
….Categorie: { local: categorie_id, foreign: id, foreignAlias: Categories }

. représente ici un espace, tant les espaces sont importants dans les fichiers yaml.

Voici le code du programme de génération :

[%
var m : Model := Model.allInstances().at(0);

for( class in m.defines )
{
%]
[%=class.name%]:
[%
if( class.column.size() > 0 ) { %]
..columns:
[% for( column in class.column ) { %]
….[%=column.name%]: { [%=column.generateProperties()%] }
[% }
}
if( class.relation.size() > 0 ) { %]
..relations:
[% for( relation in class.relation ) { %]
….[%=relation.name%]: { [%=relation.generateProperties()%] }
[% }
}
%]

[%
}

operation OneToOne generateProperties() : String {
return 'local: ' + self.relates.name.toLowerCase()+'_id' + ', foreign: id, foreignAlias: ' + self.relates.generateSingularName();
}

operation OneToMany generateProperties() : String {
return 'otm'; --je vous laisse vous amuser
}

operation ManyToMany generateProperties() : String {
return 'class : ' + self.relates.name + ', foreignAlias: ' + self.owner.generateSingularName() + ', refClass: ' + self.refClass.name + ', local: ' + self.owner.name.toLowerCase+'_id' + ', foreign: ' + self.relates.name.toLowerCase+'_id';
}

operation Column generateProperties() : String {
return 'type: '+ self.type.generateTypeName()+'('+self.type.max+')' + ', primary: ' + self.primary + ', notnull: ' + self.generateIsNull() + ', unique: ' + self.generateIsUnique();
}

operation Column generateIsUnique() : String {
return self.unique;
}

operation Column generateIsNull() : String {
return self.notnull;
}

operation Type generateTypeName() : String {
if(self.isTypeOf(sfDoctrine!Integer)){ return 'integer'; }
}

operation Class generateSingularName () : String {
return self.name;
}
%]

Et voici le résultat généré dans un fichier gen_schema.yml (il faut configurer un lanceur pour exécuter la génération, le site d’Epsilon explique ça très bien, voir les newsgroup aussi) :

Produit:
..columns:
….id: { type: integer(20), primary: true, notnull: true, unique: true }
..relations:
….Categories: { class : Categorie, foreignAlias: Produit, refClass: ProduitCategorie, local: produit_id, foreign: categorie_id }

Categorie:
..columns:
….id: { type: integer(20), primary: true, notnull: true, unique: true }
..relations:
….Produits: { class : Produit, foreignAlias: Categorie, refClass: ProduitCategorie, local: categorie_id, foreign: produit_id }

ProduitCategorie:
..columns:
..categorie_id: { type: integer(20), primary: true, notnull: true, unique: false }
….produit_id: { type: integer(20), primary: true, notnull: true, unique: false }
..relations:
….Categorie: { local: categorie_id, foreign: id, foreignAlias: Categorie }
….Produit: { local: produit_id, foreign: id, foreignAlias: Produit }

N.B.: il y a quelques différences du fait de modifications entre temps (pluriel/singulier, etc), mais on fait ce qu’on veut en tout point, ne vous concentrez donc pas sur les incohérences entre mes deux billets, ce n’est qu’un exemple réalisé en deux temps ;-)

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.

Suivre

Recevez les nouvelles publications par mail.