Zend Framework

(De)Serialization of objects implementing ArrayAccess or extending ArrayObject and using magic functions __get and __set

Details

  • Type: Bug Bug
  • Status: Open Open
  • Priority: Major Major
  • Resolution: Unresolved
  • Affects Version/s: 1.8.1
  • Fix Version/s: None
  • Component/s: Zend_Amf
  • Labels:
    None

Description

Defining the environment

I recognised those two problems trying to send Doctrine-models via AMF.

On PHP-side i have typical doctrine-classes in a specified relation:

abstract class BaseArea extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->setTableName('area');
        $this->hasColumn('id', 'integer', 4, array('type' => 'integer', 'primary' => true, 'autoincrement' => true, 'length' => '4'));
        $this->hasColumn('content_id', 'integer', 4, array('type' => 'integer', 'length' => '4'));
        $this->hasColumn('followed_by', 'integer', 4, array('type' => 'integer', 'length' => '4'));
    }

    public function setUp()
    {
       $this->hasMany('Point as Points', array('local' => 'id',
                                                'foreign' => 'area_id'));
    }
}

abstract class BasePoint extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->setTableName('point');
        $this->hasColumn('id', 'integer', 4, array('type' => 'integer', 'primary' => true, 'autoincrement' => true, 'length' => '4'));
        $this->hasColumn('top', 'integer', 4, array('type' => 'integer', 'length' => '4'));
        $this->hasColumn('left', 'integer', 4, array('type' => 'integer', 'length' => '4'));
        $this->hasColumn('area_id', 'integer', 4, array('type' => 'integer', 'length' => '4'));
    }

    public function setUp()
    {
        $this->hasOne('Area', array('local' => 'area_id',
                                    'foreign' => 'id'));
    }
}

Normal access to depending objects (in this case Points) works like this in PHP:

$result = Doctrine::getTable('Area')->findAll();

foreach($result[0]->Points as $point) {
   // do some freaky stuff with this point
}

Points is a Doctrine_Collection implementing ArrayAccess.

First problem

Fetching an $area-instance from the db and sending it via AMF does not serialize the Points.
This happens because Zend_AMF does not know that there is a "hidden" property called "Points" as it is only handled via the magic functions __get and __set (see doctrine source code for this). Now thinking about how to solve this i think i know, that AMF cannot be changed to use a configuration parameter like "concernMagicProperties":

$server = new Zend_AMF_Server();
$server->concernMagicProperties = true;

Well. I think this is not possible (and maybe also not practical for all occasions). So i made the following change. Doctrine supports adding kind of "unused" fields like this:

$area->mapValue('_hiddenFields', array('Points'));

This makes sure Doctrine continues to work (and does not complain about integrity with the database) and Zend_AMF can access this property to find out which hidden fields should be serialized. In a normal environment with the same problem (not using Doctrine) this could be reached by an public property _hiddenFields which will not be serialized or, maybe more beautiful (this idea just comes to my mind), implementing a Zend_AMF_Value_MagicBehaviour:

interface Zend_AMF_Value_MagicBehaviour {
    /** @return array ***/
    public function getHiddenFields();
}

Second Problem

After changing Zend_AMF to implement the first behaviour (using the public property _hiddenFields) i recognised the second problem:
AMF serializes the Doctrine_Collection as a named object - which is entirely correct, but the problem is, that there must be a matching AS-class which would have required me to implement sort of my own AS-Doctrine-Collection with the big problem, that the property names are unknown at the time of compilation (the properties are called 1 2 3 4 5...) and as AS has no possibility to handle this - or i don't know it (no magic __set or so) - the only possibility was to change Zend_AMF so that this class gets serialized as an Array. AS has enough ways to change this array and send the list back.

To solve this i made a dirty hack in Zend_AMF to check whether the given object is an instance of Doctrine_Collection and handle it as array. More beautiful would be a similar behaviour as mentioned above:

interface Zend_AMF_Value_HandleAsArray {}

If a class implements this Interface, Zend_AMF should handle the object as an array (not only if it IS an array).

Summarizing there are two problems to solve:

  • How can magic "hidden" properties be serialized
  • How can classes that implement ArrrayAccess or extend ArrayObject be serialized as an Array

I documented my specific changes in a patch which might not be the best, but it was a fix for me to use Doctrine.
I would like to hear your opinion on this topic, also if there is another way of design.

(offtopic: btw: why doesn't zend framework trac has codeformatting for php? )

Activity

Hide
Robert Cesaric added a comment -

Here's a hack I wrote to send Doctrine models via AMF: http://cesaric.com/?p=303. It'll get all the nested collections of a data graph and convert them to standard objects and ActionScript ArrayCollections.

On a similar note, any luck going the other way around in trying to send ActionScript objects over AMF to the corresponding Doctrine model object?

I can't seem to get ActionScript classes to map to the respective Doctrine model classes. I'm using the $server->setClassMap(); method and it works fine as long as I'm not mapping to a php class that extends "Doctrine_Record" (my BaseContact class below does extend Doctrine_Record). If I comment out "extends BaseContact" below, Zend_Amf type casts it just fine.

class Contact extends BaseContact
{

public function construct(){ $this->mapValue("_explicitType","com.example.vo.ContactVO"); }

}

Any suggestions? Btw, great work on Zend_Amf. It's coming along nicely!

Show
Robert Cesaric added a comment - Here's a hack I wrote to send Doctrine models via AMF: http://cesaric.com/?p=303. It'll get all the nested collections of a data graph and convert them to standard objects and ActionScript ArrayCollections. On a similar note, any luck going the other way around in trying to send ActionScript objects over AMF to the corresponding Doctrine model object? I can't seem to get ActionScript classes to map to the respective Doctrine model classes. I'm using the $server->setClassMap(); method and it works fine as long as I'm not mapping to a php class that extends "Doctrine_Record" (my BaseContact class below does extend Doctrine_Record). If I comment out "extends BaseContact" below, Zend_Amf type casts it just fine. class Contact extends BaseContact { public function construct(){ $this->mapValue("_explicitType","com.example.vo.ContactVO"); } } Any suggestions? Btw, great work on Zend_Amf. It's coming along nicely!
Hide
Joshua added a comment -

The 'Doctrine_Record' may not be available to your php service at the time amf is deserializing the objects. Hack open one of the amf files, put reference to a Doctrine_Record in there. If you get an error, there's your problem.

Show
Joshua added a comment - The 'Doctrine_Record' may not be available to your php service at the time amf is deserializing the objects. Hack open one of the amf files, put reference to a Doctrine_Record in there. If you get an error, there's your problem.

People

Vote (0)
Watch (2)

Dates

  • Created:
    Updated: