SabreAMF 0.3 released + more info on class mappings

Renaun Erickson recently wrote an article on how to use SabreAMF with Flex 2's services.xml configuration file..

He had some positive critique and also made me realize I made a mistake in confusing the 'source' and 'destination' properties in Flex' RemoteObject. I updated the CallBackServer as soon as I could to fix that.. Be sure to download the updated version.

Also, take a look at the article, as its quite complete in explaining how to setup Flex/FDS to communicate with SabreAMF.

The extended version

Renaun modified the CallBackServer a bit to work more like AMFPHP. I understand the need for this and I might add in an extra class to support this behaviour.. It would definitely an optional one.

The most important change was that his CallBackServer now constructs classes based on the destination and automatically invokes methods. He also added in support for AMFPHP's _explicitType attribute to automatically convert arrays and objects to a Flash/Flex class.

First thing I should note is that his implementation only works for the AMF3 part.. the AMF0 part does not have his extended behaviour. If he would have created a new class which constructed a CallBackServer instead, and listened to the onInvokeService this would work for both AMF0 and AMF3. I listed below how I could recommend extending the system. Most of the code is copy-pasted from Renauns version, except that I moved it into a separate class.

<?php

  // Warning, untested

  require_once 'SabreAMF/CallbackServer.php';

  class ServiceMapper {

     private $server;
     private $baseClassPath;

     function __construct() {

        //Construct the server
        $this->server = new SabreAMF_CallbackServer();
        // Listen to the event
        $this->server->onInvokeService = array($this,'invokeService');

     }

     function invokeService($service,$method,$arguments) {

       $dirname = realpath("./" . $this->baseClassPath);

       //Does the classpath exist    

       if (is_dir($dirname)) {
          chdir($dirname);
       } else {
         throw new Exception('Could not locate base class path: ' . $this->baseClassPath);
       }

       $classpath = $dirname . str_replace('.','/',$service) . '.php';

       //Can we actually open the file?

       if (!is_readable($classpath)) {
         throw new Exception('Could not open file: ' . $classpath);
       }

       require_once $classpath;

       $classname = str_replace('.','_',$source);

       if (!class_exists($classname)) {
          throw new Exception('Class not found: ' . $classname);
       }

       $serviceInstance = new $class;

       // Invoking the method

       return call_user_func_array(array($serviceInstance,$method),$arguments);


     }

     function setBaseClassPath($classPath = 'services/') {
        $this->baseClassPath = $classPath;
     }

     function exec() {

        return $this->server->exec();

     }

  }

  $server = new ServiceMapper();

  $server->setBaseClassPath('services/');

  $server->exec();

?>

I will probably add this class to the source (under a different name) when this is well tested.

Second thing is the _explicitType part. While this works easily if you have an AMFPHP background, SabreAMF has a more OOP equivalent for this. There are 3 different ways to map classes (force types).

<?php

  $data = array(
     'prop1' => 'val1',
     'prop2' => 'val2',
  );

  // If want to force a flash class in AMFPHP you would add in:

  $data['_explicitType'] = 'FlashClass';

  // The SabreAMF equivalent:

  // Be sure this class is included in your application
  require_once 'SabreAMF/TypedObject.php';

  $data = new SabreAMF_TypedObject('FlashClass',$data);

?>

If you are making classes on the PHP backend which are replicas of Flash/Flex classes there is an even easier way to do it, using the classmapper.

<?php

  //This is done on the beginning of the application, as this is usually a global thing and only has to be done at 1 point

  require_once 'SabreAMF/ClassMapper.php';

  SabreAMF_ClassMapper::registerClass('FlexClassName','PHPClassName');

?>

This will work in both directions, so if you make a request with a certain flex class, it will also be translated to the PHP class.

There is a third way to map classes, this is mostly useful if you already have a set of classes in your framework and you want to do extra modifications to the data before it gets sent back to the flashplayer. This is done through the ITypedObject interface..

<?php
  
  require_once 'SabreAMF/ITypedObject.php';

  class MyClass implements SabreAMF_ITypedObject {

    public $property1;
    public $property2;
    public $secretProperty;

    function getAMFClassName() {

       return 'org.rooftop.MyFlexClass';

    }

    function getAMFData() {
 
       return array(
          'property1' => $this->property1;
          'property2' => $this->property2;
       );

    }

  }

?>