phpでAMF0

Flashと通信を行う場合の方式の1つにAMFというものがあります。
現在AMFにはAMF0とAMF3の2つの実装があります。
AMF3はActionScript3にあわせて作られた新しい実装です。
色々と改善されているのですが、処理速度的にはAMF0のほうが速くなるのではないかと思います。

PHPのAMFのライブラリは複数ありますが、一から作ってみました。
全ての仕様を満たしてはいませんしx86のCPUに依存していたりしますが、十分使えるのではないかと思います。
PHPは、メソッド呼び出しが異常に遅いので、static関数で極力関数呼び出しをしないように、
ただ、どうしても必要な個所だけは再帰呼び出しするようにしてあります。
AMFでは、参照もシリアライズできるので、その辺を使わないことが決まっていれば
さらに高速化可能です。indexとobjectsを消せばOKです。
また、型付きオブジェクトのシリアライズもいらなければ消せばより高速に動作するでしょう。
サーバに負荷がかかる場合は、削ればいいわけです。

コメントの部分は、なくても動作しますが、理解を深める場合にはあったほうがよいので
残してあります。w関数をwrite....関数を使うように書き換えればより分かりやすいようになるでしょうが、
処理速度は落ちます。また、未完成な個所もあります。

<?php
class AMF0 {
    static $classNames = array(
        "Test"=>"T",
    );
    static $index = 0;
    static $objects = array();
    static function write($v) {
        self::$index=0;
        self::$objects = array();
        return self::w($v);
    }
    static function w($v) {
        if (is_string($v)) {
            $key = strlen($v);
            if ($key < 65536) return "\x02" . pack("n", $key) . $v;
            else              return "\x0C" . pack("l", $key) . $v;
        }
        if (is_array($v)) {
            self::$index++;

            $rc = false;
            foreach ($v as $key => &$value) {
                if (!is_numeric($key)) {
                    $rc = true;
                    break;
                }
            }
            if ($rc) {
                // object
                $rc = "\x03";
                foreach ($v as $key => &$value) {
                    $rc .= pack("n", strlen($key)) . $key;// utf-8
                    $rc .= self::w($value);
                }
                return $rc . "\x00\x00\x09";// utf-8-empty 2byte + object end
            }
            // strict-array
            $rc = "\x0A" . pack("N", count($v));
            foreach ($v as &$value) {
                $rc .= self::w($value);
            }
            return $rc;
        }
        if (is_int($v) || is_float($v)) return "\x00" . strrev(pack("d", $v));
        if (is_bool($v)) return "\x01" . pack("c", $v);
        if (is_null($v)) return "\x05";// null

        if (is_object($v)) {
            $hash = spl_object_hash($v);
            if(isset(self::$objects[$hash])) 
                return "\x07".self::$objects[$hash];
            self::$objects[$hash] = pack("n", self::$index++);
            $value = get_class($v);
            if (isset(self::$classNames[$value])) {
                $value = self::$classNames[$value];
                $rc = "\x10" . pack("n", strlen($value)) . $value;

                foreach ($v as $key => &$value) {
                    $rc .= pack("n", strlen($key)) . $key;// utf-8
                    $rc .= self::w($value);
                }

                return $rc . "\x00\x00\x09";// utf-8-empty 2byte + object end
            }
            // if ($v instanceof DateTime) return "\x0B";// date todo support date

            // object
            $rc = "\x03";
            foreach ($v as $key => &$value) {
                $rc .= pack("n", strlen($key)) . $key;// utf-8
                $rc .= self::w($value);
            }
            return $rc . "\x00\x00\x09";// utf-8-empty 2byte + object end
        }
        return "\x0D";// unsupported
    }
/*
    static function writeNumber($v) {
        return "\x00" . strrev(pack("d", $v));
    }
    static function writeBoolean($v) {
        return "\x01" . pack("c", $v);
    }
    static function writeString($v) {
        return "\x02" . pack("n", strlen($v)).$v;
    }
    static function writeObject($v) {
        $rc = "\x03";
        foreach ($v as $key => &$value) {
            $rc .= pack("n", strlen($key)) . $key;// utf-8
            $rc .= self::w($value);
        }
        return $rc . "\x00\x00\x09";// utf-8-empty 2byte + object end
    }
    static function writeMobieClip($v) {
        return "\x04";
    }
    static function writeNull($v) {
        return "\x05";
    }
    static function writeUndefined($v) {
        return "\x06";
    }
    static function writeReference($v) {
        return "\x07";
    }

    static function writeArray($v) {
        $rc = "\x08";
        $rc .= pack("N", count($v));
        foreach($v as $key => $value) {
            $rc .= pack("n", strlen($key)).$key;
            $rc .= self::w($value);
        }
        return $rc . "\x00\x00\x09";// utf-8-empty 2byte + object end
    }
    static function writeObjectEnd($v) {
        return "\x09";
    }
    static function writeStrictArray($v) {
        $rc = "\x0A";
        $rc .= pack("N", count($v));
        foreach($v as $value) {
            $rc .= self::w($value);
        }
        return $rc;
    }
    static function writeDate($v) {
        return "\x0B";
    }
    static function writeLongString($v) {
        return "\x0C" . pack("l", strlen($v)).$v;
    }
    static function writeUnsupported($v) {
        return "\x0D";
    }
    static function writeRecordset($v) {
        return "\x0E";//リザーブ、非サポート
    }
    static function writeXml($v) {
        return "\x0F";
    }
    static function writeTypedObject($v) {
        $key = get_class($v);
        $rc = "\x10" . pack("n", strlen($key)) . $key;
        foreach ($v as $key => &$value) {
            $rc .= pack("n", strlen($key)) . $key;// utf-8
            $rc .= self::w($value);
        }
        return $rc . "\x00\x00\x09";// utf-8-empty 2byte + object end
    }
*/

}

