Passing custom typed objects as arguments on a PHP service method call

392 Views Asked by At

We're using zendamf as a remoting gateway between a flex client and a PHP server. Mapping server side types to client side types doesn't seem to have any affect on objects passed as service method parameters. All objects that have custom types are received as stdClass instances. Is there a way to force this? Or are we missing someting here?

Any thoughts?

Thx!

1

There are 1 best solutions below

0
On

A bit older this question but not answered, yet. So let me try. ;)

First @www.Flextras.com: You are right. In PHP it is also necessary to do some class mapping stuff.

So for all of you interested in how to define class mapping by using the Zend Framework, take a look at the following short but (in my opinion) detailed introduction. If you know all that then skip introduction and go ahead with "About client-to-server mapping and stdClass in Zend Framework"

The ways of class mapping in PHP using the Zend Framework

As the documentation of the Zend Framework's Zend_Amf_Server describes you have 3 options to provide class mapping. You can find the full documentation here (somewhere in the middle of the page under "Typed Objects").

First option

// Use the setClassMap() method of Zend_Amf_Server
require_once '<PathToZendFramework>/Zend/Amf/Server.php';
$server = new Zend_Amf_Server();

$server->setClassMap('MyActionScriptClassName', 'MyPHPClassName');

// the typical package notation for ActionScript class names is also allowed here
$server->setClassMap('com.company.MyActionScriptClassName', 'MyPHPClassName');

This is the most flexible option because you can dictate the class mappings explicitly for both directions client-to-server and server-to-client. In contrast to flexibility you can make more mistakes here. You will find a checklist below what to consider when using class mapping.

Second option

// Define a special named public property on each class used for
// serialization/deserialization via AMF
public class MyPHPClassName
{
    public $_explicitType = 'MyActionScriptClassName';

    /* your class definition here */
}

Doing it this way your class mapping becomes a bit more dynamic, because Zend_Amf_Server will search automatically for the $_explicitType property. So you do not have to define a class mapping explicitly like in the first option. Unfortunately using the $_explicitType you cannot define the PHP class name for the mapping (this is the "bit more dynamic"). Further down you will find a detailed description about the problem which raises here.

Third option

// Define a special named public method on each class used for
// serialization/deserialization via AMF
public class MyPHPClassName
{
    public function getASClassName()
    {
        return 'MyActionScriptClassName';
    }

    /* your class definition here */
}

By using this last option you will get the most dynamics out of class mapping. You can simply return a string literal like in my example, but this would be the same as using the $_explicitType option. The advantage of the method approach is to let classes generate the ActionScript class name dynamically. So you could define getASClassName() on the top-most class in hierarchy that all your AMF classes inherit from. The drawback here is the same as for the second option. You cannot define the PHP class name the class is mapped to.

Class Mapping Checklist for Flex and Zend Framework

If you made something wrong in specifying class mapping, Flex will not be able to convert to strongly typed objects. Instead you will get an instance of the generic Object class. If this happens you should check whether one ore more of the following things are the case.

On Flex side

  • Your class has to have a [RemoteClass] metadata tag.
  • If you specified the remote class with an alias ([RemoteClass(alias="")]), then check whether you have made a typing error.
  • Does your class (including all classes it inherits from) define all the public properties defined on the server side equivalent? (can be either a public var ... or a getter/setter pair)
  • Did you define the public properties as readwrite?
  • Did you mark properties not to use for transfer with the [Transient] metadata tag? (This will exclude the so-marked property from being serialzed/deserialized)

On Zend Framework side

  • Did you used one of the three options for class mapping?
  • If you used the first option, then did you mistype the either the ActionScript or the PHP class name? (The ActionScript class name has to be the exact same as defined in the [RemoteClass] tag in your Flex application)

About client-to-server mapping and stdClass in Zend Framework

I also ran into the problem that strongly typed arguments passed to service class methods on server side resulting in stdClass. I often tried to find out why this happens but never took the time to really find the reason. Yesterday, after an additional attempt to make class mapping as dynamic/generic as possible in one of my projects and after several hours of research I found what Zend_Amf_Server will do when getting an AMF request. (BTW: I had never found the reason if I hadn't implement strict class naming conventions into my PHP code).

When the AMF request is coming in and Zend_Amf_Server starts parsing it, it will read the some remote class alias from the request and tries to load that class automatically. That's what we want Zend_Amf_Server to do, right!? But the AMF server supports only automatisms for handling class names like MyClassName or conform to the framework's naming conventions something like My_Class_Name. You cannot map automatically classes with a remote class alias like com.company.SomeAmfClass. Zend expects that you specified a package (what represents an ordinary directory path) and converts all the periods to underscores before making an attempt to load the class. The class that will do this stuff is Zend_Amf_Parse_TypeLoader. The AMF server will call the static method loadType() on it.

Here's the implementation of that method (copied directly out of Zend Framework 1.11.7):

/**
  * Load the mapped class type into a callback.
  *
  * @param  string $className
  * @return object|false
  */
 public static function loadType($className)
 {
     $class    = self::getMappedClassName($className);
     if(!$class) {
         $class = str_replace('.', '_', $className);
     }
     if (!class_exists($class)) {
         return "stdClass";
     }
     return $class;
 }

First I've tried to define a class that implements Zend_Loader_Autoloader_Interface and pushed it onto the Zend_Loader_Autoloader singleton's autoloader stack. But the problem here was that Zend_Amf_Parse_TypeLoader::loadType() modifies the class name before autoloading takes place - what happens when class_exists() is invoked.

So what you could do is to change that method directly in the framework to handle ActionScript class names like com.company.SomeAmfClass in an additional way. But this should be the very last option, because its bad practice to change framework code!

Another solution - and almost the cleanest - would be to reorganize and rename all your classes to fit into the Zend Framework's naming conventions, but unfortunately this may take hours of refactoring (what's not really easy to handle in PHP) and maybe days of testing and debugging your whole codebase.

To strike a balance you could monkey-patch the required framework classes. So you would not change the framework itself but do not refactor your codebase. There are some frameworks that will do the trick. I found some examples in the answers of this question

Additionally found this question here: AMF typed objects from flex back to PHP You should check whether it fits your needs.

Hope this will be helpful to someone. Tell me if I forgot something (for example in the checklist!)