使用AMFPHP2.0

The AMFPHP at this moment is different from the discussion in <PHP & Flash Bible> ; its home page is:


http://www.silexlabs.org/amfphp/


Installation


Firstly, put the folder inside the zip archive under webroot folder, namely:/xampp/htdocs/


And request the Flash example: http://192.168.0.135/amfphp-2.0/Examples/Flash/Pizza/AmfphpFlashPizzaExample.html

It works.


Then access the so-called Gatewayhttp://192.168.0.135/amfphp-2.0/Amfphp/


Would see: amfphp-services-page




OK, so far so good.


Creating My Test Case


Open the sample actionscript code:


/amfphp-2.0/Examples/Flash/Pizza/AmfphpFlashPizzaExample.as:


package  {
	import flash.display.MovieClip;
	import flash.display.SimpleButton;
	import flash.text.TextField;
	import flash.net.NetConnection;
	import flash.events.MouseEvent;
	import flash.net.Responder;
	
	public class AmfphpFlashPizzaExample extends MovieClip {
		
		public var getPizzaBtn:SimpleButton;
		public var resultTxt:TextField;
		public var warningTxt:TextField;
		
		private var _netConnection:NetConnection;
		
		public function AmfphpFlashPizzaExample() {
			getPizzaBtn.addEventListener(MouseEvent.CLICK, getPizzaBtnClickHandler);
			_netConnection = new NetConnection();
		}
		
		private function getPizzaBtnClickHandler(event:MouseEvent):void{
			if(loaderInfo.url.indexOf("file://") == 0){
				warningTxt.text = "Please access this example through a server, and not through the file system, as the gateway is set as a relative url. Alternatively you can change the url in the class.";
			}else{
				_netConnection.connect("../../Php/");
				_netConnection.call("PizzaService/getPizza", new Responder(handleResult, null));
			}
		}
		
		
		private function handleResult(result:Object):void{
			resultTxt.text = result.toString();
		}		

	}
	
}

and the PHP code which provide Pizza service: /amfphp-2.0/Examples/Php/ExampleServices/PizzaService.php:


<?php
/**
 *  This file is part of amfPHP
 *
 * LICENSE
 *
 * This source file is subject to the license that is bundled
 * with this package in the file license.txt.
 * @package Amfphp_Examples_ExampleService
 */

/**
 * an example service for the pizza examples
 * @package Amfphp_Examples_ExampleService
 * @author Ariel Sommeria-klein
 */
class PizzaService {
    public function getPizza(){
        return "margarita";
    }
}
?>


Actually, put AMF folder directly under web-root folder would not be common practice on most occasions, because we may need it work based on our project's utilities, so put in into my web project folder/htdocs/source-r14/preview/; and create a new flash program to access the/Amfphp/Services/ExampleService.php:


package  {

	import flash.display.MovieClip;
	import flash.net.NetConnection;
	import flash.net.Responder;
	
	public class amfphp extends MovieClip
	{

		private var _netConnection:NetConnection;
		
		public function amfphp() 
		{
			this._netConnection = new NetConnection();
			
			var $resp:Responder = new Responder(this.handleResult, this.handleError);
		
			this._netConnection.connect("http://192.168.0.135/source-r14/preview/amfphp-2.0/Amfphp/");
			this._netConnection.call("ExampleService/returnOneParam", $resp, "Happy Lunar New Year 2012");
		}
		
		private function handleResult($result:Object):void
		{
			returnTxt.text = $result.toString();
		}	
		
		private function handleError($result:Object):void
		{
			//trace("reach here");	// never reach here
			for (var $i in $result)
			{
				trace("[" + $i + "]\t" + $result[$i]);
			}
		}
	}
	
}

This code will access service ExampleService's method: returnOneParam, which receives a string and just return it exactly. This snippet also shows how to pass a parameter to the service from Flash.


So far, it works fine!!!


Indeed, we can just keep the folder amfphp-2.0/Amfphp/, and get rid of others, so move it to/htdocs/source-r14/preview/, and cut the path short


this._netConnection.connect("http://192.168.0.135/source-r14/preview/Amfphp/");

This would work nicely.


Creating My Services