//echo AMF0::writeNumber(1.2345);
//echo AMF0::writeBoolean(true);
//echo AMF0::writeBoolean(false);
//echo AMF0::writeString("俺");
//echo AMF0::writeStrictArray(array(1,2,3,4,5));

$p = new Test(0,0);
$n = new Test(20,20,$p);
echo AMF0::write(array("a"=>new Test(1,2,$p,$n),"b"=>5,"c"=>"auaua"));
//echo AMF0::writeArray(Array(1,2,"a"=>"b","ddd"=>"ccc"));

class Test{
    function Test($x, $y,$p=null,$n=null) {
        $this->x = $x;
        $this->y = $y;
        $this->p = $p;
        $this->n = $n;
    }
}
?>

AMFのオブジェクトを型付きで用いる場合は、registerClassAliasを使ってクラス名を登録する必要があります。
これが分からなくて結構ハマりました。

amf.as

package {
    import flash.utils.*;
    import flash.net.*;
    import flash.display.*;
    import flash.events.*;
    import flash.text.*;
    public class amf extends Sprite{
        private var loader:URLLoader = new URLLoader();
        public var loadf:Boolean = false;
        private var bytes:ByteArray;
        private var tf:TextField = new TextField();
        public function amf() {
			registerClassAlias("T", Test);
			tf.width=500;
			tf.height=500;
			bytes = new ByteArray();
			bytes.objectEncoding = ObjectEncoding.AMF0;
			bytes.writeObject(new Test());
			for(var i:int=0;i<bytes.length;i++) {
			tf.appendText(bytes[i]+" ");
			}
			tf.appendText("\n");
			addChild(tf);
			loader.dataFormat = URLLoaderDataFormat.BINARY;
			loader.addEventListener (Event.COMPLETE, onLoaded);
			load("amf.php");
        }
        public function load(url:String):void {
            if(loadf == true) return;
            loadf = true;
            var urlRequest:URLRequest = new URLRequest(url);
            loader.load(urlRequest);
        }
        private function onLoaded(event:Event):void {
            loadf = false;
            bytes = ByteArray(loader.data);
            //bytes.endian = Endian.LITTLE_ENDIAN;// または Endian.LITTLE_ENDIAN
            bytes.objectEncoding = ObjectEncoding.AMF0;
            try {
	            tf.appendText("start\n");
	            var o:* = bytes.readObject();
	            tf.appendText("readOK\n");
	            tf.appendText("aaa"+o+"\n");
	            tf.appendText("aaa"+outputObject(o));
				var t:Test = Test(o);
				tf.appendText("ok"+t.x+" "+t.y);
			} catch (e:Object){
				tf.appendText("err"+e);
			}
        }
        private function outputObject(o:*):String {
        	if(o is Number || o is Boolean || o is String || o is int || o is Test) return o.toString();
        	var s:Array = [];
        	for(var i:String in o) {
        		s.push( i+":"+outputObject(o[i]));
        	}
        	return "{"+s.join(",")+"}";
        }
    }
}

class Test {
	public var x:Number;
	public var y:Number;
	public var p:Test;
	public var n:Test;
	public function toString():String {
		return "Test("+x+","+y+","+p+","+n+")";
	}
}