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
 

MMUG Flash Remoting session files

AMFPHP, Flash, PHP 2 Comments »

A quick post to let you know that the Flash Remoting presentation files from last thursday at the MMUG Belgium are online. I covered Flash Remoting with AMFPHP and shared some project experiences, tips and tricks and showed a bunch of examples.

You can get the files here. The zip file includes the presentation slides, all the examples, the database dumps and the AMFPHP classes.

Many thanks to MMUG Belgium (Serge and Peter) and MultimediaCollege for the venue.


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

AMFPHP class mapping

AMFPHP, Flash, PHP 2 Comments »

Here are some good things to know when using class mapping with AMFPHP. As an example, imagine working on an application that handles users through a UserVO value object. Here's what the PHP and the AS2 class look like.

The UserVO class in PHP

This class is in a "vo" subfolder of the "services" folder (in your AMFPHP installation dir).

PHP:
  1. class UserVO{
  2.   var $firstname;
  3.   var $lastname;
  4.   var $age;
  5.  
  6.   function UserVO(){
  7.   }
  8. }

The UserVO class in AS2

This class is in the namespace "com.domain.vo.UserVO".

Actionscript:
  1. class com.domain.vo.UserVO{
  2.   public var firstname:String;
  3.   public var lastname:String;
  4.   public var age:Number;
  5.  
  6.   function UserVO(){
  7.   }
  8. }

Flash to PHP

Sending VO's from Flash to PHP is quite simple. Just add the parameters in the "arguments" key. Use the fully qualified classpath of the class on the server side and make sure you set "required" to true;

PHP:
  1. "saveUser" => array(
  2.   "description" => "Inserts or updates the user in de database.",
  3.   "access" => "remote",
  4.   "arguments" => array(
  5.     "aboutVO" => array("type" => "vo.UserVO", "required" => true)
  6.   )      
  7. )

PHP to Flash

Watch out here! You can leave out the "returns" key from the method entry in the methodtable. When you do that, AMFPHP will return the name of the class for you. The thing to know here is that in PHP4 the classname will be returned in lowercase, while in PHP5 it will be returned in its original format of lower- and uppercases. Note that this does not return the fully qualified classpath, but just the classname. "uservo" vs. "UserVO".

Here's a quote from the "get_class" function docs.

In PHP 4 get_class() returns a user defined class name in lowercase, but in PHP 5 it will return the class name in it's original notation.
http://be.php.net/manual/en/function.get-class.php

This is not recommended because Object.registerClass() will fail because it is case-sensitive. The best way to get Object.registerClass() to work is to pass in the name of the class in the "returns" key and then use that same name in lowercase to register the class in Flash.

PHP:
  1. "getUserVO" => array(
  2.   "description" => "Returns a user.",
  3.   "access" => "remote",
  4.   "returns" => "vo.UserVO",
  5.   "arguments" => array()
  6. )

And then in Flash...

Actionscript:
  1. Object.registerClass("vo.uservo", com.domain.vo.UserVO);

That's it, cheers!

[Update] I must have missed something here: the "returns" string is always passed to Flash in lowercase. So remember that when you use Object.registerClass().


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

Get your XML ready for Flash (2 of 2)

Flash, PHP 4 Comments »

In part 1 of this 2 part article, I showed how to put data in your XML file through the use of entity references. I also talked about the BOM, UTF-8 encoding and how to save your XML files in e.g. NotePad. What I want to do today is talk about how you can generate XML on the fly using both ASP and PHP.

 
What about server side generated xml data?

One trick that is often used is to pass in the URL of a server side script in the load() method of the XML object in Flash. That server side script can be a ASP, PHP, JSP or WEP (whatever page - ok, bad joke). What this kind of script actually does is create XML data and write it to the screen. Flash only wants XML data to be passed to an XML object and doesn't care if it's read from a static XML file or from data that is generated dynamically.

 
How do I create XML data on the server?

Probably the most used technique here is to compose a string with XML data and write it to the screen.

ASP:
  1. var xmlOutput = new String("<person>John Petrucci</person>");
  2. Response.Write(xmlOutput)

This is bad for 2 reasons:

1. You need to make sure that when you open a node, you also close it further in the string. When you're dealing with a lot of XML data things get very messy and you'll most likely forget to close one.

