Flextrine 0.7 released – now with ZendAMF!

After much soul searching I eventually decided to put in the time to convert Flextrine from using AMFPHP to ZendAMF.  There were a few reasons I decided to make this move, but eventually it boiled down to the fact that quite a few people had asked me to do so.  In retrospect it was certainly the right thing to do – ZendAMF has the green light from Adobe, its licence is LGPL instead of GPL and the cleaner code base means that the changes Flextrine requires can be implemented as overridden classes instead of having to modify the ZendAMF core.  This particularly is great news because it means that you can use Flextrine directly with the standard Zend framework installation, whereas previously you could only use the version of AMFPHP bundled with Flextrine.

Functionally speaking things remain exactly the same, and we continue to approach our stable release alongside Doctrine 2.

The new version of Flextrine can be downloaded from www.flextrine.com.  Note that if you are upgrading from a previous version of Flextrine you will need to regenerate your AS3 entities.

Flextrine 0.6.5 released

Another released of Flextrine!  Since 0.6.1 there have been a number of changes to the Flextrine core.  The entity merging algorithm has been altered to take better advantage of Doctrine 2 merging giving us a hefty performance boost (thanks for Benjamin Eberlei at Doctrine for pointing this out), the requirement to use $_explicitType in Doctrine entities has now been removed, bootstrapping and configuration code has been extended and neatened up and there have been numerous bug fixes at all levels of the application.

My continued thanks to the community for their help in testing and using Flextrine!

Flextrine Tutorial – CRUD in a simple Flex 4 address book: Deleting entities

Introduction
Setting up the server
Creating the entities
Creating the database schema
Loading the entities
Creating new entities
Deleting entities
Updating entities
Conclusion

Deleting entities is very simple.  Update the delete button MXML tag to call onDeleteClick() when clicked, and to only be enabled if there is a selection in the tree.

   1: <s:Button label="Delete" click="onDeleteClick()" enabled="{tree.selectedItem != null}" />

And implement the onDeleteClick() method:

   1: private function onDeleteClick():void {

   2:     em.remove(tree.selectedItem);

   3:     

   4:     if (tree.selectedItem is Contact)

   5:         tree.selectedItem.contactGroup.removeContact(tree.selectedItem);

   6:     

   7: }

Notice that we maintain the bi-directional association if the deleted item is a contact by removing it from its associated contactGroup.

And that’s it :)  The entity will be removed from its entity repository (updating the tree via databinding) and on the next flush() it will be removed from the database.

The final step in our little address book is to allow the user to update existing entities.

Getting AMFPHP to turn NaN into null

Annoyingly the NaN (not a number) value of Number objects in AS3 is often quite hard to translate into a server-side equivalent and ends up breaking AMFPHP, and apparently BlazeDS too.

Here is a simple modification that will make AMFPHP turn NaN into a PHP null value on de-serialization.  Unfortunately on the return journey – trying to return NaN to AS3 – there doesn’t seem to be anything clever that can be done and the Flash Player insists on turning undefined numbers into 0.  A glance through the AMF spec doesn’t reveal anything useful, and if you need this functionality you are best off creating a custom class wrapping the value of the number and including a isNotANumber:Boolean or something that you can examine and set on the server.

Anyway, here’s the code.  In AMFBaseDeserializer.php find the following function:

   1: function readDouble() {

   2:     $bytes = substr($this->raw_data, $this->current_byte, 8);

   3:     $this->current_byte += 8;

   4:     if ($this->isBigEndian) {

   5:          $bytes = strrev($bytes);

   6:     } 

   7:     $zz = unpack("dflt", $bytes); // unpack the bytes

   8:     

   9:     return $zz['flt']; // return the number from the associative array

  10: } 

and change the last line so it reads:

   1: function readDouble() {

   2:     $bytes = substr($this->raw_data, $this->current_byte, 8);

   3:     $this->current_byte += 8;

   4:     if ($this->isBigEndian) {

   5:          $bytes = strrev($bytes);

   6:     } 

   7:     $zz = unpack("dflt", $bytes); // unpack the bytes

   8:     

   9:     return ($zz['flt'] != 'NAN') ? $zz['flt'] : null; // return the number from the associative array

  10: } 

