Prana Framework 0.5 Released

ActionScript, Flex, Inversion of Control, Prana 5 Comments »

I'm pleased to announce that the Prana Framework 0.5 release is now available.

Prana

Download | API Documentation | Changelog

In this release we further focused on bringing the core Inversion of Control container API closer to that of the well known Spring API. In addition, the release also contains the following:

  • an XSD for editing object definition XML files
  • application context classes that extend the object factory
  • support for external property files to be used in an application context
  • support for multiple configuration files
  • updated support for PureMVC 2.0.3
  • updated samples
  • several new utility methods and enhancements to the existing ones
  • complete new build system and project layout
  • setup wizard to configure prana inside eclipse
  • minor bugfixes

Using the XSD

The XML Schema Definition (XSD) is a useful aid in creating object definition XML files. It has the advantage that your object definition XML files will be validated as you create them and a good XML editor will also give you code hinting on the available elements and attributes. In order to use it, make sure you are using an XML editor that has XSD support and place the following in your XML file:

XML:
  1. <objects xmlns="http://www.pranaframework.org/objects"
  2.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3.          xsi:schemaLocation="http://www.pranaframework.org/objects http://www.pranaframework.org/schema/objects/prana-objects-0.5.xsd ">

Note: There is a free XML editor available for Eclipse in the Eclipse Europa release. See the Web Tools Platform for more details.

The Application Context

As of this release, we have introduced the concept of the application context. This is actually an extension class of the object factory that offers additional functionality. As a result, we recommend updating your code to use the XMLApplicationContext instead of the XMLObjectDefinitionsLoader.

What follows is an example of how to use the XMLApplicationContext. All samples in this release have been updated to also use the XMLApplicationContext.

Actionscript:
  1. // load a single 'applicationContext.xml' config
  2. var applicationContext:XMLApplicationContext = new XMLApplicationContext("applicationContext.xml");
  3. applicationContext.addEventListener(Event.COMPLETE, onApplicationContextComplete);
  4. applicationContext.load();
  5.  
  6. // load multiple config files
  7. var applicationContext:XMLApplicationContext = new XMLApplicationContext(["applicationContext-1.xml", "applicationContext-2.xml"]);
  8. applicationContext.addEventListener(Event.COMPLETE, onApplicationContextComplete);
  9. applicationContext.load();

Note: There is no support for the <import> tag at this moment.

External Properties

Is some scenarios it might be useful to store certain properties of the config XML file externally so that you can easily change them without having to go through the entire config. The application context now allows you to specify external properties in separate Ant like *.properties files. You can then define property placeholders in your config files with the ${...} syntax.

Here's an example. Note that the path to the *.properties file is relative to the path of the config file.

XML:
  1. // in a file called strings.properties
  2. s1=First string
  3. s2=Second string
  4.  
  5. // in the config file
  6. <objects>
  7.  
  8.   <property file="strings.properties" />
  9.  
  10.   <object id="string1" class="String">
  11.     <constructor-arg value="${s1}"/>
  12.   </object>
  13.  
  14.   <object id="string2" class="String">
  15.     <constructor-arg value="${s2}"/>
  16.   </object>
  17.  
  18. </objects>

Building Prana and Setting up your Eclipse Environment

The way the Prana source tree is structured has changed drastically. The new project layout now allows us to create separate branches of the main source tree, the samples and the additional support projects. If you plan on building the sources yourself, please see the prana-install project and the documentation (prana-install/resources/docs/installation-instructions.txt) that contains instructions on how to set it up.

Here's the relevant part of the docs to give you a head start.

- Checkout "prana-install" project from https://prana.svn.sourceforge.net/svnroot/prana/prana-install/trunk
- Checkout other projects that you want to have in your Flex Builder workspace. You don't have to checkout all of them, but you must keep original names ("prana-main", "prana-main-tests" etc.) since a build system relies on them:
https://prana.svn.sourceforge.net/svnroot/prana/prana-main/trunk
https://prana.svn.sourceforge.net/svnroot/prana/prana-main-tests/trunk
https://prana.svn.sourceforge.net/svnroot/prana/prana-sample-cairngormStore/trunk
https://prana.svn.sourceforge.net/svnroot/prana/prana-sample-movieApp/trunk
https://prana.svn.sourceforge.net/svnroot/prana/prana-sample-puremvcArch101Demo/trunk
- Open main build file from "prana-install" project (prana-install/ant/build.xml) and run and "setup-projects" target.
- Answer to questions in GUI wizard.
- Wait until Flex Builder finishes building a workspace.
- That's it. Your workspace should now be fully configured.

To check if everything is set up correctly, you can try some of targets from each project's main build file located in "[project-name]/ant/build.xml".

