flashとのAMF通信用CakePHPコンポーネント

こんにちは、中川です。
以前、「AMFPHPを試してみました」でphpとFlashの通信を試してみましたが、
今回はCakePHPを使ったFlashとAMFでのやり取りする方法を考えてみました。

CakePHPでAMF通信ができるものを探してみたところ、
CakeAMFPHP」、
CakeAMF」、
CakeSWXPHP
など、いろいろ見つかりました。

一通り試してみましたが、設置が面倒であったりうまく動かなかったり
(私のやり方が悪いと思いますが、)どうもしっくりきませんでした。

もっとCakePHPから使いやすそうなものがないか探してみたところ、
SabreAMF」というライブラリを見つけました。
これを通常の使用方法で試したところ、非常に簡単にやり取りができましたので、
CakePHPからも使いやすいようにCakePHP用のコンポーネントを作ってみました。

※動作イメージ

構成は以下のようになります。


`-- myproject
    `-- app
        |-- controllers
        |   |-- amfs_controller.php(サンプルファイル)
        |   `-- components
        |       `-- sabre_amf.php(コンポーネント)
        `-- vendors
            |-- SabreAMF(解凍した中身を配置)
            `-- sabreamf_ini.php(PATH設定用ファイル)

まず、SabreAMFの最新ライブラリ(今回はSabreAMF-1.1.187)を http://code.google.com/p/sabreamf/downloads/list から
ダウンロードして、解凍しSabreAMFに改名して、


/PROJECT_DIR/app/vendors/SabreAMF 

に配置します。

そして、パス設定用のファイル


/PROJECT_DIR/app/vendors/sabreamf_ini.php 

を作成します。


<?php
//sabreamf_ini.php
define('SABREAMF_PATH', dirname(__FILE__));
set_include_path(SABREAMF_PATH . PATH_SEPARATOR . get_include_path());
?>

あとは今回作成したコンポーネント、sabre_amf.php を


/PROJECT_DIR/app/controllers/components/sabre_amf.php

に設置します。


<?php
//sabre_amf.php
vendor("sabreamf_ini");
vendor('SabreAMF/CallbackServer');
$_cakeController = null;
function amfCallBack($service, $method, $data) {
    global $_cakeController;
    $res = null;
    if ($_cakeController) {
    	if (strpos($method, "_") !== 0) {	// _(アンダーバー)で始まるmethodはエラー。
    	$_cakeController->amfData = $data;
    	if (method_exists($_cakeController, $method)) {
    		$res = $_cakeController->{$method}();
    	} else {
    		$res = "not found action.";
    	}
    	} else {
    		$res = "invalid method name.";
    	}
    }	else {
    	$res = "not found controller.";
    }
return $res;
}
class SabreAmfComponent extends Object {
function initialize( &$controller) {
global $_cakeController;
$_cakeController = $controller;
$controller->isAmf = true;	// default true
}
function startup( &$controller) {
if ($controller->isAmf) {
Configure::write('debug', 0);
$controller->autoRender = false;
    $server = new SabreAMF_CallbackServer();
    $server->onInvokeService = 'amfCallBack';
    $server->exec();
exit;		//ここでexitしてるので、amf の 場合はほかのactionは呼べないはず。。。
}
}
function beforeRender( &$controller) {
if ($controller->isAmf) {
exit;	//	念のため
}
}
}
?>

これで準備完了です。
AMFを使用したいControllerで、


var $components = array("SabreAmf");

とすれば、SabreAMFを利用してFlashとAMFでやりとりができるようになります。

以下、簡単な、文字列と、配列のやり取りのサンプル用Controllerです。


