Missing patch to ZendAMF… Doctrine 2, ZendAmf, Flex and Acceleo - Part 2: generating php services from UML
Nov 04

I am more and more interested in the integration of PHP and Flex. Especially, using Doctrine2 as an ORM. So, I walked trough a great article written by Mihai Corlan, giving an example of how to work with Doctrine2, ZendAmf and Flex, using Flash Builder, of course. It was clear, easy to reproduce and the data-centric development feature of Flash Builder, the Data/Service tab, is really a great feature to introspect PHP classes and create the service wrappers as well as the AS3 value objects. It is a long time that I wanted to test this feature. It is very cool but I was actually really amazed until I opened the code generated by Flash Builder for services and value objects. It was too complicated and totally unmanageable for me. So, in a project, either I manage this part as a black box (which is probably the easiest way to do, trusting Adobe team in generating a light-weight and performant code), either, I find another code generator.

The second point that made me uncomfortable, with Doctrine2, this time, is that I missed a way to get the "big picture" of the entities: a diagram. In the Mihai’s example, the context is very simple, 4 entities… ok. But in a real, big project, managing (I mean, creating and maintaining) more than 40 entities and their relationships would be terrific without a diagram… a UML diagram.

So I decided to reproduce Mihai’s example in a different way. I want to:

  • start from a UML diagram, modelling the application - just including Doctrine2 entities and php services,
  • generate and maintain Doctrine2 entities that I don’t want to care,
  • generate the server-side services (at least a skeleton of methods),
  • replace the code generated by FB4 on the client side.

I don’t want to:

  • rewrite the client application (just reuse Mihai’s methods),
  • rewritte the php services (just reuse Mihai’s methods),

my main goal being to test the feasiblity of a full code generation in the Doctrine2 / ZendAmf / Flex configuration, based on a UML diagram.

The best and totally free way I know is Acceleo - an Eclipse plug-in that generates any text from a uml/xmi model, using a template - coupled with a UML diagramming tool - I chose Modelio. Modelio is actually not specialized in php. It is more java oriented but it is a full featured UML diagramming tool, even in its free edition, the best one I found after Visual-Paradigm for UML. And it has a uml/xmi export feature that is mandatory here. Of course, you can use your preferred UML tool to follow this serie. The most important is the export feature.

You will notice that these 2 tools are french… this is not a selection criteria, it is just like that. ;)

Preparing materials and the environnement

Installing the example

  • Get the project code from Mihai’s post,
  • Install it (import it in FB4 if you use it),
  • Import his database.sql file into your MySQL server,
  • Update links and the db configuration in ZendAmf config and the Doctrine 2 bootstrap files to make the application work in your environnement.
  • Once the application works correctly, rename the following items:
    • the table marks2 to marks,
    • in the students table, the fields last_name and first_name to respectively lastName and firstName.
      • this is required by the code generation way, that implies to have rules.

Creating the Acceleo project

In your eclipse client (I use Helios for PHP dev):

  1. Select File menu, New…
  2. Select Acceleo project
  • Define the parent folder,
  • Give a name to the 1st template: generateEntities for example,
  • Browse the metamodel URI and selected http://www.eclipse.org/uml2/3.0.0/UML.
    • If this metamodel is not present, install the UML2 plug-in for Eclipse. (I tried some other metamodels but they were not satisfying, maybe I missed something…).
  • Select the type: Class which should be the default selection,
  • Finish
  1. Switch to the Acceleo perspective

You should now have your first empty Acceleo template open. You can find more information in the Help documentation coming with Acceleo (from the Help menu of Eclipse).

Acceleo also comes with very good autocompletion capabilities and documentation (user guide and the language reference). You will find everything to start using this great tool. I was able to create a template very quickly, and just had difficulties in the tricky context of Doctrine2 mapping rules.

We will also need a folder to store the xmi model. So, in the source folder of the Acceleo project, create a ‘model’ folder.

And you can finally download the source of the template here: Doctrine2_UML_Code_Generation (391).

The UML diagram of the entities

First off, I want to remind you that modelling is just a way to communicate something and it can depend on what you want or need to describe, and your goals. My goal is to tie as much as possible to Mihai’s example and change the minimum of code. So here is the schema of the example: 

 