For example:
- prana-main/ant/build.xml -> "release" target
- this target creates release archives in "prana-main/antbuild/release"
- prana-main-tests/ant/build.xml -> "tests-run" target
- this target runs tests and creates HTML report in "prana-main-tests/antbuild/reports/tests/html"
- prana-sample-[sample-name]/ant/build.xml -> "run-defaultApp" target
- this target runs the sample in a configured browser

If you don't want to checkout the sources from SVN, you can download the prana-projects-0.5.zip file that contains all projects. It is then possible to import those projects in your Eclipse environment. Please see the readme file that comes with the prana-projects-0.5 distribution for more info.

Conclusion

We hope this release will prove valuable to all Flex developers out there. Whether you're just using Prana's utilities, the Cairngorm or PureMVC extensions or the full blown IoC container, there definitely will be something helpful for you.

Don't hesitate to get in touch with the development team and other Prana users via our mailinglist. We appreciate all feedback, be it questions, comments, new ideas or code contributions.

Hope you enjoy this release and have fun coding.


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

Speaking at the Dutch Flex Usergroup

ActionScript, Conferences, Flex, Prana, Talks No Comments »

FlugrJust a quick note to let you know that I'll be giving a presentation on Prana at the Dutch Flex Usergroup called Flugr on the 13th of June. So if you're in the neighborhood (Utrecht) and would like to meet, feel free to visit the event.




The following sessions will be given:

- Benjamin Dobbler & Stephan Janssen: The creation of Parleys
- Nicolas Lierman: Advanced Flex
- myself: The Prana Framework

For more info, please visit the Flugr website at http://www.flugr.nl/


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

Hacking the AMFPHP Deserializer with Propel

AMFPHP, PHP No Comments »

I've been working on a project lately that uses Flex, AMFPHP, Propel and MySQL. For most of you these technologies may be well known, except maybe for Propel. In this post, I'll briefly mention what Propel is, what it does and how you can hack the AMFPHP Deserializer to make maximum use of Propel.

Propel

From the Propel project website: Propel is an Object Relational Mapping (ORM) framework for PHP5. It allows you to access your database using a set of objects, providing a simple API for storing and retrieving data.

Propel allows you, the web application developer, to work with databases in the same way you work with other classes and objects in PHP.

* You don't have to worry about database connections or writing SQL -- unless you want to.
* You never have to worry about escaping data or type-casting results.
* You define your database in a simple XML format (or tell Propel to build it from an existing database) and Propel will create database initialization files for your database and will generate static classes and objects that provide an OO interface to your database. (It can generate other useful things based on the datamodel too!)
* Propel builds classes which are aware of the structure of your database so there's no performance lost to initialization or to on-the-fly database metadata queries.

Most of you will agree that writing database queries and access code is one of the most tedious jobs in application development. Propel does an extremely good job at simplifying this work for the reasons mentioned above.

Mapping objects

The classes built by Propel use explicit getters and setters. That means they have methods like "setMyProperty" and "getMyProperty" to modify and access their fields. In addition to that, collections are an exception to that rule because they don't define a setter method. Instead, they define a method like "addMyItem" to fill the collection called "myItems".

The setter methods don't just set a property. They also contain logic that will mark a property as modified so that queries can be optimized.