<?php
class AmfsController extends AppController {
var $name = 'Amfs';
var $uses = array();
var $components = array("SabreAmf");
function beforeFilter() {
// ここでamf出力ではないactionを設定できる。
// 全部amf出力の場合は何も設定いらない。
// この場合は「normal」のみ通常アクセスできる。
// ほかのactionは、URLからは実行されないはず。。。
if ($this->action == "normal") {
$this->isAmf = false;
Configure::write('debug', 2);
$this->autoRender = true;
}
}
// indexアクションは通常出力に使わないほうがいい。
// beforeFilterで除外すると、初期アクセスがamfから指定ない場合、
// リモートで呼び出されたときにエラーでちゃう
function index() {
return "index";
}
//これは通常表示できるアクション
function normal() {
$this->set("hoge", date("Y-m-d H:i:s"));
}
//文字列返却
function getstr() {
$testname = $this->amfData[0];	// flashから送られてきた値はamfDataにある。しかし、キーが消えてるよ。。。
return date("Y-m-d H:i:s") . " こんにちは " . $testname;	//各action で return した値が flashに返る。
}
//配列返却
function getarr() {
$res_list = array();
for($i = 0; $i < 10; $i++) {
$res = array(
"col1" => $i,
"col2" => rand(1, 1000),
);
$res_list[] = $res;
}
$this->log(print_R($res_list, true), LOG_ERROR);
return $res_list;
}
}
?>

あとは、このコントローラにFlashからリクエストのサンプルです。

■RemoteTest.mxml(RemoteTest.swf)


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:Script>
<![CDATA[
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.controls.Alert;
import mx.utils.ArrayUtil;
[Bindable]
private var myDataItem:Array = [];
private function resultHandler(evt:ResultEvent):void {
myDataItem = ArrayUtil.toArray(evt.result);
}
private function faultHandler(evt:FaultEvent):void {
Alert.show("Fault: " + evt.fault + " Msg: " + evt.message);
}
]]>
</mx:Script>
<!--
remoting-config.xmlをちゃんと設定したほうがいいのかな?
このサンプルでは、xmlを用意しないで、endpoint指定でやってます。
endpoint="{'http://{server.name}:{server.port}/amfs/'}" と書けば、
flash設置サーバの/amfs/と通信します。
以下のようにendpoint(cakeのcontroller呼び出し用URL)さえ指定しておけば
source は今回の場合は適当でいいです。というのも、sevice名として
サーバで受け取りますが、endpointのcakeのcontrollerの
メソッドしか呼べないようにしているので送信しているが使っていない。
また、destinationも適当でいいですが指定しないとエラーでます。
-->
<!-- 文字列読み込み例 -->
<mx:RemoteObject id="ro" 
endpoint="{'http://{server.name}:{server.port}/amfs/'}" 
destination="MyAmfs"
source="Amfs"
result="myLabel.text = event.result.toString()" 
fault="faultHandler(event)">
<mx:method name="getstr">
<mx:arguments>
<testname>hoge</testname>
</mx:arguments>
</mx:method>
</mx:RemoteObject>
<mx:Label text="string_sample" id="myLabel" />
<mx:Button label="string load test" click="ro.getstr.send()"  x="24" y="42"/>
<!-- ro.●●.send() でcake側のアクションを呼べる -->
<mx:Spacer height="2" width="200"/>
<!-- 配列読み込み例 -->
<mx:RemoteObject id="ro2" 
endpoint="{'http://{server.name}:{server.port}/amfs/'}" 
destination="MyAmfs"
source="Amfs"
result="resultHandler(event)" 
fault="faultHandler(event)">
</mx:RemoteObject>
<mx:Button label="array load test" click="ro2.getarr.send()"  x="24" y="42"/>
<mx:DataGrid id="myData" dataProvider="{myDataItem}">
<mx:columns>
<mx:DataGridColumn headerText="列 1" dataField="col1"/>
<mx:DataGridColumn headerText="列 2" dataField="col2"/>
</mx:columns>
</mx:DataGrid>
</mx:Application>

上記サンプルでは、RemoteObjectを使用していますが、
NetConnectionを使った場合は以下のような感じで通信できると思います。
(以前のAMFPHPの記事も参照)


var con:NetConnection = new NetConnection();
con.connect(GATEWAY_URL);
con.objectEncoding = ObjectEncoding.AMF3;
con.call("Amfs.getstr", new Responder(resultHandler, faultHandler));

※しっかりした、動作確認はできておりません。
使用される場合は自己責任でご自由にお使いください。

———————————————–
最近CakePHPの人気はすごいですねー。今週水曜CakePHPの勉強会ですが、
申し込みに間に合わず、満員で参加できませんでしたorz
いきたかったなぁ~。。。