You can notice that I added an "Entity" stereotype on each class. By this way, we will be able to generate specific code for classes that are Entities. If you are not familiar with UML, you must know that the ends of the class associations (the arrows) are considered as attributes of the starting class: then ‘country‘ and ‘marks’ are attributes of Student (they are one of the end - the arrow - of the  association starting from Student). So there is no need to explicitly add them as attributes. Of course, you could define ‘country‘ as an explicit attribute of the Entity Student and use associations without ends. This would make the generation more complicated from my own perspective but this can be a design choice. You also must care about the direction you take when you draw an association: always in the direction of the navigability, this is important as I use the method last() and first() when I analyse the class associations in the Acceleo template. Any way, you must know that the application model driven development requires strong rules that drive the way the model is written (naming convention, custom properties or annotations…) and how the template will be defined.

A second point that must be highlighted is the specific decimal type used in the Mark class. This type was not available by default so I had to create a Primitive Type for decimal. I could use float or real (even if not that adapted for marks) but it would involve another modification of the MySQL database to change the type of the field mark in the marks table.

Now that we have our UML schema, we can convert it to xmi. Use the export / convert feature of your diagramming tool and save the xmi file to the model folder of the Acceleo project. When you open the xmi file from Eclipse, you should get something like this:

The Acceleo template for entities

(I am very new to Acceleo so the following lines may be not optimal - feel free to correct anything and send me some advices if you are an Acceleo expert ;) ).

The main principle in the template is to parse the model to get each class, then parse the class and get its attributes to test them and manipulate them, adding text parts according to their values or types… So to generate a very simple php class from our schema, generating only class properties and getters/setters, we could write in our template:

?php
namespace entities;
 
[comment For each class, I get its name and ensure the 1st letter is uppercase /]class [c.name.toLower().toUpperFirst()/]
{
   [comment For each class, I parse all attributes (Property), get their name and build a public property /]
   [for (p : Property | c.attribute)]
   private $[p.name/];
 
   public function set[p.name.toUpperFisrt()/]($val)
   {
      $this->[p.name/] = $val;
   }
   public function get[p.name.toUpperFisrt()/]()
   {
      return $this->[p.name/];
   }
   [/for]
}

This is so easy, just with this short piece of code, we get a simple php class. But at the moment there is no Doctrine annotation. I will show you some part here but I won’t give the full detail as this is actually quite complicated, please download the template (link available in the Conclusion) and check by yourself…

For the primary key, I just test the property name: if the name is ‘id’, I consider we have the primary key

[if (p.name.toLower() = 'id')]
   /**
    * @Id @Column(type="integer")
    * @GeneratedValue(strategy="AUTO")
    */
[else]...

For simple properties (not involved in associations), I get the name and type properties to build the @Column annotation:

[comment Define the type of the property /]
[let attrDataType:String = p.type.name.toLower()]
   /**
   [if (p.association = null)]
   [comment Some data type starts with a 'e' that must be eliminated /]
    * @Column(name="[p.name/]", type="[if (attrDataType.startsWith('e'))][attrDataType.last(attrDataType.size()-1)/][else][attrDataType/][/if]")
    */
[/let]
   private $[p.name/];

This will generate something like this:

class Student
{
  /**
   * @Column(name="lastName", type="string")
   */
   private $lastName;
...
}

You should now understand why I told you to rename the last_name and first_name fields in the database: the property is persisted in a field having exactly the same name. Once again, you must make choices and define your own naming conventions for class properties and/or database fields. It is possible of course to manage different class property names and db field names (lastName property persisted in last_name field) but you have to define that in your UML diagram and adapt the template to catch the information correctly. I kept this part as simple as possible.

For getters and setters, I used the visibility property of each attributes to decide if a setter should be generated:

[if (p.isReadOnly = false)]
   public function set[p.name.toUpperFirst()/]( $val )
   {
      $this->[p.name/] = $val;
   }
[/if]

For properties that comes from associations, please check my template. This case is the most complicated as OneToOne, OneToMany relationships and so on must be addressed. I had actually to check each association ends and get their upper value to verify if the mapping was of type OneToOne, OneToMany or ManyToMany. My template is a prototype that is enough for this serie of articles but it doesn’t cover all the Doctrine annotations. This is a good starting point and if you would want to go further (refactoring of the template, new annotations…).

To specify the Entity persistence (the name of the db table where the entity is persisted), I used a default annotation called ‘persistence‘ provided by Modelio. I hope this is a standard one and that you have the same in your diagramming tool:

So to define the @Entity annotation, I used the following code:

  /**
   * Description:
   * [c.ownedComment._body/]
   *
   * @Entity
   * @Table(name="[getValue(c.getAppliedStereotype('default::Classifier'), 'persistence')/]")
   */
class [c.name.toLower().toUpperFirst()/]
{
   ...

If you have any problem with this part, you can define the persistence table as follows for example:

 /**
  * @Entity
  * @Table(name="t_[c.name.toLower()/]")
  */
class [c.name.toLower().toUpperFirst()/]
{
...

You will then have to rename the tables accordingly in the MySQL db.

Acceleo allows you to define some parts in the template that are protected from overwritte. You can regenerate the whole application but the protected parts, containing your own code implementation, are preserved (used actually with extreme caution, if the regeneration fails, you will lose your code…). I used the [protected/] tag to insert the methods that Mihai added to the Student class (and that I didn’t mentionned in my UML model as I prefer not to define methods in Entites. Methods - excepted the getters and setters - should be better placed in the Service class).

Generating the code

The template is almost ready, we will generate our Doctrine2 entities in a few seconds.

For that, you must define where the files will be generated and their name. You can notice, in the template, the properties of the first Acceleo [file] command:

[file ('/' + c._package.name + '/' + c.name.concat('.php'), false, 'UTF-8')]

  1. the file name will be the class name: c.name
  2. the extension is php so I added it at the end of the name
  3. and to precise where the file will be stored, I used the package, naturally named ‘entities’, in the beginning of the name.
     

We now need to create a main generator. This file is the central point of the code generation, each template has to be imported and added into the main module in order to be executed:

  1. In the source folder, the same as the template folder, create a new Acceleo Main Module file.
  2. Give it a name
  3. In the second step, check the template we have defined ‘generateEntities‘: this will automatically add it to the body of the main file.
  4. Finish

The next and last step is done in the Run configuration of Eclipse:

  1. In the Run configurations dialog, browse to select the xmi file we have generated earlier,
  2. Browse to select the destination folder - where the files will be created: choose the doctrine2-student folder of the application (not the entities subfolder, as this has been already defined in the template itself).

Execute the main module and check the result in the result tab or in the entities folder.

Please note that 1 manual operation remains: add the cascade option in the @OneToMany annotation of the marks property of the Student entity. This option is not addressed yet in the protoype template.

Launch the Flex application to ensure it is still working.

Conclusion

Using a UML diagramming tool and an Acceleo template (even a prototype), we were able here to generate some Doctrine2 entities from our model. Now, we can modify existing entities, add more entities in the model and regenerate the code extremely quickly, in a standardized manner. Don’t forget that the database must also be updated using doctrine capabilities. I can imagine that the whole process can be embeded in an Ant script…

The generation of Doctrine2 annotations is complex as the different rules have to be addressed in the UML diagram and/or the template itself. The UML diagram also doesn’t address specifically the annotations: for example, the cascade option of the @OneToMany, has not been addressed here. The prototyped template should be refactored to be extended later to address more annotations in a smart and clean way. If you have some tips to share, don’t hesitate to drop a comment !

Lots of tools are able to generate code from user-defined templates. You maybe know or use one. Whatever the tool, this is the same way to work and you have to define your own rules and policies to model your application and to define your template. You will also probably use specific features of your tool and use them in the template.

In the next part, I will generate the php services but in the meantime, you can be also interested in 2 other tools :

  • ORM-Designer that is specifically built to generate entities (for Doctrine/Doctrine2, Propel…) from a UML diagram. I have tested it quickly: it is simple, with a nice user interface. You can get the code of your entities quickly. But I miss the generation of the php service skeletons, the AS3 services and value objects. This would also be an additional and too specific tool in my developement stack, that I want to keep as simple as possible and in which I need a full featured UML diagramming tool.
  • Mia Studio that could replace and even over-perform Acceleo as Mia Studio adds really interesting features that could ease the development of templates (result comparison, model transformations, indicators showing not used protected parts…). I didn’t tested it first as the Community version as some limitations in the volume of generated code. This limitation is ok for a short example like this topic but not for a real large project. But I definately have to give it a try, so do you (it is of course another french software editor.. :p ).

 Flattr this

Related posts

Written by Arnaud
Creative Commons License
Tags: , , , , ,

Share/Save/Bookmark

Help me improve my blog by rating this post or sending a comment.

not goodquite goodgoodvery goodexcellent (No Ratings Yet)
Loading ... Loading ...


Comments are closed.