A comprehensive journey into the deep wild code generator world. Part 1, translating class diagram structure: Class
, Interface
, class inheritance and interface realization to PHP code.
In this series of articles, we will teach you step-by-step how to build with your bare hands a UML to PHP generator. Each time, we will detail: Acceleo code, the identified UML model fragments and how everything is put together. With the keys you will get in these articles, you should be able to code your generator for any language in no time.
Developing code generators in GenMyModel – Tools
As you know, the GenMyModel editor enables you to create online models (UML, Database, Flowchart, BPMN), share them and edit them collaboratively. GenMyModel also gives you the ability to generate code from your class diagram to Java, Java-JPA, SQL… using existing code generators. You can also create your own Acceleo generator and code it directly in you browser by using the GenMyModel MTL Editor.
This editor provides syntax highlighting, smart autocompletion and type checking. During the code/test process, the MTL editor delegates Acceleo/MTL script compilation and execution to our servers whereas the smart autocompletion and the type checking are performed in your browser. Finally, one of the most useful feature of the MTL editor is the preview mode which gives you a quick feedback about the files generated by your script.
In order to write and test your generator, the process that we will apply is the following:
- script creation,
- small model creation (only few elements for testing purposes),
- script coding,
- preview.
Obviously, points 3.
and 4.
will be repeated all along the development process.
Note for impatient people: you can get the full example code of this first article and use it on a very simple class diagram.
Setup your environment
First thing first, we’re going to create a new code generator and a new UML model. I assume you already know how to create a GenMyModel UML project and you have opened it in the GenMyModel model editor. Now, click on the Generator
tab on the top left of your model editor, the generator management view will open:
![GenMyModel Code Generators View Code Generator Management]()
Click on the +
, a new tab named newgenerator
will open in your main dock with code in it. As you can see, everything is integrated into the editor:
![New Empty Online Generator New Online Empty Generator]()
Let’s try it! Click on Preview
and wait for the result. At this point your code is compiled and executed on our server, using your current project as input model.
If your model is empty (no Class/Interface…), a Result report
should pop up with a beautiful warning message saying:
No generated file:
Did you forget to create '[file' block or to call the template which contains it/them?
Here is a translation “Hey! I’m the server, your code compiled fine but when I ran your code, I was unable to produce files. Don’t you forget to ask for files creations?”. That’s obvious that no files were generated as our model is empty. At the moment, don’t look at the existing code, we will detail it later. Up to now, change your generator name:
- click on
Configuration
,
- change your generator name from
newgenerator
to PHP
,
- click on
Update
,
- go back to your script and in the first line, change
newgenerator
to PHP
, the first line should now looks like this: [module PHP('http://www.eclipse.org/uml2/4.0.0/UML')/]
- hit
CTRL + s
in order to save modifications performed on your script.
Before we jump into code, we have to create elements in our model to test the script. Go to your diagram and draw 2 classes and 2 interfaces so it looks like this:
![UML Input Model for PHP Code Generator UML diagram for PHP Code Generator]()
We have two classes, A
(which is abstract) and B
with an inheritance relation between B
and A
. Two interfaces C
and D
with an inheritance relation between C
and D
. Finally class A
implements C
interface.
CatsooLang Tip
If you are lazy like me, you can use the CatsooLang terminal to create your model: first open your terminal, type:
#import 'UML'
and wait for the CatsooLang UML module loading. Then, copy/paste/enter the script below (If you want to learn more about CatsooLang, try the documentation).
__ROOT _uml_cInterface('D', 100, 100);
__ROOT _uml_cInterface('C', 100, 200) _uml_inherits(D);
__ROOT _uml_cClass('A', 270, 180){isAbstract := true} _uml_implements(C);
__ROOT _uml_cClass('B', 270, 300) _uml_inherits(A);
We are now ready to begin our trip to Acceleo.
Understand the current Acceleo script
When you create a new generator, the file comes with a kind of hello word
script. It is minimal and allows you to get a first idea of what an Acceleo script looks like. Here are some explanations about what it does:
Line 1. this is the module name definition and the metamodel of the input model. In our case the input metamodel is UML: 'http://www.eclipse.org/uml2/4.0.0/UML'
.
Line 3. defines a template
called generate
which can be applied on Class
elements. The general syntax for template is [template _visibility_ _tname_(_args_)]...[/template]
where _visibility_
can be private
or public
. If you don’t remember template
syntax, you can access code snippets using CTRL + SPACE
.
Line 4. defines that this template is the main entry point. In GenMyModel, your main entry points (yes, entry point_S_) MUST be named generate
.
Line 5. defines a file
block which creates files named after the input class name concatenated with .txt
(c.name + '.txt'
) in an UTF-8 format.
Line 6. is the text which will be written in the generated file. All text written in between [file...][/file]
tags is written in the generated file. Each code written between [.../]
will be interpreted and the result of the expression will be written in the generated file. So, what does this line 6. means? Easy, “write ‘#Hello world from’ in the file then write the class name”.
That’s all for this code fragment. As you see, Acceleo and OCL are quite verbose language, but they are quite easy to read.
PHP generator strategy
There are many ways for organizing PHP classes: many classes/interfaces per file or one class/interface per file. It depends on what you want, you can apply one strategy or another, it is up to you. In this series of articles, we are going to assume that each class/interface will be generated in its own file.
Generate PHP Classes
Finally… we are going to write a template for PHP Class code generation \o/
So, we are going to modify the existing generate
template. Each file must be now named after the class name postfixed by .php
instead of .txt
. The line 5. is modified as this:
[file (c.name + '.php', false, 'UTF-8')]
If we launch a Preview
of our modified script, two tabs named ‘A.phpand
B.php` should appear. Not bad!
Now lets modify the template contents. Instead of # Hello...
we want our PHP class code. The template becomes:
[template public generate(c : Class)]
[comment @main/]
[file (c.name + '.php', false, 'UTF-8')]
<?php
class [c.name/]
{
}
?>
[/file]
[/template]
Hit the Preview
button once again. Behold your generated code:
![PHP Generated Code Preview PHP Generated Code Preview]()
Great, this is your first generated PHP code!
But the A
class is abstract in our input model… It should also be abstract in the PHP code. So, the question here is how can we gain access to this information? There is two main ways of doing this without leaving your browser and GenMyModel:
- You can type
[c.
and use smart auto-completion to look through the attribute you can access. Then, you can search for attribute that sounds good (names are pretty explicit so you should find them easily).
- You can use the CatsooLang terminal to question your model.
We will take the second option here. Open the CatsooLang terminal and type introspect(Class);
, this command will ask the Class
metaclass from the UML metamodel to display its attributes (result is quite verbose):
cl> introspect(Class);
-= Super types =-
[EncapsulatedClassifier, BehavioredClassifier]
-= Attributes =-
... <-- bunch of attributes here
inheritedMember : NamedElement [0..*] - READONLY
isAbstract : Boolean [1..1]
isFinalSpecialization : Boolean [1..1]
... <-- bunch of attributes here
From the displayed attribute, the isAbstract
one sounds interesting. Let’s test it in the terminal on A
and B
:
cl> log(A.isAbstract);
true
cl> log(B.isAbstract);
false
Yep, that’s it, isAbstract
is the attribute we are looking for. If this attribute is true, it means the class is abstract. Thus, the strategy is the following: if the isAbstract
attribute is true, then we will write abstract
in the file. You can easily write this “strategy” in Acceleo by using [if (...)] [/if]
tags. Our request becomes: [if (c.isAbstract)]abstract [/if]
and our template now looks like this:
[template public generate(c : Class)]
[comment @main/]
[file (c.name + '.php', false, 'UTF-8')]
<?php
[if (c.isAbstract)]abstract [/if]class [c.name/]
{
}
?>
[/file]
[/template]
Running a Preview
gives us:
<?php
abstract class A
{
}
?>
That’s what we wanted. Now, we are going to translate class inheritance. This point is a little bit more complex because we have to understand how inheritance is built in UML. Let’s take a look to the model tree in CatsooLang terminal:
cl> tree(__ROOT);
+- Model 'model' [-]
| +- (PackageImport, 10144) [packageImport]
| +- (PackageImport, 10145) [packageImport]
| +- Class 'A' [packagedElement]
| | +- InterfaceRealization 'null' [interfaceRealization]
| +- Class 'B' [packagedElement]
| | +- (Generalization, 10139) [generalization]
| +- Interface 'C' [packagedElement]
| | +- (Generalization, 10140) [generalization]
| +- Interface 'D' [packagedElement]
As we can see, the B
class contains a Generalization
. Moreover, the tree(...)
command shows us that you can access this Generalization
from B
using the generalization
attribute. If we try to look into here what we found:
cl> introspect(Generalization);
-= Super types =-
[DirectedRelationship]
-= Attributes =-
eAnnotations : EAnnotation [0..*]
ownedComment : Comment [0..*]
ownedElement : Element [0..*] - READONLY
owner : Element [0..1] - READONLY
relatedElement : Element [1..*] - READONLY
source : Element [1..*] - READONLY
target : Element [1..*] - READONLY
general : Classifier [1..1] <-- sounds good, but is Class instance a Classifier?
generalizationSet : GeneralizationSet [0..*]
isSubstitutable : Boolean [0..1]
specific : Classifier [1..1]
cl> log(B isKindOf(Classifier));
true
cl> log(B.generalization.general);
[CL_EOBJECT(Class, A, 11045)] <-- sweet
cl> type(B.generalization.general.name);
'Collection: [String: A]
In UML, Generalization
elements represent an inheritance relation between two objects. The object that contains the Generalization
is the object that inherits from another object.
We can now use this information. We know that we can access a collection of name by navigation. So if the collection is not empty, we will simply write extends
in the file, followed by the name contained in the collection. In Acceleo, here is the code: [if (c.generalization->notEmpty())] extends [c.generalization.general.name/][/if]
We add this Acceleo request to our template:
[template public generate(c : Class)]
[comment @main/]
[file (c.name + '.php', false, 'UTF-8')]
<?php
[if (c.isAbstract)]abstract [/if]class [c.name/][if (c.generalization->notEmpty())] extends [c.generalization.general.name/][/if]
{
}
?>
[/file]
[/template]
And we get:
<?php
class B extends A
{
}
?>
Victory! With a simialar idea, we now tame the interface realizations. Beware, the template line will be very, very long (I told you it is a verbose language). I cut to the chase and give you the CatsooLang request result:
cl> type(A.interfaceRealization.contract);
'Collection: [CL_EOBJECT(Interface, C, 13131)]
The line is almost the same as the one for inheritance BUT there is a twist: this time, we want to put a ,
between each interface the class implements. Using the CatsooLang information, the Acceleo line is:
[if (c.interfaceRealization->notEmpty())] implements [c.interfaceRealization.contract.name->sep(', ')/][/if]
and our generate
template becomes:
[template public generate(c : Class)]
[comment @main/]
[file (c.name + '.php', false, 'UTF-8')]
<?php
[if (c.isAbstract)]abstract [/if]class [c.name/][if (c.generalization->notEmpty())] extends [c.generalization.general.name/][/if][if (c.interfaceRealization->notEmpty())] implements [c.interfaceRealization.contract.name->sep(', ')/][/if]
{
[comment Told you the line will be very long/]
}
?>
[/file]
[/template]
and the result for A
class (Yeaaaay \o/):
<?php
abstract class A implements C
{
}
?>
Generate PHP Interfaces
The generation process for Interface
is almost the same as the one for Class
. The only differences are that an Interface
cannot implement another Interface
and abstract Interface
does not exist. Here is the template for Interface
:
[template public generate(i : Interface)]
[comment @main/]
[file (i.name + '.php', false, 'UTF-8')]
<?php
interface [i.name/][if (i.generalization->notEmpty())] extends [i.generalization.general.name/][/if]
{
}
?>
[/file]
[/template]
The only thing that should bother you is the [comment @main/]
. With the one in the generate
template for Class
, our PHP generator has now two main entry points. Is that possible in Acceleo? Short answer, yes. You can put as many entry points as you want, they all will be evaluated against your input model.
And that’s all for Class
and Interface
.
Is anything missing?
The careful readers and PHP enthusiasts should be boiling at this point: “What about includes? You extend classes, implement interfaces but no includes? Burn him!”.
So, how to deal with include
? Pretty much the same way we dealt with inheritance and interface implementations:
- In
Class
context, if the generalization
collection is not empty, we generate an include
and for each element of interfaceRealization
collection, we generate and include
.
- In
Interface
context, if the generalization
collection is not empty, we generate an include
.
Here is the code for inherited class inclusion in Class
context (in Interface
context, the code is the same, except variable i
is used instead of c
):
[if (c.generalization->notEmpty())]require_once("[c.generalization.general.name/].php");[/if]
And here is the code for interface realizations:
[for (s : String | c.interfaceRealization.contract.name)]
require_once("[s/].php");
[/for]
This time we used [for (...)][/for]
tags. The for
block enables us to iterate on each element of a collection and to do something with this element. In our case, we iterate on each name of the implemented interfaces and we write the require_once(...)
code into the generated file.
Finally, here is the generate
template for Class
with all the fragments:
[template public generate(c : Class)]
[comment @main/]
[file (c.name + '.php', false, 'UTF-8')]
<?php
[if (c.generalization->notEmpty())]require_once("[c.generalization.general.name/].php");[/if]
[for (s : String | c.interfaceRealization.contract.name)]
require_once("[s/].php");
[/for]
[if (c.isAbstract)]abstract [/if]class [c.name/][if (c.generalization->notEmpty())] extends [c.generalization.general.name/][/if][if (c.interfaceRealization->notEmpty())] implements [c.interfaceRealization.contract.name->sep(', ')/][/if]
{
}
?>
[/file]
[/template]
and here is the generated PHP code for B
class:
<?php
require_once("A.php");
class B extends A
{
}
?>
To sum up
The language used to write our code generator is Acceleo and the language we used to question the model is CatsooLang. We saw many things:
- how to search information in UML metamodel using CatsooLang,
- how inheritances and interfaces realizations are managed in UML,
if, for, file
blocks and template
definitions in Acceleo.
At the end of this article, your generator should generate code for classes, interfaces and it manages class/interface inheritance and class interface implementations.
What’s next?
As you probably imagine, there are numerous ways for accessing model elements and generating text in Acceleo. In further articles, we will show you advanced tips to improve your generator speed.
Well, this is a goodbye, but let us meet again for the part 2. This time, we will discuss about UML packages and enumerations. I’m sure you guess that we will have to discuss a lot about generation strategies.
If you have any question, feel free to comment this post!
Love,