The first step in setting up automatic mapping of the client and server objects, is to create Data Transfer Objects (DTO's, also known as Value Objects or VO's - although I prefer DTO after reading Eric Evans' DDD book.) that have the same property names as the protected properties in the PHP classes generated by Propel. To simplify the server objects, we changed the protected scope to public since it then allows us to send these to the client.

So what we have so far is a bunch of classes on the server and a bunch of classes on the client. Sending an object from server to client will allow mapping at this moment since we changed the protected scope of the properties to a public scope. Also note that you need to instruct AMFPHP about the type of the class by defining it in every object you want to map:

PHP:
  1. class MyClass extends BaseMyClass{
  2.   var $_explicitType="MyClass";
  3. }

Mapping objects from client to server

Mapping objects from client to server is actually not that easy. After all, when we send a property from the client, say "myProperty", the AMFDeserializer inside AMFPHP will try to set a property called "myProperty" on the mapped server object. This would actually work, but the queries would fail to execute correctly since they will not see any properties as being modified. That's why we need to call the setter method "setMyProperty" instead, which will mark the property as modified.

In order to invoke the setters (and "add*" methods of the collections) we changed the AMFDeserializer class so it tries to invoke setters dynamically. Let's jump right into the code.

PHP:
  1. // method "readAmf3Object"
  2.  
  3. for ($i = 0; $i <$memberCount; $i++)
  4. {
  5.   $val = $this->readAmf3Data();
  6.   $key = $members[$i];
  7.  
  8.   if ($isObject)
  9.   {
  10.     $isCollection = (substr($key,0,4) == "coll");
  11.    
  12.     if ($isCollection)
  13.     {
  14.       $addMethod = "add" . substr($key,4,-1);
  15.      
  16.       foreach ($val as $item)
  17.       {
  18.         $obj->$addMethod($item);
  19.       }
  20.     }
  21.     else
  22.     {
  23.       if ($key == "isNew")
  24.       {     
  25.         $setter = "setNew";
  26.       }
  27.       else
  28.       {
  29.         $keyparts = explode("_", $key);
  30.         $setter = "set";   
  31.        
  32.         for ($j=0; $j<count($keyparts); $j++)
  33.         {
  34.           $setter .= ucfirst($keyparts[$j]);
  35.         }
  36.       }
  37.                        
  38.       if (method_exists($obj, $setter))
  39.       {
  40.         $obj->$setter($val);
  41.       }
  42.       else
  43.       {
  44.         $obj->$key = $val;
  45.       }
  46.     }
  47.   }
  48.   else
  49.   {
  50.     $obj[$key] = $val;
  51.   }
  52. }

The above code tries to match the name of the setters by looking at the name of the properties inside the object being send. If it finds a setter method, it will invoke it and pass in the property as a value. The code will also try to detect a collection and invoke the "add*" method instead of a setter. There's also a "isNew" property to instruct the classes whether to do an update or an insert query when the save method is invoked on an object.

This hack brought me to the idea that AMFPHP should maybe provide a pluggable architecture for the serializers and deserializers. Instead of going into the code and modifying it to suit your needs, you could then register a custom (de)serializer in the config which will be used. Upgrading to newer versions of AMFPHP would then be less of a pain.

Anyway, hope you find this useful. Have fun coding!


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

It’s a boy!

General 7 Comments »

Mathis was born on March 21, 2008 at 12.45h. Mom and baby are doing very well. Thomas is excited with his baby brother and I'm a proud dad.

Mathis Herreman


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

Combining Factory and Strategy patterns

Design Patterns, Inversion of Control, Prana 4 Comments »

We often run into a scenario at work where we have to create an object from xml. More specifically we are parsing xml files with exercise (or assessment item) data to a concrete exercise type like a MultipleChoice, FillGaps, HotSpot, etc. The thing is that we are supporting several xml dialects for the same type of exercise and we end up with a lot of parsers. On top of that we also need to be able to decide on a per project basis what xml dialects are supported (a customer may only be allowed to use dialext X for exercise Y). In the next lines I will try to explain the way we manage and configure the objects that are involved in this process.

The basic idea is that we create a factory, let's call it ExerciseFactory, and define a createFromXML() method on it that takes an xml object as its input and "creates" a concrete exercise as its output. The factory is basically an xml parser. It works as follows:

Actionscript:
  1. var exerciseFactory:ExerciseFactory = new ExerciseFactory();
  2. var exercise:Exercise = exerciseFactory.createFromXML(xml);

Exercise is the base class for all exercise types like MultipleChoice, FillGaps, etc. The given xml object will be in a format that corresponds to such a concrete type. The factory however needs to know what the given xml is and what concrete exercise type it should return. The easiest way to handle these different xml dialects is with a switch on some property of the xml data. This results in code similar to this (in the ExerciseFactory class):

Actionscript:
  1. public function createFromXML(xml:XML):Exercise {
  2.   var result:Exercise;
  3.   switch (xml.someProperty) {
  4.     case multiplechoice-dialect-a:
  5.       // parse type MultipleChoice with dialect a
  6.       break;
  7.     case multiplechoice-dialect-b:
  8.       // parse type MultipleChoice with dialect b
  9.       break;
  10.     case multiplechoice-dialect-c:
  11.       // parse type MultipleChoice with dialect c
  12.       break;
  13.     case fillgaps-dialect-a:
  14.       // parse type FillGaps with dialect a
  15.       break;
  16.     case fillgaps-dialect-b:
  17.       // parse type FillGaps with dialect b
  18.       break;
  19.     default:
  20.       throw new Error("Cannot create exercise for the given xml object.");
  21.   }
  22.   return result;
  23. }

Although this works, it is certainly bad design since it is code that is hard to maintain and manage. If we need to add or remove some parsers, we have to modify this code which is buried deep inside some package hierarchy. We can't change this behavior at runtime since the parsers are hardcoded into the switch/case.

We could solve this with a mapping configured inside the ExerciseFactory. As its key, it contains the value of the property in the xml data we did the switch on. As its value it contains the class or an instance of the parser. This results in something like the following:

Actionscript:
  1. public function ExerciseFactory() {
  2.   this.map = {};
  3.   this.map[multiplechoice-dialect-a] = new MultipleChoiceDialectAParser();
  4.   this.map[multiplechoice-dialect-b] = new MultipleChoiceDialectBParser();
  5.   this.map[fillgaps-dialect-a] = new FillGapsDialectAParser();
  6. }
  7.  
  8. public function createFromXML(xml:XML):Exercise {
  9.   var parser:ExerciseParser = this.map[xml.someProperty];
  10.   return parser.parse(xml);
  11. }

Ok this seems better, but we can't configure the parsers externally since they are coded in the contructor. Let's externalize this by passing in the map to the constructor and additionally create a method for adding a parser.

Actionscript:
  1. public function ExerciseFactory(map:Object) {
  2.   this.map = map;
  3. }
  4.  
  5. public function addParser(key:String, parser:ExerciseParser):void {
  6.   this.map[key] = parser;
  7. }
  8.  
  9. public function createFromXML(xml:XML):Exercise {
  10.   var parser:ExerciseParser = this.map[xml.someProperty];
  11.   return parser.parse(xml);
  12. }

This is a neat implementation. We're setting a behavior or strategy for the parser in the factory at runtime, based on some configuration. But it has a drawback: the name of the property (xml.someProperty) in the xml data we use as a key in our map always needs to be the same for the different xml dialects. Unfortunately, we can't really control this because a new dialect might be used one day that does not contain the property.

To solve this, let's add another method to our parsers: canCreate(xml:XML):Boolean. As a parameter, it takes the xml data and then checks if it can parse the data by looking into the xml structure. Finally, it returns a boolean indicating whether or not the xml can be parsed. This way, the parser decides for itself what portions of the xml data to check instead of just the one property mentioned earlier.

To force this behavior upon the parsers, we can create an interface, IExerciseParser, and then let every parser implement it:

Actionscript:
  1. public interface IExerciseParser {
  2.   function canParse(xml:XML):Boolean;
  3.   function parse(xml:XML):Exercise;
  4. }
  5.  
  6. public class MultipleChoiceDialectAParser implements IExerciseParser {
  7.   public function canParse(xml:XML):Boolean {
  8.     // check the xml structure
  9.   }
  10.   function parse(xml:XML):Exercise {
  11.     // parse the xml to a MultipleChoice
  12.   }
  13. }
  14.  
  15. public class MultipleChoiceDialectBParser implements IExerciseParser {
  16.   public function canParse(xml:XML):Boolean {
  17.     // check the xml structure
  18.   }
  19.   function parse(xml:XML):Exercise {
  20.     // parse the xml to a MultipleChoice
  21.   }
  22. }
  23.  
  24. public class FillGapsDialectAParser implements IExerciseParser {
  25.   public function canParse(xml:XML):Boolean {
  26.     // check the xml structure
  27.   }
  28.   function parse(xml:XML):Exercise {
  29.     // parse the xml to a FillGaps
  30.   }
  31. }

We can now change the implementation of the ExerciseParser. We will remove the map because we will no longer be checking on a key to get a parser, but the parsers will check for themselves.

Actionscript:
  1. public function ExerciseFactory(parsers:Array) {
  2.   this.parsers = parsers;
  3. }
  4.  
  5. public function addParser(parser:IExerciseParser):void {
  6.   this.parsers.push(parser);
  7. }
  8.  
  9. public function createFromXML(xml:XML):Exercise {
  10.   var result:Exercise;
  11.   for (var i:int = 0; i<this.parsers.length; i++) {
  12.     var parser:IExerciseParser = this.parsers[i];
  13.     if (parser.canParse(xml)) {
  14.       result = parser.parse(xml);
  15.       break;
  16.     }
  17.   }
  18.   if (!result) {
  19.     throw new Error("Cannot create exercise for the given xml object.");
  20.   }
  21.   return result;
  22. }

... and configure our exercise factory before using it:

Actionscript:
  1. var exerciseFactory:ExerciseFactory = new ExerciseFactory();
  2. exerciseFactory.addParser(new MultipleChoiceDialectAParser());
  3. exerciseFactory.addParser(new MultipleChoiceDialectBParser());
  4. exerciseFactory.addParser(new FillGapsDialectAParser());

What we have achieved now is that we can add new parsers that support new xml dialects without the need to alter the code of the ExerciseFactory. All we need to is create a new parser, let it implement the IExerciseParser interface and it to the ExerciseFactory.

Additionaly, configuring the ExerciseFactory in a Prana application context is a piece of cake:

XML:
  1. <objects>
  2.   <object id="exerciseFactory" class="ExerciseFactory">
  3.     <constructor-arg>
  4.       <array>
  5.         <object class="MultipleChoiceDialectAParser"/>
  6.         <object class="MultipleChoiceDialectBParser"/>
  7.         <object class="FillGapsDialectAParser"/>
  8.       </array>
  9.     </constructor-arg>
  10.   </object>
  11. </objects>

Hope you enjoyed this. Don't hesitate to leave questions or comments. Have fun coding!


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 
WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Login