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
 

Do DDD Repositories and Flex make sense?

ActionScript, Air, Domain-Driven Design, Flex 3 Comments »

I have been thinking about some of the concepts and patterns described in the Domain-Driven Design book and how they could be implemented in a Flex or AIR environment.

One thing that interests me in particular is the use of a Repository as a container for Domain Objects. In short, a Repository is responsible for fetching, persisting and manipulating objects. Its intent is to shield the client from the implementation details of the data storage or the remote services that are used to get and manipulate objects.

However, there seems to be a fundamental problem in creating an ActionScript implementation. Since remote services are handled asynchronously, it is impossible to create a Repository as it is originally described by Eric Evans.

Here's a snippet of a post (by myself) on the DDD list:

Would it be correct to assume that repositories don't make much sense if you are dealing with async remote calls? Or should I say that async data fetching is a problem currently not addressed by DDD? Quoting Eric Evans in the DDD book (p. 157): "The client of a REPOSITORY should be given the illusion that the objects are in memory.". That being said, it is no problem to call a method on a repository and have it return the objects immediately if the objects are in memory. But, if the objects are sent back asynchronously, the whole point of encapsulating that behavior is gone since the client needs to "listen" for a response on the repository. So it knows about the implementation details of the repository.

As a reply, someone offered to try to make the call synchronous by waiting for a result. Although I think that might work, for instance by entering a loop until the result is received, I'm not completely sure as I haven't tried this. Here's my reply:

The problem I see is that the UI will be locked/frozen until the response is received because the code runs in a single thread within the Flash player. That means it won't be possible to create a (animated) loading screen to give the user some visual feedback. This would probably not be a problem with remote calls that return a small amount of data and a fast remote connection, but it certainly will be a (usability) problem if the connection is slow or if we are loading large chunks of data.

Conclusion

So my conclusion at this moment is that implementing Repository in Flex doesn't make much sense since the client still needs to know about some of the technical details of the implementation. Locking the UI would most certainly lead to usability problems (if it would even be possible). On the other hand, a Repository could be perfectly implemented as intended in an AIR application that uses an embedded SQLite database and synchronous calls, but the UI would also be locked.

Thoughts?


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

Flex Builder 3 on Vista: Trial Expired Solved

Flex 2 Comments »

There seem to be some problems with the Flex 3 licenses. Matt Chotin posted some info here and here.

However if you are installing the trial version, the info provided about removing the license.properties file on Windows is not correct if you're on Vista.

The correct path is C:\Users\[USERNAME]\AppData\Local\VirtualStore\ProgramData\
Adobe\Flex\license.properties

After you removed this file, restart Eclipse and you'll be prompted with the license window that states you can continue the 60 day trial.

Have fun!


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