Quantcast
Channel: The GenMyModel blog
Viewing all articles
Browse latest Browse all 52

Build your own PHP generator from UML online using Acceleo – Part 1

$
0
0

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:

  1. script creation,
  2. small model creation (only few elements for testing purposes),
  3. script coding,
  4. 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:

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 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:

  1. click on Configuration,
  2. change your generator name from newgenerator to PHP,
  3. click on Update,
  4. 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')/]
  5. 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 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.phpandB.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

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:

  1. 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).
  2. 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,


Viewing all articles
Browse latest Browse all 52

Trending Articles