Hope that helps someone!

Announcement of Flextrine 2

Flextrine Bar

I am most proud to announce the development and upcoming release of Flextrine – an open source project I am developing that simplifies and encapsulates ORM with Flex and PHP.  Very simply, Flextrine maps AS3 objects to database tables giving you the ability to save and load objects to a remote database without having to worry about any server coding.  It supports all usual associations, local caching, indexing, Flex databinding and many other things, as well as having a nifty web interface for generating schemas and stubs.

Flextrine is planned for release on 1st September to match the release of its parent project Doctrine 2.

Check out http://code.google.com/p/flextrine2/ for more details.

AMFPHP, PHP 5.3 and namespaces

I spent a very frustrating day discovering that AMFPHP (trunk) doesn’t support class mapping to PHP classes that exist in the brand new PHP 5.3 namespaces.  So, if you happen to have the following class mapping:

   1: namespace vo;

   2:  

   3: class User {

   4:     $_explicitType = "vo.User";

   5: }

   1: package vo;

   2:  

   3: [RemoteClass(alias="vo.User")]

   4: class User {

   5:  

   6: }

… AMFPHP won’t pick up the fact that these are supposed to be the same class and will create an associative array when you send User as a parameter to a service.  It works fine in the opposite direction (i.e. returning User back to AS3 is ok).

I tried out ZendAMF and WebORB for PHP, but eventually decided that whilst they definitely have their merits, these frameworks are way too heavyweight for my application; ZendAMF requires the whole Zend Framework stack in order to work and WebORB is 22MB!  Instead I set about patching AMFPHP to support namespaces.  And here it is!

In amfphp/core/amf/io/AMFBaseSerializer.php change line 392 to:

   1: $classname = (strstr($typeIdentifier, "/")) ? str_replace("/", "", $typeIdentifier) : substr($mappedClass, $lastPlace);

Then if you want to class map to a PHP class in a namespace instead of using . notation, put forward slashes (/).  In fact PHP in its infinite wisdom uses backslashes () to delimit namespaces but as these denote escape characters terrible things happen when we start serializing strings and rather than have to escape the backslash all the time I just plumped for the forward slash.  Therefore our example, which in PHP namespace notation refers to the class voUser would change to:

   1: namespace vo;

   2:  

   3: class User {

   4:     $_explicitType = "/vo/User";

   5: }

… and our AS3 class would become:

   1: package vo;

   2:  

   3: [RemoteClass(alias="/vo/User")]

   4: class User {

   5:  

   6: }

Tada!  AS3 to PHP class mapping working as expected again!

Nike Team Kit Builder

As this was such a large project I couldn’t fit all the bits in my portfolio page, so here is a list of technologies and collaborations along with a selection of pretty screenshots.

The majority of work was performed off-site, and if you would like to see it in action you can go and create your very own team kit on the first floor of Niketown, Oxford Street, London.

UPDATE: This project has at last been modified into a web-friendly version and put online, so you can now see many of the original features and create your custom kit on Nike’s site at http://www.nike.com/nikeos/p/nikefootball/en_US/kitbuilder

Technologies

  • AS3, Flash CS3 and a lot of complex OOP
  • A custom MVC framework developed specifically for the project
  • XML and E4X
  • Papervision 3D (GreatWhite)
  • Blender
  • AMFPHP
  • MySQL, AdoDB & ActiveRecord
  • Smarty
  • Dynamic mail generation
  • Database administration and setup on OSX

Collaboration

  • Working closely with one other developer throughout the project lifecycle
  • Remote working using SVN and remote administration through VNC
  • Working with a large team of artists and designers, including 3D designers

Screenshots

Matchday/training selection screen
Matchday/training selection screen
Team formation selector
Team formation selector
Matchday player top editor
Matchday player top editor
Papervision 3D real time mapping
Papervision 3D real time mapping
Style selector
Style selector
Socks style and colour editor
Socks style and colour editor
Live preview screen
Live preview screen
Matchday/training + preview
Matchday/training + preview
Checkout and confirmation screen
Checkout and confirmation screen
Shipping details screen
Shipping details screen
   

Writing private attributes to a database using AMF in AS3 (IExternalizable)