Create a PHP file: eDirectoryService.php, under/Amfphp/Services/:


<?php
/*
 * Created on 2012/Jan/12
 *
 * To get Flash Player Version eDirectory connected with SQLite
 * @Author: Vincent Zhang
 */

class eDirectoryService 
{
	
	protected $URL = "C:/xampp/htdocs/source-r14/daily/eDirectory.sdb";
	
	private $_conn = null;
	
	private function connect()
	{
		if($this->_conn == null)
		{
			$this->_conn = new SQLite3($this->$URL);
		}
	}

	private function queryDB($SQL)
	{
	   
		$q = $this->_conn->query($SQL);

		if(!$q)
		{
			die($this->_conn->lastErrorMsg());
		}
		$r = array();

		while($l = $q->fetchArray(SQLITE3_ASSOC))
		{
			$r[] = $l;
		}

		return($r);
	}	
	
	private function disconnect()
	{
		if($this->_conn != null)
		{
			$this->_conn->close();
			$this->_conn = null;
		}
	}
	
	public function Preview_getSystemConfig()
	{
		$this->connect();
		$SQL = "SELECT value FROM sys_config WHERE id = 2";
		$r = $this->queryDB($SQL);
		return($r);	
    }

	public function Preview_getCompanys()
	{
		$this->connect();
		$SQL = "SELECT company.cmp_id, company.eng_name, "
				."company.chi_name, company.schi_name, "
				."company.amt_stroke, company.samt_stroke, "
				."company.logo, company.descp, company.is_def_font "
				."FROM company";
		$r = $this->queryDB($SQL);
		return($r);
	}
	
}

?>

Before using Flash to connect to this service, you can view it with browser via this URL: http://localhost/source-r14/preview/amfphp/


amfphp-service


And now change the actionscript code:


package  {

	import flash.display.MovieClip;
	import flash.net.NetConnection;
	import flash.net.Responder;
	
	public class amfphp extends MovieClip
	{
		private var _netConnection:NetConnection;
		
		public function amfphp() 
		{
			this._netConnection = new NetConnection();
			var $resp:Responder = new Responder(this.handleResult, this.handleError);
			this._netConnection.connect("http://192.168.0.135/source-r14/preview/Amfphp/");
			this._netConnection.call("eDirectoryService/Preview_getSystemConfig", $resp);
		}
		
		private function handleResult($result:Object):void
		{
			trace($result.length);
			trace($result[0]["value"]);
		}	
		
		private function handleError($result:Object):void
		{
			trace("rec");
			for (var $i in $result)
			{
				trace("[" + $i + "]\t" + $result[$i]);
			}
		}
	}
	
}

The result was:


result-sys-conf


Validation On Returned Data


Get NULL Data Cell


SQlite has some behavior that is abnormal, it accepts empty string from PHP for a NOT-ALLOW-NULL field. But with most GUI tools, you could hardly set a such field to empty string, so for the ALLOW-NULL fields, there might be two values:empty string or NULL, and the empty string is generally submitted by PHP. That might because different wares call SQLte API in different ways.


Here is the data preview from SQLite Spy:


null-in-sqlite


The pink color indicates NULL, and white color indicates empty string.


Now let's take a look at what will Actionscript get via AMFPHP services.


AS code:

package  {

	import flash.display.MovieClip;
	import flash.net.NetConnection;
	import flash.net.Responder;
	
	public class amfphp extends MovieClip
	{

		private var _netConnection:NetConnection;
		
		public function amfphp() 
		{
			this._netConnection = new NetConnection();
			
			var $resp:Responder = new Responder(this.handleResult, this.handleError);
			
			this._netConnection.connect("http://192.168.0.135/source-r14/preview/Amfphp/");
			
			this._netConnection.call("eDirectoryService/Preview_getCompanys", $resp);
		}
		
		private function handleResult($result:Object):void
		{
			
			trace($result.length);
			trace("3rd English name: " + $result[2]["eng_name"]);
			
			
			trace("2nd logo: " + $result[1]["logo"]);
			trace("3rd logo: " + $result[2]["logo"]);
			
			if("" == $result[1]["logo"])
			{
				trace("2nd item's logo value is empty string");
			}
			else if(null == $result[1]["logo"])
			{
				trace("2nd item's logo value is null");
			}
			
			if("" == $result[2]["logo"])
			{
				trace("3rd item's logo value is empty string");
			}
			else if(null == $result[2]["logo"])
			{
				trace("3rd item's logo value is null");
			}
			
			
		}	
		
		private function handleError($result:Object):void
		{
			trace("rec");
			for (var $i in $result)
			{
				trace("[" + $i + "]\t" + $result[$i]);
			}
		}
	}
	
}


