<?php
/**
* AMFSerializer manages the job of translating PHP objects into
* the actionscript equivalent via amf. The main method of the serializer
* is the serialize method which takes and AMFObject as it's argument
* and builds the resulting amf body.
*
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @copyright (c) 2003 amfphp.org
* @package flashservices
* @subpackage io
* @version $Id$
*/
define("MAX_STORED_OBJECTS", 1024);
include_once(AMFPHP_BASE . "amf/io/AMFBaseSerializer.php");
class AMFSerializer extends AMFBaseSerializer {
/**
* Classes that are serialized as recordsets
*/
var $amf0StoredObjects = array();
var $storedObjects = array();
var $storedDefinitions = 0;
var $storedStrings = array();
var $outBuffer;
var $encounteredStrings = array();
var $native = false;
/**
* AMFSerializer is the constructor function. You must pass the
* method an AMFOutputStream as the single argument.
*
* @param object $stream The AMFOutputStream
*/
function AMFSerializer() {
AMFBaseSerializer::AMFBaseSerializer();
}
/**
* writeBoolean writes the boolean code (0x01) and the data to the output stream
*
* @param bool $d The boolean value
*/
function writeBoolean($d) {
$this->writeByte(1); // write the boolean flag
$this->writeByte($d); // write the boolean byte
}
/**
* writeString writes the string code (0x02) and the UTF8 encoded
* string to the output stream.
* Note: strings are truncated to 64k max length. Use XML as type
* to send longer strings
*
* @param string $d The string data
*/
function writeString($d) {
$count = strlen($d);
if($count < 65536)
{
$this->writeByte(2);
$this->writeUTF($d);
}
else
{
$this->writeByte(12);
$this->writeLongUTF($d);
}
}
/**
* writeXML writes the xml code (0x0F) and the XML string to the output stream
* Note: strips whitespace
* @param string $d The XML string
*/
function writeXML($d) {
if(!$this->writeReferenceIfExists($d))
{
$this->writeByte(15);
$this->writeLongUTF(preg_replace('/\>(\n|\r|\r\n| |\t)*\</','><',trim($d)));
}
}
/**
* writeData writes the date code (0x0B) and the date value to the output stream
*
* @param date $d The date value
*/
function writeDate($d) {
$this->writeByte(11); // write date code
$this->writeDouble($d); // write date (milliseconds from 1970)
/**
* write timezone
* ?? this is wierd -- put what you like and it pumps it back into flash at the current GMT ??
* have a look at the amf it creates...
*/
$this->writeInt(0);
}
/**
* writeNumber writes the number code (0x00) and the numeric data to the output stream
* All numbers passed through remoting are floats.
*
* @param int $d The numeric data
*/
function writeNumber($d) {
$this->writeByte(0); // write the number code
$this->writeDouble(floatval($d)); // write the number as a double
}
/**
* writeNull writes the null code (0x05) to the output stream
*/
function writeNull() {
$this->writeByte(5); // null is only a 0x05 flag
}
/**
* writeArray first deterines if the PHP array contains all numeric indexes
* or a mix of keys. Then it either writes the array code (0x0A) or the
* object code (0x03) and then the associated data.
*
* @param array $d The php array
*/
function writeArray($d)
{
if($this->writeReferenceIfExists($d))
{
return;
}
$numeric = array(); // holder to store the numeric keys
$string = array(); // holder to store the string keys
$len = count($d); // get the total number of entries for the array
$largestKey = -1;
foreach($d as $key => $data) { // loop over each element
if (is_int($key) && ($key >= 0)) { // make sure the keys are numeric
$numeric[$key] = $data; // The key is an index in an array
$largestKey = max($largestKey, $key);
} else {
$string[$key] = $data; // The key is a property of an object
}
}
$num_count = count($numeric); // get the number of numeric keys
$str_count = count($string); // get the number of string keys
if ( ($num_count > 0 && $str_count > 0) ||
($num_count > 0 && $largestKey != $num_count - 1)) { // this is a mixed array
$this->writeByte(8); // write the mixed array code
$this->writeLong($num_count); // write the count of items in the array
$this->writeObjectFromArray($numeric + $string); // write the numeric and string keys in the mixed array
} else if ($num_count > 0) { // this is just an array
$num_count = count($numeric); // get the new count
$this->writeByte(10); // write the array code
$this->writeLong($num_count); // write the count of items in the array
for($i = 0 ; $i < $num_count ; $i++) { // write all of the array elements
$this->writeData($numeric[$i]);
}
} else if($str_count > 0) { // this is an object
$this->writeByte(3); // this is an object so write the object code
$this->writeObjectFromArray($string); // write the object name/value pairs
} else { //Patch submitted by Jason Justman
$this->writeByte(10); // make this an array still
$this->writeInt(0); // give it 0 elements
$this->writeInt(0); // give it an element pad, this looks like a bug in Flash,
//but keeps the next alignment proper
}
}
function writeReferenceIfExists($d)
{
if(count($this->amf0StoredObjects) >= MAX_STORED_OBJECTS)
{
return false;
}
if(is_array($d))
{
$this->amf0StoredObjects[] = "";
return false;
}
if(($key = patched_array_search($d, $this->amf0StoredObjects, true)) !== FALSE)
{
$this->writeReference($key);
return true;
}
else
{
$this->amf0StoredObjects[] = & $d;
return false;
}
}
function writeReference($num)
{
$this->writeByte(0x07);
$this->writeInt($num);
}
/**
* Write a plain numeric array without anything fancy
*/
function writePlainArray($d)
{
if(!$this->writeReferenceIfExists($d))
{
$num_count = count($d);
$this->writeByte(10); // write the mixed array code
$this->writeLong($num_count); // write the count of items in the array
for($i = 0 ; $i < $num_count ; $i++) { // write all of the array elements
$this->writeData($d[$i]);
}
}
}
/**
* writeObject handles writing a php array with string or mixed keys. It does
* not write the object code as that is handled by the writeArray and this method
* is shared with the CustomClass writer which doesn't use the object code.
*
* @param array $d The php array with string keys
*/
function writeObjectFromArray($d) {
foreach($d as $key => $data) { // loop over each element
$this->writeUTF($key); // write the name of the object
$this->writeData($data); // write the value of the object
}
$this->writeInt(0); // write the end object flag 0x00, 0x00, 0x09
$this->writeByte(9);
}
/**
* writeObject handles writing a php array with string or mixed keys. It does
* not write the object code as that is handled by the writeArray and this method
* is shared with the CustomClass writer which doesn't use the object code.
*
* @param array $d The php array with string keys
*/
function writeAnonymousObject($d) {
if(!$this->writeReferenceIfExists($d))
{
$this->writeByte(3);
$objVars = (array) $d;
foreach($d as $key => $data) { // loop over each element
if($key[0] != "\0")
{
$this->writeUTF($key); // write the name of the object
$this->writeData($data); // write the value of the object
}
}
$this->writeInt(0); // write the end object flag 0x00, 0x00, 0x09
$this->writeByte(9);
}
}
/**
* writePHPObject takes an instance of a class and writes the variables defined
* in it to the output stream.
* To accomplish this we just blanket grab all of the object vars with get_object_vars
*
* @param object $d The object to serialize the properties
*/
function writeTypedObject($d) {
if($this->writeReferenceIfExists($d))
{
return;
}
$this->writeByte(16); // write the custom class code
$classname = $this->getClassName($d);
$this->writeUTF($classname); // write the class name
if(AMFPHP_PHP5)
{
$objVars = $d;
}
else
{
$objVars = (array) $d;
}
foreach($objVars as $key => $data) { // loop over each element
if($key[0] != "\0")
{
$this->writeUTF($key); // write the name of the object
$this->writeData($data); // write the value of the object
}
}
$this->writeInt(0); // write the end object flag 0x00, 0x00, 0x09
$this->writeByte(9);
}
/**
* writeRecordSet is the abstracted method to write a custom class recordset object.
* Any recordset from any datasource can be written here, it just needs to be properly formatted
* beforehand.
*
* This was unrolled with at the expense of readability for a
* 10 fold increase in speed in large recordsets
*
* @param object $rs The formatted RecordSet object
*/
function writeRecordSet(&$rs)
{
//Low-level everything here to make things faster
//This is the bottleneck of AMFPHP, hence the attention in making things faster
if($this->writeReferenceIfExists($rs))
{
return;
}
$ob = "";
$data = $rs->rows;
if($GLOBALS['amfphp']['encoding'] == 'amf0')
{
$this->writeByte(16); // write the custom class code
$this->writeUTF("RecordSet"); // write the class name
$this->writeUTF("serverInfo");
//Start writing inner object
$this->writeByte(3); // this is an object so write the object code
//Write total count
$this->writeUTF("totalCount");
$this->writeNumber($rs->getRowCount());
//Write initial data
$this->writeUTF("initialData");
//Inner numeric array
$colnames = $rs->columns;
$num_count = count($rs->rows);
$this->writeByte(10); // write the mixed array code
$this->writeLong($num_count); // write the count of items in the array
//Allow recordsets to create their own serialized data, which is faster
//since the recordset array is traversed only once
$numcols = count($colnames);
$ob = "";
$be = $this->isBigEndian;
$fc = pack('N', $numcols);
for($i = 0 ; $i < $num_count ; $i++)
{
// write all of the array elements
$ob .= "\12" . $fc;
for($j = 0; $j < $numcols; $j++) { // write all of the array elements
$d = $data[$i][$j];
if (is_string($d))
{ // type as string
$os = $this->rsCharsetHandler->transliterate($d);
//string flag, string length, and string
$ob .= "\2" . pack('n', strlen($os)) . $os;
}
elseif (is_float($d) || is_int($d))
{ // type as double
$ob .= "\0";
$b = pack('d', $d); // pack the bytes
if ($be) { // if we are a big-endian processor
$r = strrev($b);
} else { // add the bytes to the output
$r = $b;
}
$ob .= $r;
}
elseif (is_bool($d))
{ //type as bool
$ob .= "\1";
$ob .= pack('c', $d);
}
elseif (is_null($d))
{ // null
$ob .= "\5";
}
}
}
$this->outBuffer .= $ob;
//Write cursor
$this->writeUTF("cursor");
$this->writeNumber(1);
//Write service name
$this->writeUTF("serviceName");
$this->writeString("PageAbleResult");
//Write column names
$this->writeUTF("columnNames");
$this->writePlainArray($colnames, 'string');
//Write version number
$this->writeUTF("version");
$this->writeNumber(1);
//Write id
$this->writeUTF("id");
$this->writeString($rs->getID());
//End inner serverInfo object
$this->writeInt(0); // write the end object flag 0x00, 0x00, 0x09
$this->writeByte(9);
//End outer recordset object
$this->writeInt(0); // write the end object flag 0x00, 0x00, 0x09
$this->writeByte(9);
$this->paging = -1;
}
else
{
$numObjects= 0;
$this->writeAmf3ArrayCollectionPreamble();
//Amf3 array code
$this->writeByte(0x09);
$numObjects++;
$numRows = count($rs->rows);
$toPack = 2*$numRows + 1;
//Write the number of rows
$this->writeAmf3Int($toPack);
//No string keys in this array
$this->writeByte(0x01);
$numCols = count($rs->columns);
$columnStringOffsets = array();
if($numRows > 0)
{
$j = 0;
$colNames = array();
$rows = $rs->rows;
foreach ($rows as $key => $line) {
//Usually we don't use class defs in the serializer since we don't
//have sealed objects in php, but for recordsets we do use them
//since they are well suited for what we have to do (the same keys
//across all objects)
if($key == 0)
{
$this->outBuffer .= "\12";
$this->writeAmf3Int($numCols << 4 | 3);
$this->outBuffer .= "\1";
foreach($rs->columns as $key => $val)
{
$this->writeAmf3String($val);
}
$defOffset = $this->getAmf3Int(
($this->storedDefinitions) << 2 | 1
);
$this->storedDefinitions++;
}
else
{
$this->outBuffer .= "\12" . $defOffset;
}
$numObjects++;
for($i = 0; $i < $numCols; $i++)
{
//Write the col name
$value = $line[$i];
if(is_string($value))
{
$this->outBuffer .= "\6";
$value = $this->rsCharsetHandler->transliterate($value);
$this->writeAmf3String($value, true);
}
elseif(is_int($value))
{ //int
$this->writeAmf3Number($value);
}
elseif(is_float($value))
{ //double
$this->outBuffer .= "\5";
$b = pack("d", $value); // pack the bytes
if ($this->isBigEndian) { // if we are a big-endian processor
$r = strrev($b);
} else { // add the bytes to the output
$r = $b;
}
$this->outBuffer .= $r;
}
elseif(is_bool($value))
{
$this->outBuffer .= $value ? "\3" : "\2";
}
else
{
$this->outBuffer .= "\1"; //null
}
}
//End object
}
}
//Add fake objects to make sure the object counter still works
for($i = 0; $i < $numObjects; $i++)
{
$this->storedObjects[] = "";
}
}
}
/**
* writeData checks to see if the type was declared and then either
* auto negotiates the type or relies on the user defined type to
* serialize the data into amf
*
* Note that autoNegotiateType was eliminated in order to tame the
* call stack which was getting huge and was causing leaks
*
* manualType allows the developer to explicitly set the type of
* the returned data. The returned data is validated for most of the
* cases when possible. Some datatypes like xml and date have to
* be returned this way in order for the Flash client to correctly serialize them
*
* recordsets appears top on the list because that will probably be the most
* common hit in this method. Followed by the
* datatypes that have to be manually set. Then the auto negotiatable types last.
* The order may be changed for optimization.
*
* @param mixed $d The data
* @param string $type The optional type
*/
function writeData(& $d) {
if (is_int($d) || is_float($d))
{ // double
$this->writeNumber($d);
return;
}
elseif (is_string($d))
{ // string
$this->writeString($d);
return;
}
elseif (is_bool($d))
{ // boolean
$this->writeBoolean($d);
return;
}
elseif (is_null($d))
{ // null
$this->writeNull();
return;
}
elseif ($GLOBALS['amfphp']['encoding'] == 'amf3')
{
$this->writeByte(0x11);
$this->writeAmf3Data($d);
return;
}
elseif (is_array($d))
{ // array
$this->writeArray($d);
return;
}
elseif (is_resource($d))
{ // resource
$type = get_resource_type($d);
list($type, $subtype) = $this->sanitizeType($type);
}
elseif (is_object($d))
{
$className = strtolower(get_class($d));
if(array_key_exists($className, $this->resourceObjects))
{
$type = "__RECORDSET__";
$subtype = $this->resourceObjects[strtolower(get_class($d))];
}
else if(AMFPHP_PHP5 && $className == 'domdocument')
{
$this->writeXML($d->saveXml());
return;
}
else if(!AMFPHP_PHP5 && $className == 'domdocument')
{
$this->writeXML($d->dump_mem());
return;
}
elseif($className == "simplexmlelement")
{
$this->writeXML($d->asXML());
return;
}
else if($className == 'stdclass' && !isset($d->_explicitType))
{
$this->writeAnonymousObject($d);
return;
}
elseif(is_a($d, 'ArrayAccess') || is_a($d, 'ArrayObject'))
{
$this->writeArray($d);
return;
}
else
{
$this->writeTypedObject($d);
return;
}
}
else
{
$type = gettype($d);
}
switch ($type) {
case "__RECORDSET__" :
$classname = $subtype . "Adapter"; // full class name
$includeFile = include_once(AMFPHP_BASE . "shared/adapters/" . $classname . ".php"); // try to load the recordset library from the sql folder
if (!$includeFile) {
trigger_error("The recordset filter class " . $classname . " was not found", E_USER_ERROR);
}
$recordSet = new $classname($d); // returns formatted recordset
$this->writeRecordSet($recordSet); // writes the recordset formatted for Flash
break;
default:
// non of the above so lets assume its a Custom Class thats defined in the client
$this->writeTypedObject($unsanitizedType, $d);
// trigger_error("Unsupported Datatype");
break;
}
}
/********************************************************************************
* AMF3 related code
*******************************************************************************/
function writeAmf3Data(& $d)
{
if (is_int($d))
{ //int
$this->writeAmf3Number($d);
return;
}
elseif(is_float($d))
{ //double
$this->outBuffer .= "\5";
$this->writeDouble($d);
return;
}
elseif (is_string($d))
{ // string
$this->outBuffer .= "\6";
$this->writeAmf3String($d);
return;
}
elseif (is_bool($d))
{ // boolean
$this->writeAmf3Bool($d);
return;
}
elseif (is_null($d))
{ // null
$this->writeAmf3Null();
return;
}
elseif (is_array($d))
{ // array
$this->writeAmf3Array($d);
return;
}
elseif (is_resource($d))
{ // resource
$type = get_resource_type($d);
list($type, $subtype) = $this->sanitizeType($type);
}
elseif (is_object($d))
{
$className = strtolower(get_class($d));
if(array_key_exists($className, $this->resourceObjects))
{
$type = "__RECORDSET__";
$subtype = $this->resourceObjects[strtolower(get_class($d))];
}
else if(AMFPHP_PHP5 && $className == 'domdocument')
{
$this->writeAmf3Xml($d->saveXml());
return;
}
else if(!AMFPHP_PHP5 && $className == 'domdocument')
{
$this->writeAmf3Xml($d->dump_mem());
return;
}
elseif($className == "simplexmlelement")
{
$this->writeAmf3Xml($d->asXML());
return;
}
elseif($className == 'bytearray')
{
$this->writeAmf3ByteArray($d->data);
return;
}
elseif(is_a($d, 'ArrayAccess') || is_a($d, 'ArrayObject'))
{
$this->writeAmf3Array($d, true);
return;
}
else
{
$this->writeAmf3Object($d);
return;
}
}
else
{
$type = gettype($d);
}
switch ($type) {
case "__RECORDSET__" :
$classname = $subtype . "Adapter"; // full class name
$includeFile = include_once(AMFPHP_BASE . "shared/adapters/" . $classname . ".php"); // try to load the recordset library from the sql folder
if (!$includeFile) {
trigger_error("The recordset filter class " . $classname . " was not found");
}
$GLOBALS['amfphp']['stringOffset'] = count($this->storedStrings);
$recordSet = new $classname($d); // returns formatted recordset
$this->writeRecordSet($recordSet); // writes the recordset formatted for Flash
break;
default:
// non of the above so lets assume its a Custom Class thats defined in the client
//$this->writeTypedObject($unsanitizedType, $d);
trigger_error("Unsupported Datatype: " . $type);
break;
}
}
/**
* Write an ArrayCollection
*/
function writeAmf3ArrayCollectionPreamble()
{
$this->writeByte(0x0a);
$this->writeByte(0x07);
$this->writeAmf3String("flex.messaging.io.ArrayCollection");
$this->storedDefinitions++;
$this->storedObjects[] = "";
}
function writeAmf3Null()
{
//Write the null code (0x1) to the output stream.
$this->outBuffer .= "\1";
}
function writeAmf3Bool($d)
{
$this->outBuffer .= $d ? "\3" : "\2";
}
function writeAmf3Int($d)
{
//Sign contraction - the high order bit of the resulting value must match every bit removed from the number
//Clear 3 bits
$d &= 0x1fffffff;
if($d < 0x80)
{
$this->outBuffer .= chr($d);
}
elseif($d < 0x4000)
{
$this->outBuffer .= chr($d >> 7 & 0x7f | 0x80) . chr($d & 0x7f);
}
elseif($d < 0x200000)
{
$this->outBuffer .= chr($d >> 14 & 0x7f | 0x80) . chr($d >> 7 & 0x7f | 0x80) . chr($d & 0x7f);
}
else
{
$this->outBuffer .= chr($d >> 22 & 0x7f | 0x80) . chr($d >> 15 & 0x7f | 0x80) .
chr($d >> 8 & 0x7f | 0x80) . chr($d & 0xff);
}
}
function writeAmf3String($d, $raw = false)
{
if( $d == "" )
{
//Write 0x01 to specify the empty ctring
$this->outBuffer .= "\1";
}
else
{
if( !isset($this->storedStrings[$d]))
{
if(strlen($d) < 64)
{
$this->storedStrings[$d] = $this->encounteredStrings;
}
if(!$raw)
{
$d = $this->charsetHandler->transliterate($d);
}
$handle = strlen($d);
$this->writeAmf3Int($handle*2 + 1);
$this->outBuffer .= $d;
$this->encounteredStrings++;
return $this->encounteredStrings - 1;
}
else
{
$key = $this->storedStrings[$d];
$handle = $key << 1;
$this->writeAmf3Int($handle);
return $key;
}
}
}
function writeAmf3Array($d, $arrayCollectionable = false)
{
//Circular referencing is disabled in arrays
//Because if the array contains only primitive values,
//Then === will say that the two arrays are strictly equal
//if they contain the same values, even if they are really distinct
//if(($key = patched_array_search($d, $this->storedObjects, TRUE)) === FALSE )
//{
if(count($this->storedObjects) < MAX_STORED_OBJECTS)
{
$this->storedObjects[] = & $d;
}
$numeric = array(); // holder to store the numeric keys
$string = array(); // holder to store the string keys
$len = count($d); // get the total number of entries for the array
$largestKey = -1;
foreach($d as $key => $data) { // loop over each element
if (is_int($key) && ($key >= 0)) { // make sure the keys are numeric
$numeric[$key] = $data; // The key is an index in an array
$largestKey = max($largestKey, $key);
} else {
$string[$key] = $data; // The key is a property of an object
}
}
$num_count = count($numeric); // get the number of numeric keys
$str_count = count($string); // get the number of string keys
if (($str_count > 0 && $num_count == 0) ||
($num_count > 0 && $largestKey != $num_count - 1)) { // this is a mixed array
$this->writeAmf3ObjectFromArray($numeric + $string); // write the numeric and string keys in the mixed array
} else { // this is just an array
if($arrayCollectionable)
{
$this->writeAmf3ArrayCollectionPreamble();
}
$num_count = count($numeric);
$this->outBuffer .= "\11";
$handle = $num_count * 2 + 1;
$this->writeAmf3Int($handle);
foreach($string as $key => $val)
{
$this->writeAmf3String($key);
$this->writeAmf3Data($val);
}
$this->writeAmf3String(""); //End start hash
for($i = 0; $i < $num_count; $i++)
{
$this->writeAmf3Data($numeric[$i]);
}
}
//}
//else
//{
// $handle = $key << 1;
// $this->outBuffer .= "\11";
// $this->writeAmf3Int($handle);
//}
}
function writeAmf3ObjectFromArray($d)
{
//Type this as a dynamic object
$this->outBuffer .= "\12\13\1";
foreach($d as $key => $val)
{
$this->writeAmf3String($key);
$this->writeAmf3Data($val);
}
//Now we close the open object
$this->outBuffer .= "\1";
}
/*
public void WriteAMF3DateTime(DateTime value)
{
if( !_objectReferences.Contains(value) )
{
_objectReferences.Add(value, _objectReferences.Count);
int handle = 1;
WriteAMF3IntegerData(handle);
// Write date (milliseconds from 1970).
DateTime timeStart = new DateTime(1970, 1, 1, 0, 0, 0);
string timezoneCompensation = System.Configuration.ConfigurationSettings.AppSettings["timezoneCompensation"];
if( timezoneCompensation != null && ( timezoneCompensation.ToLower() == "auto" ) )
{
value = value.ToUniversalTime();
}
TimeSpan span = value.Subtract(timeStart);
long milliSeconds = (long)span.TotalMilliseconds;
long date = BitConverter.DoubleToInt64Bits((double)milliSeconds);
this.WriteLong(date);
}
else
{
int handle = (int)_objectReferences[value];
handle = handle << 1;
WriteAMF3IntegerData(handle);
}
}
*/
function getAmf3Int($d)
{
$d &= 0x1fffffff;
if($d < 0x80)
{
return chr($d);
}
elseif($d < 0x4000)
{
return chr($d >> 7 & 0x7f | 0x80) . chr($d & 0x7f);
}
elseif($d < 0x200000)
{
return chr($d >> 14 & 0x7f | 0x80) . chr($d >> 7 & 0x7f | 0x80) . chr($d & 0x7f);
}
else
{
return chr($d >> 22 & 0x7f | 0x80) . chr($d >> 15 & 0x7f | 0x80) .
chr($d >> 8 & 0x7f | 0x80) . chr($d & 0xff);
}
}
function writeAmf3Number($d)
{
if($d >= -268435456 && $d <= 268435455)//check valid range for 29bits
{
$this->outBuffer .= "\4";
$this->writeAmf3Int($d);
}
else
{
//overflow condition would occur upon int conversion
$this->outBuffer .= "\5";
$this->writeDouble($d);
}
}
function writeAmf3Xml($d)
{
$d = preg_replace('/\>(\n|\r|\r\n| |\t)*\</','><',trim($d));
$this->writeByte(0x07);
$this->writeAmf3String($d);
}
function writeAmf3ByteArray($d)
{
$this->writeByte(0x0C);
$this->writeAmf3String($d, true);
}
function writeAmf3Object($d)
{
//Write the object tag
$this->outBuffer .= "\12";
if( ($key = patched_array_search($d, $this->storedObjects, TRUE)) === FALSE && $key === FALSE)
{
if(count($this->storedObjects) < MAX_STORED_OBJECTS)
{
$this->storedObjects[] = & $d;
}
$this->storedDefinitions++;
//Type the object as an array
if(AMFPHP_PHP5)
{
$obj = $d;
}
else
{
$obj = (array) $d;
}
$realObj = array();
foreach($obj as $key => $val)
{
if($key[0] != "\0" && $key != '_explicitType') //Don't show private members
{
$realObj[$key] = $val;
}
}
//Type this as a dynamic object
$this->outBuffer .= "\13";
$classname = $this->getClassName($d);
$this->writeAmf3String($classname);
foreach($realObj as $key => $val)
{
$this->writeAmf3String($key);
$this->writeAmf3Data($val);
}
//Now we close the open object
$this->outBuffer .= "\1";
}
else
{
$handle = $key << 1;
$this->writeAmf3Int($handle);
}
}
}
?>