I've now looked at the array handling code and there are possible improvements.
public function write_array_opt($d) {
// Arrays go onto the object_table if we haven't seen them before
// NOTE: strict equality will return true if two distinct arrays
// have identical keys and values...if we can find a way to check for
// a real reference, that would rule
// DISABLED FOR NOW because we don't want arrays to be artificially referencing each other
# $key = array_search($d, $this->object_table, TRUE);
# if ($key !== FALSE) {
if (FALSE) {
// we have already written an indentical array to the object table
// write a ref and be done with it
$ref = $key << 1; // low bit is zero to indicate ref, other bits are 29-bit variable int to specify index
$this->write_integer($ref);
return;
}
// not a ref, store reference to this object in object_table if there is room
if (count($this->object_table) < self::AMF_OBJECT_MAX_REFERENCES) {
# $this->object_table[] =& $d
$this->object_table[] = ''; // we have to store empty strings so that when
// we do add an object reference that the ref index
// will match what flash expects
}
// divide the array into two arrays, one for numeric keys, one for associative keys
# To be precise, this comment should be:
# divide the array into two parts, the dense part and the non-dense part.
# Note that
# The dense part consists of 0-based, ordered and contiguous keys: all numeric
# The non-dense part consists of keys that are not dense. They could start above 0,
# be unordered and/or non-contiguous: all may still be numeric.
$num = array();
# I kept using $assoc to make the rewrite more evident. See comment below code block on how
# to optimize further. When that is done, $assoc is no longer needed.
$assoc = array();
$array_is_dense = true;
$next_dense_key_would_be = 0;
foreach($d as $key => $val) {
# From this point, all values will go into $assoc
if (!$array_is_dense) {
$assoc[$key] = $val;
} else {
# AMF3 uses 28 bits (3,5 bytes) to encode the length of the dense array.
# PHP does not run on 16 bits systems so I suppose a PHP int is never
# less than 4 bytes and this is therefor safe to compare without bounds checking
if (is_int($key) && $key == $next_dense_key_would_be) {
$num[] = $val;
$next_dense_key_would_be += 1;
# The code you posted thew on: $contig_len > self::AMF_ARRAY_MAX_CONTIG_LENGTH
# But it is possible to send the first AMF_ARRAY_MAX_CONTIG_LENGTH elements as
# a dense array and the rest of it in the associative part. But I do not know if the spec
# prohibits this.
if ($next_dense_key_would_be > self::AMF_ARRAY_MAX_CONTIG_LENGTH) {
$array_is_dense = false;
# if the spec prohibits sending the "overflow" elements as an associative array,
# simply reinstate the throw here
# throw new Exception('Array contiguous length exceeded the maximum allowed of ' . self::AMF_ARRAY_MAX_CONTIG_LENGTH);
}
} else {
$array_is_dense = false;
$assoc[$key] = $val;
}
}
}
// write the length of the contiguous part to the buffer
// as length packed together with the value/reference flag bit
// living in the lowest bit of a variable length integer
# $len_val_ref = $contig_len << 1 | 0x01;
# now becomes either
# $len_val_ref = count($num) << 1 | 0x01;
# or
$len_val_ref = ($next_dense_key_would_be - 1) << 1 | 0x01;
$this->write_integer($len_val_ref);
// output the key/value pairs
// e.g., arr['AB'] = 1
// first the key, a string:
// 00000101 - first bytes indicate key length, here it says length is 2, low bit is 'val' flag
// 01000001 - UTF code for upper case A
// 01000010 - UTF code for upper case B
// next, the value
// 00000100 - first byte is type indicator, here an integer
// 01111111 - the value of the integer, here 127
# I have changed the order in which the non-dense part is sent from the original code. The new
# ordering brings it in-line with the spec (as I understand it). Your original code sent, in order
# - numeric keys and their values, from the non-dense part of the array
# - non-numeric keys and their values,
# - the dense part of the array
/*
// first, the non-contiguous numeric indices
foreach($num as $key => $val) {
if (($key < 0) || ($key > $contig_len)) { // NOTE that $num[$contig_len] should never contain a value
// as it is either greater than the array length for
// contiguous arrays and for non-contiguous arrays, it
// is the index of the lowest empty slot
$this->write_string($key);
$this->serialize_r($val);
}
}
// then the associative values
foreach($assoc as $key => $val) {
$this->write_string($key);
$this->serialize_r($val);
}
*/
# I do not re-order the associative array
foreach ($assoc as $key => $val) {
$this->write_string($key);
$this->serialize_r($val);
}
// after name-value pairs are finished, output an empty string
$this->write_string('');
// now output just the values of the contiguous part of the num array (if any);
for($i = 0; $i < $next_dense_key_would_be; $i++) {
$this->serialize_r($num[$i]);
}
}
It is now rather easy to optimize away the remaining unnecessary looping. As soon as $is_dense is set to false, write the length of the dense array. Then replace the part inside the initial foreach loop that assigns to $assoc by the part that loops over $assoc to write its key/values, so that those key/values are written on the fly. Finally, outside the initial foreach-loop, first write the empty string delimiter, then loop over $num to write the dense part.