And here goes the result:


null-received-as


So, after transferring, empty string would still be empty string, NULL would still be NULL. This result implies that the null data from AMFPHP should be treated as the same as accessing local SQLite with AIR.


Empty Dataset Returned


If the SQL query returns a empty data set, what will Flash get through AMF?


Now, add the following function definition to eDirectoryService.php:


public function Preview_getRoomByCompanyID($cmpy_id)
{
	$this->connect();
	$SQL = vsprintf("SELECT renting.room_id, room.ordering, floor.floor_name, floor.ordering AS floor_order,"
			."floor.floor_name || room.room_no AS room "
			."FROM renting INNER JOIN room ON renting.room_id = room.r_id "
			."INNER JOIN floor ON room.floor_id = floor.floor_id "
			."WHERE (renting.cmy_id = %d) ORDER BY renting.ordering ASC",
			array( $this->_conn->escapeString($cmpy_id) ) );
	$r = $this->queryDB($SQL);
	return($r);
}

And use the following as code to access this new method:


package  {
	
	import flash.display.MovieClip;
	import flash.net.NetConnection;
	import flash.net.Responder;
	
	public class emptySet extends MovieClip
	{
		private var _netConnection:NetConnection;
		
		public function emptySet() 
		{
			this.getCompanysDataThroughAMF();
		}

		private function getCompanysDataThroughAMF():void
		{
			this._netConnection = new NetConnection();
			var $resp:Responder = new Responder(this.handleResult, this.handleError);
			this._netConnection.connect("http://192.168.0.135/source-r14/preview/Amfphp/");
			this._netConnection.call("eDirectoryService/Preview_getRoomByCompanyID", $resp, "12");
		}
		
		private function handleResult($result:Object):void
		{
			if(null != $result)
			{
				
			}
			else if("" == $result)
			{
				trace("the returned dataset is empty string.");
			}
			else if(null == $result)
			{
				trace("the returned dataset is null!");
			}
			
			trace("result is: " + $result);
			
			// why length doesn't show, since its array
			for (var $prop:String in $result)
			{
				trace("[" + $prop +"]: " + $result);
			}
			
			if($result is Object)
			{
				trace("result is Object");
			}
			
			if($result is Array)
			{
				trace("result is Array");
			}
			
			trace("its length is: " + $result.length);
			
			if($result.length > 0)
			{
				for each(var $company in $result)
				{
					trace("***********************");
					for (var $attr:String in $company)
					{
						trace("[" + $attr +"]: " + $company[$attr]);
					}
				}
			}
			
		}	
		
		private function handleError($result:Object):void
		{
			trace("rec");
			for (var $i in $result)
			{
				trace("[" + $i + "]\t" + $result[$i]);
			}
		}


	}
	
}

If the company id is valid, then the output is:


result is: [object Object]
[0]: [object Object]
result is Object
result is Array
its length is: 1
***********************
[floor_order]: 9
[ordering]: 1
[room_id]: 66
[floor_name]: 9
[room]: 901-05

Otherwise, if the id is invalid, means there is no such company:


result is: 
result is Object
result is Array
its length is: 0

So, the received variable is Array type, and it is not null; but it has no element, because its length is 0. So here the validation would be different from the validation in case of local database -- SQLite shipped with AIR, the returned object would be null, if the data set is empty; but here, its an Array with 0 length. So before using it, should use

 

if($result.length > 0)


to check if the data is operable.



Performance Comparison


We have a table named company, it has 119 rows, and 13 columns. Previously, I wrote the same function that works with XML, and now we add the same function working on XML into this test Flash program, and compare the two methods:


package  {

	import flash.display.MovieClip;
	import flash.net.NetConnection;
	import flash.net.Responder;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLVariables;
	import flash.net.URLRequestMethod;
	
	public class amfphp extends MovieClip
	{

		private var _netConnection:NetConnection;
		
		private var _start:Number;
		private var _end:Number;
		
		public function amfphp() 
		{
			//this.getCompanysDataThroughAMF();
			this.getCompanysDataThroughXML();
		}
		
		private function getCompanysDataThroughAMF():void
		{
			this._netConnection = new NetConnection();
			var $resp:Responder = new Responder(this.handleResult, this.handleError);
			this._netConnection.connect("http://192.168.0.135/source-r14/preview/Amfphp/");
			var $date:Date = new Date();
			this._start = $date.getTime();
			this._netConnection.call("eDirectoryService/Preview_getCompanys", $resp);
		}
		
		private function getCompanysDataThroughXML():void
		{
			var $urlLoader:URLLoader = new URLLoader();
			var $req:URLRequest = new URLRequest("http://192.168.0.135/source-r14/preview/getCompanys.php");
			/* 
			 * we don't wanna the XML result is cached, 
			 * that will shorten the loading time dramatically, 
			 * then this trial would be highly inaccurate!
			 */
				$req.method = URLRequestMethod.GET;
			var $variables:URLVariables = new URLVariables(); 
			var $date:Date = new Date();
				$variables.killcache = $date.getTime();
				$req.data = $variables;
				
				$urlLoader.addEventListener(Event.COMPLETE, this.companysLoadedHandler);
				$urlLoader.addEventListener(IOErrorEvent.IO_ERROR, this.companysIOErrorHandler);
				// no need for IOSecurityError
				try{
					$date = new Date();
					this._start = $date.getTime();
					$urlLoader.load($req);
				}
				catch($e:Error)
				{
					
				}
		}
		
		private function handleResult($result:Object):void
		{
			for each(var $company in $result)
			{
				trace("***********************");
				for (var $attr:String in $company)
				{
					trace("[" + $attr +"]: " + $company[$attr]);
				}
			}
			var $date:Date = new Date();
			this._end = $date.getTime();
			
			trace( "AMF case duration: " + (this._end-this._start) );
		}	
		
		private function companysLoadedHandler($e:Event):void
		{
			// remove listener
						
			var $companysXML:XML;
			try{
				$companysXML = new XML($e.target.data);
			}
			catch($e:Error)
			{
				
			}
			
			if(undefined == $companysXML.error)
			{
				
				for each(var $row in $companysXML.children())
				{
					trace("***********************");
					trace("[chi_name]: " + $row.chi_name.toString());
					trace("[samt_stroke]: " + $row.samt_stroke.toString());
					trace("[amt_stroke]: " + $row.tamt_stroke.toString());
					trace("[eng_name]: " + $row.eng_name.toString());
					trace("[descp]: " + $row.descp.toString());
					trace("[cmp_id]: " + $row.cmp_id.toString());
					trace("[schi_name]: " + $row.schi_name.toString());
					trace("[is_def_font]: " + $row.is_def_font.toString());
					trace("[logo]: " + $row.logo.toString());
				}
				
				var $date:Date = new Date();
				this._end = $date.getTime();
			
				trace( "XML case duration: " + (this._end-this._start) );
			}
			
			
		}
		
		private function handleError($result:Object):void
		{
			trace("rec");
			for (var $i in $result)
			{
				trace("[" + $i + "]\t" + $result[$i]);
			}
		}
		
		private function companysIOErrorHandler($e:IOErrorEvent):void
		{
			// remove listener
			// do nothing now
		}
		
		
	}
	
}

Before performing this trial, I restart my local web server, to clean up all the cached code or data of Apache and PHP, and then we access the data from Flash through XML and AMF, and the results were:


XML:


time-duration-xml


AMF:


time-duration-amf


But, please note that: this result is not taking the cache on Apache Server into account; if we let the server cached the PHP code, and what will the comparison be:


AMF case duration: 157
XML case duration: 94

It seems that the cached PHP functions for outputting XML works more fast. 



Problems With AMFPHP


Handling Error:


