Issues

ZF-6641: Shared references are duplicated

Description

If I read correctly, Zend_Amf supports AMF3, but I encounter this case :


class Obj {
public $innerObj;
}

// create Obj instances
$o1 = new Obj();
$o2 = new Obj();
$o3 = new Obj();

// obj3 is shared between o1 and o2
$o1->innerObj = $o3;
$o2->innerObj = $o3;

// this is sent by AMF
$ret = array($o1, $o2);

Flex sees two diferrent innerObj instances...

(I also tried to set restore-references to true in flex's services-config, without success)

Comments

Anybody ? This is quite cumbersome... It forces us to have a kind of hashmap referencing all instances to avoid duplicates and so on.

As far as I've seen, the current AMF Serializer doesn't write class traits nor references. So your findings are definitely a missing feature on the Zend_Amf side. I'll also need that feature soon. Wade do you already have a schedule for this? - I could possibly work on a patch for that, but only if there are chances to get the patch committed...

I would greatly appreciate if you submitted a path. I can write the unit tests. Please use the code from the zend incubator as a lot of zend amf is changing. Greatly appreciate your help with this.

Wade

This bug keeps Zend Amf from fully implementing the AMF3 protocol.

Looking forward to have this patch ! Thanks Stefan.

As promised, here is a patch to for the Amf3 Serializer. It is rather huge as support for object references has an impact on nearly everything of the current serializer. (This patch is against revision 15132 in http://framework.zend.com/svn/framework/… )

The following features were added: - support for String references - support for classTrait references - support for object references (Objects, Arrays, XMLStrings, ByteArrays, Dates)

There is one notable glitch introduced with this patch: There is no support for dynamic classes beside the use of stdClass. (There never has been support for this, but the old serializer didn't use class trait references, and therefore treated each object on its own, and dynamic properties on the same class were automatically sent). We might add something like _isDynamic or getASDynamic to indicate, if a class is dynamic...

I've tested most of the code, except the ByteArray support. A descent testsuite is still needed.

BTW. This patch also addresses the problems discussed in ZF-6205.

Comments and (failing) tests are greatly appreciated.

Cheers Stefan

Uploaded new version of the patch. I forgot to exlude properties starting with "_" when sending dynamic objects.

Thank you Stefan !!! Everything works in my tests, the patch works great ! (just had to correct a small miss at line 216 this -> $this)

BTW, what are "dynamic classes" ?

Thanks again,

Thanks for the correction. (as I said a testsuite is still needed ;-)) I attached a new patch with the added $. Dynamic classes are classes where each instance can have different properties. The typical dynamic class is stdClass in PHP and Object is ActionScript. In ActionScript you can make a class dynamic by using the [Dynamic] metadata tag, whereas in PHP you can create them by overriding the __set __get magical functions.

Regards Stefan

Ok that's what I thought it was... We are using these behaviors, and in my tests your patch seems to work as expected !??.

Here is what I do on AS side :


package com.vega.core.model.amf
{
    [RemoteClass(alias="com.vega.core.model.amf.InnerObj")]
    dynamic public class InnerObj
    {
        public var a:String;
    }
}
package com.vega.core.model.amf
{
    [RemoteClass(alias="com.vega.core.model.amf.Obj")]
    public class Obj
    {
        public var innerObj:InnerObj;

    }
}

And on PHP side :


class Obj
{
    public $_explicitType = "com.vega.core.model.amf.Obj";
    // public $innerObj;
}

class InnerObj
{
    public $_explicitType = "com.vega.core.model.amf.InnerObj";
    public $a = "youhou";
}

function testReferences ()
{
    $a = new Obj();
    $a->innerObj = new InnerObj();
    // try dynamic member
    $a->innerObj->b = "test";

    $b = new Obj();    
    $b->innerObj = $a->innerObj;
    
    return array($a,$b);    
}

And I get the corrrect result in AS i.e. : - same reference to innerObj - innerObj having a dynamic member b = "test"

In which cases will this fail ?

Hi Mat,

I currently don't have the time to check this. I'm on vacation for one week. But I'm a bit puzzled, that you didn't get a php error when accessing $a->innerObj (the innerObj member was commented out in class Obj) and I would $a->innerObj->b expect to throw an error because of an undefined member b.

If that works for you it will most likely fail if you instntiate a second InnerObj, and define a dynamic member c. From this object only the members of the instance which occured first will get transferred. eg: innerObj1->b = "bb" innerObj2->c = "cc"

will result in 2 objects, one with b="aa" and the second with b=null and c undefined

Regards Stefan

Okay I confirm the limitations you are describing. It does not impact us too badly.

Thanks,

Mathieu

I applied this patch and I'd like to test it but I can't seem to trigger the switch between Amf0 and Amf3 - See extensive whinging about this in ZF-6205.

Stefan Klug can you update your patch against the current standard trunk. I have added a couple patches for other bugs and I can't get your patch to merge without lots of conflicts. I can go line by line if I really need to. Thanks!

Hi Wade,

please revert the last commit to Zend/Amf/Parse/Amf3/Serializer.php, then my patch should apply cleanly (I didn't try, only had a look at the SVN log). That last commit (ZF-6205/patch_serializer_v2.patch) is incomplete, breaks AMF serialization (as it doesn't reference XML, Dates etc.) and is completely replaced by my patch.

Regards Stefan

I reverted to 15896 and the patch still can not be applied. Can you attach your serializer and I can just go line by line.

Hi Wade,

I updated the patch to work with todays standard trunk (011-Zend_Amf_Parse_Amf3_Serializer.patch) and also attached my complete Serializer.php

BTW could you explain the exact semantics of the Incubator? I thought the Incubator is always the bleeding edge and the place for experimentation, whilest the trunk is a mostly stable version and only takes commits for files which don't exist in the incubator or copies from the incubator. But the Amf3 Serializer exists in Incubator, still you are committing to trunk. How are users expected to handle this situation? I normally place the incubator before the trunk in my php include path, so that the Incubator is layered ontop of trunk. This no longer works in the Serializer case, as trunk is newer.

Thanks a lot for clarification.

Regards Stefan

Committed to trunk with unit tests updated for shared references. Thanks stefan klug for your diligent work and suppling the As3 serialization patch!

This has been resolved in 1.8.4 please let me know if anyone experiences any additional issues.

Hello Wade, Thanks for your good work and this great framework! You asked for experiences regarding the fix. Well If one AMF3-Message contains string references as well as Bytearrays, the string references are getting mixed up - depending on the transmission order, string references are then wrong deserialized. This is because (at least I think so) string references are kept in a separate virtual table to complex objects virtual table. Because in the deserializer, the reading of bytearrays are traited as reading strings (see l. 107: case Zend_Amf_Constants::AMF3_BYTEARRAY: return $this->readString(); ) the string reference array gets mixed up when reading a bytearray. I think, the reading of bytearrays should go into the table $_referenceObjects

Here, the next question comes in: when reading complex objects, like dates, the method calls readString - if the date object has a reference inside (I am not whether this is possible at all), the readString method puts the complex object again in the $_stringReferences.

Thanks again for this framework, I am using it on a daily basis and like it very much, timtom