Quantcast
Channel: Christophe Herreman » PHP
Viewing all articles
Browse latest Browse all 4

Hacking the AMFPHP Deserializer with Propel

$
0
0

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!


Viewing all articles
Browse latest Browse all 4

Latest Images

Trending Articles





Latest Images