2. As covered in part 1, you need to replace special characters (remember them?) with entity references. What you can do to achieve this, is create a function or a prototype when you are using asp/jscript and call that function or prototype on every text you insert. This may not sound like a disadvantage because you have to replace them anyway, but you'll see that this can done much cleaner and with less work. In addition, you can easily forget to send the text through that function.

ASP:
  1. //String prototypes to encode the XML data.
  2. String.prototype.replaceCharacter = function(target, replacement){
  3.     var re = new RegExp(target, "gi");
  4.     var out = this.replace(re, replacement);
  5.    
  6.     return out;
  7. }
  8.    
  9. String.prototype.encode = function(){
  10.     var out = this;
  11.    
  12.     out = out.replaceCharacter("&", "&amp;");
  13.     out = out.replaceCharacter("<", "&lt;");
  14.     out = out.replaceCharacter(">", "&gt;");
  15.     out = out.replaceCharacter("'", "&apos;");
  16.     out = out.replaceCharacter(""", "&quot;");
  17.    
  18.     return out;
  19. }
  20. var xmlData = new String("<?xml version='1.0'?>");
  21.  
  22. //data pulled from a recordSet
  23. xmlData += "<data>" + myRecordSet.Fields("someField").Value.encode() + "</data>";
  24.  
  25. Response.Write(xmlData);

 
Using XML DOM

A better way of composing XML data would be to use the XML DOM object. When you're dealing with ASP, you can use Microsoft.XMLDOM. PHP has a DomDocument you can use. Here's an ASP code example.

ASP:
  1. var xmlDoc = Server.CreateObject("Microsoft.XMLDOM");
  2.  
  3. //create a processing instruction
  4. //utf-8 is the default and should not be specified
  5. var pi = xmlDoc.createProcessingInstruction("xml", "version=\"1.0\"");
  6. xmlDoc.appendChild(pi);
  7.  
  8. //create the rootnode and append it to the xml object
  9. var rootNode = xmlDoc.createElement("idiomatic");
  10. rootNode.setAttribute("version", "1.0");
  11. xmlDoc.appendChild(rootNode);
  12.  
  13. //create the courseNode
  14. var courseNode = xmlDoc.createElement("course");
  15. courseNode.setAttribute("label", objRdsTree.Fields("title").Value);
  16. rootNode.appendChild(courseNode);
  17.  
  18. //sets the rootNode (documentElement)
  19. xmlDoc.documentElement = rootNode;

Note that is it not needed to call the encode function on the data you pass in. This is handled automatically by the XML DOM object. You also can't forget to close nodes, since you're not using strings.

To get the XML data on screen (or load it into Flash), you can call Response.Write() and pass in the xmlDoc.xml property. This will give you a string representation of its data. Also, don't forget to set the codepage of your ASP page to UTF-8. This is the 65001 in the code below.

ASP:
  1. Response.Write(xlmDoc.xml);

ASP:
  1. <%@LANGUAGE="JAVASCRIPT" CODEPAGE="65001"%>

 
Generating XML files

What if you want to save your XML data in a file on the server? The best way I found is to write a file using ADODB.Stream. That way, you can save your file under UTF-8 encoding which is exactly what we need.

ASP:
  1. function saveXmlToFile(xmlDoc, fileName){
  2.     var adoStream = Server.CreateObject("ADODB.Stream");
  3.     adoStream.Open();
  4.     adoStream.Charset = "UTF-8";
  5.     adoStream.WriteText(xmlDoc.xml);
  6.     adoStream.SaveToFile(Server.MapPath("xml/" + fileName));
  7.     adoStream.Close();
  8. }

If you're using PHP, you can save UTF-8 files using the pack() function which lets you pack data into binary strings.

PHP:
  1. if($file=fopen("xml/myFile.xml","w")){
  2.     fwrite($file, pack("CCC",0xef,0xbb,0xbf));
  3.     fputs($file, $xmlContent);
  4. }

When you saved your file on the server, download and open it (notepad) and check its encoding. It should be UTF-8.

 
Wrapping up

That's about it. I hope this article helped you (in whatever way). Feel free to contact me in case you have something to add or came up with other solutions.

Thank you and goodnight!


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