Flex+PHPでアプリケーションを開発する時、サーバとのデータ通信部分の実装は手間がかかるものです。しかし、CakePHPを使えば簡単に実装することができます。本記事では、アドレス帳サンプルをもとに、CakePHPを使ったデータ通信方法を解説します。

CakePHPについて

CakePHPは、Ruby On Railsの影響を強く受けたフレームワークで、MVCやActiveRecord形式のO/Rマッピングといった特徴を持っています。CakePHPを使えば効率的に作業を進めることができ、短期間での開発が可能です。

FlexとPHPを用いたデータ通信方法には、FlexのRemoteObjectによる通信と、REST(XML)による通信の2通りがあります。RemoteObject通信では、AMF3(Action Message Format3)を使用することができます。CakePHPには、通信データをAMF形式で出力するためのライブラリがあります。以下は、その代表的なライブラリです。

  • SabreAMF
  • CakeSWXPHP
  • CpAMF
  • CakeAMFPHP

本記事では、この中から簡単に実装可能なCakeSWXPHPを使用して説明します。

CakePHPとCakeSWXPHPのインストール

CakePHPは最新バージョン1.3.xを使用します。CakePHPのインストール方法については、CakePHPのサイトを参考にして下さい。

http://www.cakephp.jp/

CakePHPのインストールが完了したら、続いてCakeSWXPHPをインストールします。以下のサイトからCakeSWXPHPをダウンロードしてください。CakeSWXPHPは、CakePHPのバージョンごとに異なるダウンロードファイル(1.1.x向けと1.2.x向け)が用意されています。本記事ではCakePHP 1.3.xですが、1.2.x向けのCakeSWXPHPを使用することができます。

http://blog.aboutme.be/cakeswxphp/

ダウンロードしたCakeSWXPHPを解凍すると、フォルダ内に以下のディレクトリがあります。

app/
vendors/

これらのディレクトリを、CakePHPのappディレクトリとvendersディレクトリにそれぞれ上書きします。

次に、このままでは日本語を扱った際に文字化けが起きるので、文字コードに関する記述を変更します。「app/webroot/amf.php」ファイルの34行目付近に、PHPの文字コードとMySQLの文字コードに関する記述があります。今回、PHPの文字コードとMySQLの文字コードをUTF-8にしているので、以下のように変更します。

<変更前>

$gateway->setCharsetHandler('utf8_decode','ISO-8859-1' ,'ISO-8859-1');

<変更後>

$gateway->setCharsetHandler('utf8_decode','UTF-8' ,'UTF-8');

アドレス帳サンプルの概要

CakePHPとCakeSWSPHPを用いたシステムのサンプルとして、アドレス帳を作成します。
FlexとPHPでデータ通信を行う場合、「データベースからデータを取得して、XMLまたはAMF3で渡す」といったPHPプログラムの作成が必要になります。データ表示部分をFlexで作成し、CakePHPでデータ取得用のコントローラとモデルを作成します。

以下は、Flexで作成するアドレス帳の表示部分です。左カラムがAMFデータ用、右カラムがXMLデータ用で、各カラムの下にある[データ取得]ボタン をクリックすると各データを取得します。AMFデータの取得にはRemoteObjectを使用し、XMLデータの取得にはHTTPServiceを使用 します。なお、取得したデータはDataGridで表示させます。

アドレス帳のデータの作成

データベースサーバはMySQLを使用し、アクセス権限を以下のように設定します。

ユーザ名 root
パスワード 123456
データベース amfsample

アドレス帳のデータを格納するテーブル「address」を作成してデータを格納します。