Yesterday may have been one of the most frustrating days of my entire career. The brief – simple; take a bunch of classes acting as models (with a single top-level model containing all others in a tree hierarchy) and write their contents to a database. Client-side technology is Flash CS3 with Actionscript 3, server-side technologies are PHP 5 and MySQL. After having a look at the problem I decided that the quickest and most elegant way of achieving this would be to use Flash Remoting with AMFPHP on the server and to use custom class mapping to effectively pass the top level model and all its children to the server so they arrive with the same class structure as they left.

The code to make a Flash Remoting call is pretty standard stuff (note that this assumes the existence of onResult and onFault handlers):

   1: var myService:NetConnection = new NetConnection();
   2: myService.objectEncoding = ObjectEncoding.AMF0;
   3: myService.connect("http://localhost/amfphp/gateway.php");
   4:
   5: var responder = new Responder(onResult, onFault);
   6: myService.call("MyService.write_data", responder, myDataModel);

Once I’d implemented this along with a simple AMFPHP service containing the write_data function I was surprised to find that no data was getting passed to the server. After a lot of fiddling around I discovered the reason why:

By default AMF only encodes public attributes.

This is pretty annoying since no programmer worth their salt is going to be making all the attributes of their model public. That’s fine, thought I, I’ll implement some public getters:

   1: class MyDataModel {
   2:     private var myInt:uint;
   3:     private var myString:String;
   4:
   5:     public function get dbMyInt():uint {
   6:         return myInt;
   7:     }
   8:
   9:     public function get dbMyString():String {
  10:         return myString;
  11:     }
  12: }

Beautiful. Except for one thing.

By default AMF only encodes real attributes.

So this doesn’t work at all – the getters are completely ignored and still no data is passed to the server.

The ‘official’ solution

Finally I hit upon the real solution – use the IExternalizable interface to override the default behaviour of the AMF encoder and select what you want to encode/decode. Note that in order to use this you need to set the object encoding to AMF3 with myService.objectEncoding = ObjectEncoding.AMF3 (and you need to use AMFPHP 1.9+ as earlier versions don’t support AMF3).

   1: class MyDataModel implements IExternalizable {
   2:     private var myInt:uint;
   3:     private var myString:String;
   4:
   5:     public function writeExternal(output:IDataOutput) {
   6:         output.writeInt(myInt);
   7:         output.writeUTF(myString);
   8:     }
   9:
  10:     public function readExternal(input:IDataInput) {
  11:         myInt = input.readInt();
  12:         myString = input.readUTF();
  13:     }
  14: }

Perfect, except for one thing… as soon as you implement IExternalizable AMFPHP no longer works, giving the classic NetConnection.Call.BadVersion – and no amount of fiddling seems to fix it. In desperation I turned to WebORB PHP, but it has the same problem (see this forum post) and I didn’t have time to properly work out SabreAMF. From reading around various forums and blogs it appears that IExternalizable works fine with BlazeDS, but I have seen no reports of it working with anything else.

A working solution

By this point I’d pretty much given up doing it ‘properly’; the job needed to get done and this had taken far too long already. I created a class that acts as a superclass for all models:

   1: class SerializableModel {
   2:     public function toObject():Object {
   3:         return new Object();
   4:     }
   5: }

By making all classes override this function explicitly adding their private attributes we circumvent the problems with IExternalizable and end up with an associative array on the PHP side which we can iterate through. Unfortunately this doesn’t allow us to map the classes to equivalent PHP classes on the server, but this does at least get the job done.

   1: class MyDataModel extends SerializableModel {
   2:     private var myInt:uint;
   3:     private var myString:String;
   4:
   5:     public override function toObject():Object {
   6:         var object:Object = super.toObject();
   7:
   8:         object.myInt = myInt;
   9:         object.myString = myString;
  10:
  11:         return object;
  12:     }
  13:

  15: }

Finally in the remote method call we pass the object constructed by this function using:

   1: : myService.call("MyService.write_data", responder, myDataModel.toObject());

A better solution?

I’d love to hear one! Please post comments with any suggestions or ideas on how to do this better. Even better, if anyone knows how to patch AMFPHP 1.9 to accept AMF messages constructed with the IExternalizable interface I would love to see it.