If the PHP on server run into a compilation error (this will not happen on production server definitely, but we just discuss here) :


error-compile


But, in this case, the fault handler would not be called:


private function handleError($result:Object):void
{
	trace("rec"); // never reach here
	for (var $i in $result)
	{
		trace("[" + $i + "]\t" + $result[$i]);
	}
}

2012-04-13

Let's continue with this topic.


What kind of error could be captured by the above handler? I did a little trial, change the called method's name to a non-exist method, like: 'Preview_getRoomByCompanyIDs', there is only Preview_getRoomByCompanyID() method.


We would get this page when calling this method in browser:


error-non-exist-method


Then, we call this non-exist method from our Flash application, and handleError() will output:


[faultString]	method Preview_getRoomByCompanyIDs not found on eDirectoryService object 
[faultCode]	0
[faultDetail]	#0 C:\xampp\htdocs\source-r14\preview\Amfphp\Core\Amf\Handler.php(107): Amfphp_Core_Common_ServiceRouter->executeServiceCall('eDirectoryServi...', 'Preview_getRoom...', Array)
#1 C:\xampp\htdocs\source-r14\preview\Amfphp\Core\Amf\Handler.php(138): Amfphp_Core_Amf_Handler->handleRequestMessage(Object(Amfphp_Core_Amf_Message), Object(Amfphp_Core_Common_ServiceRouter))
#2 C:\xampp\htdocs\source-r14\preview\Amfphp\Core\Gateway.php(199): Amfphp_Core_Amf_Handler->handleDeserializedRequest(Object(Amfphp_Core_Amf_Packet), Object(Amfphp_Core_Common_ServiceRouter))
#3 C:\xampp\htdocs\source-r14\preview\Amfphp\index.php(29): Amfphp_Core_Gateway->service()
#4 {main}
[rootCause]	[object Object]

So, this handler has its value!


Now, let's talk about how to handle this kind of error:


error-compile


I tried to regenerate this error, so I copy the declaration of the function, but what I got is:


redeclare-error


It doesn't matter, it still stands up for a kind of error, the key to handle this kind of error is that the object to throw error is NetConnection instance, and it can dispatch event of NetStatusEvent. So we declare a handler for that:


private function netStreamStatusHandler($e:NetStatusEvent):void
{
	/* there are plenty of errors that may occur at this point
	 * according to online document of NetStatusEvent
	 * code could be such value to indicate an error happen on NetConnection:
	 * "NetConnection.Call.BadVersion"
	 * "NetConnection.Call.Failed"
	 * "NetConnection.Call.Prohibited"
	 * "NetConnection.Connect.AppShutdown"
	 * "NetConnection.Connect.Failed"
	 * "NetConnection.Connect.InvalidApp"
	 * "NetConnection.Connect.Rejected"
	 * Whereas, level has only two values: "error" or "status", then we can use it to determine if 
	 * error happens
	 */
	trace($e.info.code);
	trace($e.info.level);
}

And attach it to the connection object to listen:


this._netConnection.addEventListener(NetStatusEvent.NET_STATUS, this.netStreamStatusHandler);

With this handler, we call the above broken service, the output is:


NetConnection.Call.BadVersion

Although I haven't tried out to prove that, I think the listener for IOError is also necessary, means we need code like this:


private function netConnIOErrorsHandler($e:IOErrorEvent):void
{
	this._logger.buildInError("loading data set of adver play list failed...", $e.toString(), 
	"com.ibeagle.vincezhang.edirectory.model.loaders.AdvPlaylistRemoteLoader::netConnIOErrorsHandler()");
}

And attach this listener to the NetConnection object.




global variable scope in PHP:








http://www.hotscripts.com/forums/php/15169-php-global-variables-dont-work-inside-class.html

http://stackoverflow.com/questions/4489134/php-class-global-variable-as-property-in-class

http://bobobobo.wordpress.com/2008/09/13/why-dont-my-php-globals-work-inside-of-classes/


Security:


Sand box security model in Flash technology may arise a problem when the server and client are not belonging to the same domain, please refer to:

http://www.silexlabs.org/amfphp/documentation/deploying-amfphp/




REFs:









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值