CREATE TABLE IF NOT EXISTS `address` ( `id` int(11) NOT NULL, `name` varchar(150) collate utf8_unicode_ci NOT NULL, `address` varchar(255) collate utf8_unicode_ci NOT NULL, `tel` varchar(13) collate utf8_unicode_ci NOT NULL, `remarks` text collate utf8_unicode_ci NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

今回作成するサンプルはデータの取得部分のみですので、テスト用データはサンプル内のAMFSample/SQL/address.sqlにあるデータ挿入文を実行してください。

CakePHP側のコード作成

まず、先ほど作成したデータベースへアクセスするための設定をapp/config/database.php に記述します。

class DATABASE_CONFIG { var $default = array( 'driver' => 'mysql', //使用するデータベースサーバ 'persistent' => false, //持続的接続 'host' => 'localhost', //データベースサーバを設置しているホスト名 'login' => 'root', //データベースサーバにアクセスするユーザ名 'password' => '123456', //データベースサーバにアクセスするパスワード 'database' => 'amfsample ',//使用するデータベース名 'prefix' => '', //接頭語 'encoding' => 'utf8' //文字エンコーディング ); }

作成したデータベースのテーブルに対してモデルとコントローラを作成して、それぞれ指定されたディレクトリに置きます。

モデル app/models
コントローラ app/controllers
ビュー app/views

モデルの作成

以下は、モデルのPHPコードです。

app/models/address.php

class AddressesController extends AppController { var $name = 'Addresses'; // コントローラ名 var $helpers = array( 'Xml'); // ヘルパー /** * 検索結果取得用メソッド * @param $page 取得するページ * @param $count 取得件数 * @return 検索結果の連想配列 */ function get_amf( $page = 0 , $count = 20 ) { return $this -> Address -> find( 'all' , array('limit'=> $count , 'offset' => $count*$page ) ); } /** * 検索結果取得用メソッド(XMLで出力) * @param $page 取得するページ * @param $count 取得件数 */ function get_xml( $page = 0 , $count = 20 ) { $this->set( 'data',$this -> Address -> find( 'all' , array('limit'=> $count , 'offset' => $count*$page ) ) ); $this -> layout = 'xml'; } }

【AMF用】
コントローラの中で、以下の部分がAMF用のコードです。ActionScript から直接アクセスすることができるCakePHPのコントローラ内のメソッドを定義しています。ActionScript は、このメソッドの戻り値を取得できます。

/** * 検索結果取得用メソッド * @param $page 取得するページ * @param $count 取得件数 * @return 検索結果の連想配列 */ function get_amf( $page = 0 , $count = 20 ) { return $this -> Address -> find( 'all' , array('limit'=> $count , 'offset' => $count*$page ) ); }

【XML用】
コントローラの中で、以下の部分がXML用のコードです。CakePHP には、ビューの処理を効率化するために「ヘルパー」という機能があります。

/** * 検索結果取得用メソッド(XMLで出力) * @param $page 取得するページ * @param $count 取得件数 */ function get_xml( $page = 0 , $count = 20 ) { $this->set( 'data',$this -> Address -> find( 'all' , array('limit'=> $count , 'offset' => $count*$page ) ) ); $this -> layout = 'xml'; }

ビューのdata 変数に検索結果を割り当てて、出力する際に使用するレイアウト名(ここでは「xml」)を定義します。取得した結果をXMLに変換するには、XMLヘルパーを設定します。
XMLヘルパーを使用するには、コントローラ内の$helpersプロパティに以下のように記述します。

var $helpers = array( 'Xml');

XML用レイアウトファイルの作成

コントローラで「$this -> layout = 'xml';」とレイアウト名を「xml 」と定義したので、xml.ctpファイルをレイアウトディレクトリに作成します。

app/views/layouts/xml.ctp

<?php echo $this->Xml->header(); // XML ヘッダー出力 ?> <?php echo $content_for_layout; // コンテンツ出力 ?>

ビューの作成

コントローラのメソッドに対応するディレクトリをビューディレクトりに作成します。address コントローラの get_xml メソッドのビューを作成します。

app/views/addresses/get_xml.ctp

<addresses> <?php //data変数をXMLヘルパーのserializeメソッドで出力 echo $xml->serialize($data); ?> </addresses>

Flex側のコード作成

次にFlexのコードを作成します。Sample.mxml、AmfSample.mxml、XmlSample.mxmlの3つのファイルを作成します。Sample.mxmlがアプリケーションファイルで、AMF用サンプルのAmfSample.mxmlとXML用サンプルのXmlSample.mxmlはコンポーネント化します。

Sample.mxml

以下は、Sample.mxmlのMXMLコードです。

FlexSample/src/Sample.mxml

<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600" xmlns:ns1="*"> <ns1:AmfSample x="10" y="10" /> <ns1:XmlSample x="555" y="10" /> </s:Application>

AmfSample.mxml

以下は、AmfSample.mxmlのMXMLコードです。

FlexSample/src/AmfSample.mxml

<?xml version="1.0" encoding="utf-8"?> <s:Group xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" width="600" height="600"> <s:layout> <s:BasicLayout/> </s:layout> <fx:Declarations> <!-- 非ビジュアルエレメント (サービス、値オブジェクトなど) をここに配置 --> <s:RemoteObject id="remote" endpoint="http://localhost/AMFSample/amf.php" destination="cakeSWXphp" source="AddressesController" result="handleResult(event)" /> </fx:Declarations> <s:Label x="19" y="7" text="AMFサンプル" fontSize="20"/> <mx:DataGrid x="19" y="30" id="adg1" dataProvider="{addresses}" height="500"> <mx:columns> <mx:DataGridColumn headerText="列 1" dataField="Address.id"/> <mx:DataGridColumn headerText="列 2" dataField="Address.name"/> <mx:DataGridColumn headerText="列 3" dataField="Address.address"/> <mx:DataGridColumn headerText="列 4" dataField="Address.tel"/> <mx:DataGridColumn headerText="列 5" dataField="Address.remarks"/> </mx:columns> </mx:DataGrid> <s:Button x="441" y="550" label="データ取得" id="btn" click="onClickBtn()"/> <s:Label x="6" y="559" text="取得時間"/> <s:Label x="73" y="560" text="{time}ミリ秒"/> <s:TextInput x="305" y="549" id="sCount" restrict="[0-9]" imeMode="ALPHANUMERIC_HALF"/> <s:Label x="245" y="559" text="取得件数"/> <fx:Script> <![CDATA[ import flash.utils.getTimer; import mx.collections.ArrayCollection; import mx.controls.Alert; import mx.rpc.events.ResultEvent; [Bindable] private var addresses:Array; [Bindable] private var time:Number; private var sTime:Number; /** * 結果の取得 */ private function handleResult( event:ResultEvent ):void{ time = getTimer() - sTime; addresses = event.result as Array; } /** * ボタンクリック時の動作を定義 */ private function onClickBtn():void{ sTime = getTimer(); //時間の計測 var count:int; if( sCount.text != ''){ count = parseInt( sCount.text ); //データの取得件数を設定 } else{ count = 20; } remote.get_amf( 0,count ); //リモートのメソッドの実行 } ]]> </fx:Script> </s:Group>

コードのポイントを解説していきましょう。RemoteObjectを使用すると、サーバ側のメソッドを直接コールすることができます。RemoteObjectを使用するため、MXMLで以下のように記述します。

Endpoint CakePHPのディレクトリ/webroot/amf.phpのURLを指定します。
Destination 何でも良いので、記述が必要です。
Source コントローラのファイル名を記述します。
Result 結果を受け取った時に実行するメソッドを記述します。

ボタンクリック時に、「remote.get_amf(0,20)」のようにCakePHPに記述したメソッドをそのまま実行できます。

private function onClickBtn():void{ sTime = getTimer(); //時間の計測 var count:int; if( sCount.text != ''){ count = parseInt( sCount.text ); //データの取得件数を設定 } else{ count = 20; } remote.get_amf( 0,count ); //リモートのメソッドの実行 }

正常に結果が返るとresultイベントが発生して、指定されたメソッド(handleResult)を実行します。

[Bindable] private var addresses:Array; … private function handleResult( event:ResultEvent ):void{ time = getTimer() - sTime; addresses = event.result as Array; }

取得したアドレスデータを表示するため、DataGridコンポーネントを使用します。

<mx:DataGrid x="19" y="30" id="adg1" dataProvider="{addresses}" height="500"> <mx:columns> <mx:DataGridColumn headerText="列 1" dataField="Address.id"/> <mx:DataGridColumn headerText="列 2" dataField="Address.name"/> <mx:DataGridColumn headerText="列 3" dataField="Address.address"/> <mx:DataGridColumn headerText="列 4" dataField="Address.tel"/> <mx:DataGridColumn headerText="列 5" dataField="Address.remarks"/> </mx:columns> </mx:DataGrid>

XmlSample.mxml

以下は、XmlSample.mxmlのMXMLコードです。

FlexSample/src/XmlSample.mxml

<?xml version="1.0" encoding="utf-8"?> <s:Group xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" width="600" height="600"> <s:layout> <s:BasicLayout/> </s:layout> <fx:Declarations> <!-- 非ビジュアルエレメント (サービス、値オブジェクトなど) をここに配置 --> <s:HTTPService id="service" result="handleResult(event)" url="http://localhost/AMFSample/addresses/get_xml/{page}/{viewCount}" resultFormat="e4x"> </s:HTTPService> </fx:Declarations> <s:Label x="19" y="7" text="XMLサンプル" fontSize="20"/> <mx:DataGrid x="19" y="30" id="adg1" dataProvider="{addresses}" height="500"> <mx:columns> <mx:DataGridColumn headerText="列 1" dataField="@id"/> <mx:DataGridColumn headerText="列 2" dataField="@name"/> <mx:DataGridColumn headerText="列 3" dataField="@address"/> <mx:DataGridColumn headerText="列 4" dataField="@tel"/> <mx:DataGridColumn headerText="列 5" dataField="@remarks"/> </mx:columns> </mx:DataGrid> <s:Button x="441" y="550" label="データ取得" id="btn" click="onClickBtn()"/> <s:Label x="6" y="559" text="取得時間"/> <s:Label x="73" y="560" text="{time}ミリ秒"/> <s:TextInput x="305" y="549" id="sCount" restrict="[0-9]" imeMode="ALPHANUMERIC_HALF"/> <s:Label x="245" y="559" text="取得件数"/> <fx:Script> <![CDATA[ import flash.utils.getTimer; import mx.collections.ArrayCollection; import mx.controls.Alert; import mx.rpc.events.ResultEvent; //アドレス一覧のデータ [Bindable] private var addresses:XMLList; //表示件数 [Bindable] private var viewCount:int = 20; //表示ページ番号 [Bindable] private var page:int = 0; //計測時間 [Bindable] private var time:Number; //開始時間 private var sTime:Number; /** * 結果の取得 */ private function handleResult( event:ResultEvent ):void{ time = getTimer() - sTime; //時間の計測 //取得したデータを設定 addresses = (event.result as XML).children(); } /** * ボタンクリック時の動作を定義 */ private function onClickBtn():void{ sTime = getTimer(); // 時間の計測 if( sCount.text != "" ){ //取得件数を設定 viewCount = parseInt( sCount.text ); } service.send(); } ]]> </fx:Script> </s:Group>

コードのポイントを解説していきましょう。HttpServiceを使用して、XMLを取得する場合のコードを見ていきます。

<s:HTTPService id="service" result="handleResult(event)" url="http://localhost/AMFSample/addresses/get_xml/{page}/{viewCount}" resultFormat="e4x"> </s:HTTPService>

RemoteObject と同様に、ボタンクリック時にHTTPServiceのSendメソッドでリクエストが送信されます。

private function onClickBtn():void{ sTime = getTimer(); // 時間の計測 if( sCount.text != "" ){ //取得件数を設定 viewCount = parseInt( sCount.text ); } service.send(); }

正常に結果が返ると、resultイベントが発生し、指定されたメソッド(handleResult)を実行します。

[Bindable] private var addresses:XMLList; … private function handleResult( event:ResultEvent ):void{ time = getTimer() - sTime; //時間の計測 //取得したデータを設定 addresses = (event.result as XML).children(); }

RemoteObject と同様に、取得したアドレスデータを表示するため、DataGridコンポーネントを使用します。

<mx:DataGrid x="19" y="30" id="adg1" dataProvider="{addresses}" height="500"> <mx:columns> <mx:DataGridColumn headerText="列 1" dataField="Address.id"/> <mx:DataGridColumn headerText="列 2" dataField="Address.name"/> <mx:DataGridColumn headerText="列 3" dataField="Address.address"/> <mx:DataGridColumn headerText="列 4" dataField="Address.tel"/> <mx:DataGridColumn headerText="列 5" dataField="Address.remarks"/> </mx:columns> </mx:DataGrid>

EST(XML)とAMFの速度比較

アドレス帳のデータをHTTPServiceによるXMLを使用したREST通信と、RemoteObjectを使用したAMF3の通信の速度について比較してみましょう。10件、100件、1000件、10000件でそれぞれ10回接続した場合の平均値で比較することにします(Flexアプリケーションでリクエストを送信してから、結果を取得するまでのミリ秒数)。処理時間は、以下の環境で計測しています。

  • CakePHP 1.3.2
  • PHP 5.2.6
  • Flash Player 10.1
  • Flash Builder 4

速度比較結果

データ件数 AMF XML
10件の場合 235ミリ秒 215ミリ秒
100件の場合 276ミリ秒 265ミリ秒
1000件の場合 463ミリ秒 842ミリ秒
10000件の場合 2221ミリ秒 7391ミリ秒

結果を見ると、取得するデータが少ない場合、XMLの方が若干速いことが分かります。また、情報量が増えるにしたがってAMFの方が速いことが分かります。比較的データ量が少ない場合やデータ量が固定されている場合はXMLの方が有効で、データ量が多くなる場合やデータ構造がXMLでは複雑になる場合はAMFの方が有効だと思います。

最後に

本記事では、FlexとCakePHPによるデータ通信の方法について紹介しました。これによりCakePHP で開発されたWebアプリケーションを、Flexで実現する際にデータ通信方式をデータ量に応じて、選定できる基準になると思います。