Jono B Thu Sep 09 05:26:04 -0400 2010

Subject: Php.ActiveRecord and AMF

I use AMF (http://en.wikipedia.org/wiki/Action_Message_Format) to communicate between a flash front end and php backend. This is done through AMFPHP (http://www.amfphp.org/).

I have been testing PHP.ActiveRecord as a method to help generate my crud, and wanted to share my experience.

The first thing to note is that AMF typically makes use of PHP objects (i.e. value objects) to transmit data, which means that it can be read and parsed very efficiently by the flash client. So, usually, you have something like:

1 /* file UserVO.php*/
2 class UserVO
3 {
4     public $id;
5     public $email;
6 }  

And returning data to flash would involve something like:
1 /* file user_service.php*/
2 $sql = 'SELECT * FROM user';
3 $data = mysql_query($sql);
4 while ($row = mysql_fetch_object($data, "UserVO"))
5 {
6     $result[] = $row;
7 }
8 return $result;  

How to achieve the same thing in ActiveRecord? I went down the following route:

 1 /* file UserVO.php*/
 2 class UserVO
 3 {
 4     public $id;
 5     public $email;
 6 
 7     public function __construct($data)
 8     {
 9         $this->id = (int)$data['id'];
10         $this->email = $data['email'];
11     } 
12 }

1 /* File Model_User.php*/
2 class Model_User extends ActiveRecord\Model
3 {
4     static $table_name = 'user';
5 }

1 /* file user_service.php*/
2 $data = Model_User::find('all'); 
3 $count = count ($data);
4 for ($i = 0; $i < $count; $i++)
5 {
6   $result[] = new UserVO($data[$i]->attributes());
7 }
8 return $result;

And this seems to work fine!

Any thoughts on the above? I am not sure if this is the most efficient way of doing things, so if you have any advice, then I'd really appreciate hearing about it.

I did notice that ActiveRecord comes with to_json and to_xml functions...I wonder if this could be extended to also have a to_amf function.


Jono B Wed Sep 15 16:04:50 -0400 2010

How silly of me. The above snippets should be changed to:

 1 /* file UserVO.php*/
 2 class UserVO
 3 {
 4     public $id;
 5     public $email;
 6 
 7     public function __construct($data)
 8     {
 9         $this->id = (int)$data->id;
10         $this->email = $data->email;
11     } 
12 }

1 /* file user_service.php*/
2 $data = Model_User::find('all'); 
3 $count = count ($data);
4 for ($i = 0; $i < $count; $i++)
5 {
6   $result[] = new UserVO($data[$i]);
7 }
8 return $result;
Jon Moberley Wed Oct 27 13:01:31 -0400 2010

Jono-

Looks like you're on the right track. Here is how I handle it:

 1 /* file VO.php */
 2 abstract class VO
 3 {
 4     public function __construct($attributes = array())
 5     {    
 6         foreach($attributes as $field => $value)
 7         {
 8             if(property_exists(get_called_class(), $field))
 9             {
10                 $this->$field = $value;
11             }
12         }
13     }
14 }

Then just have each of your VOs extend that class. I haven't had any trouble with types (Postgres DB/Flex 3 cleint) so at least for me the (int) cast on the ID isn't necessary.

Jono B Wed Oct 27 13:55:05 -0400 2010

Hey Jon,

Thanks for sharing, thats pretty cool.

What I have also done in the meantime is create my own (very simple) to_amf function. Most of the time I'm sending a strongly typed VO to the client, but sometimes its just a simple array. This function should be able to handle both.

 1 /**
 2  * Converts a phpar object to a typed VO object or array
 3  * @param array $data
 4  * @param string $vo if not supplied, then an array will be created instead of an object
 5  * @return object or array
 6  */
 7 function to_amf($data, $vo = null)
 8 {
 9     $count = count($data);
10 
11     if ($count == 0)
12         return $data;
13 
14     $result = array();
15 
16     if ( ! array_key_exists('0', $data))
17     {
18         $result = ($vo) ? new $vo($data) : $data->attributes();;
19     }
20     else
21     {
22         for ($i = 0; $i < $count; $i ++ )
23         {
24             $result[] = ($vo) ? new $vo($data[$i]) : $data[$i]->attributes();
25         }
26     }
27     return $result;
28 }

Regarding the ID fields, I like to type those, as in my case AMFPHP seems to convert everything to strings. So, ID's get typecast as (in), numbers as (float), bools as (bool) and I also convert dates to a DateTime object. Its a bit more work to set up on the server side, but at least then I am 100% guaranteed of the right format in the client. Not sure if this is a mysql or amfphp quirk or not...

I'll look into combining your code with this at some point in the not too distant future...

(1-3/3)