Thursday, 10 September 2009

Using AMFPHP with AS3 and CodeIgniter

As you can see there's not much to look at, but in fact it's the basis for our Flash communication classes; we can simply and easily send and receive data objects between the client side and the server side without serialisation.

Before we start...

It's a logical idea that you've had a good delve with ActionScript and PHP. You don't necessarily have to know how to use CodeIgniter as you could just use a PHP class as a service, but it's worth knowing PHP.

Step 1: Why Use AMFPHP?

This question gets thrown around a lot. We know that flash can get data from XML, JSON, etc... But AMFPHP uses a different form of encoding called "Action Message Format". It's simply sending binary data rather than ASCII. Now there's lots of blog posts around comparing XML, JSON and AMF, but at the end of the day AMF has these great advantages:

  • You don't need to serialise any data! For example, if you're passing some objects from PHP to Flash, you need to serialise them as, say, JSON. Flash will then have to parse them in order for them to be usable, this takes time and memory.
  • It's lighter than XML because you're never duplicating yourself. For example, in XML you have to start with a root tag, then more tags, then closing tags, so you'll see yourself duplicating tag names and this takes up bandwidth. Further more, XML, just like JSON, needs to be parsed into E4X and this takes time.
  • AMF can be fully integrated with your PHP code meaning that you can write less code. I'll show you this by using CodeIgniter as well as AMF's standard services.

Remember, AMF isn't a replacement for XML or JSON, it's simply another tool in your arsenal when it comes to developing scalable dynamic applications.

Step 2: The Set Up

Fire up your favourite IDE, whether it's Flex Builder, FDT, FlashDevelop or TextMate and create a new ActionScript project. In addition to this, we'll be doing some PHP coding too, so get your PHP IDE ready (I recommend Aptana)

Additionally, we're going to be using GreenSock's TweenLite class just to give the application a bit of magic.

For those of you who've read my recent articles: we're not going to be delving into PureMVC this time as I wanted the outcome of this article to be a set of classes that you can reuse again, with or without PureMVC.

Step 3: Creating the Base App

Just like with any ActionScript app, we need to set up our base app. I'm just going to start by just creating a simple background for the app, then we're going to look at creating the text area for the response and buttons to send a string, array, and object to PHP; so create a new file called "App.as" within "src/":

  1. package
  2. {
  3. import flash.display.GradientType;
  4. import flash.display.Sprite;
  5. import flash.geom.Matrix;
  6. import flash.text.Font;
  7. [SWF( width='600', height='400', frameRate='30', backgroundColor='#000000' )]
  8. public class App extends Sprite
  9. {
  10. [Embed( systemFont='Arial', fontName='Arial', mimeType='application/x-font' )]
  11. private var arialFont:Class;
  12. public function App()
  13. {
  14. init();
  15. }
  16. private function init():void
  17. {
  18. var mat:Matrix = new Matrix();
  19. var bg:Sprite = new Sprite();
  20. mat.createGradientBox( stage.stageWidth, stage.stageHeight, Math.PI * .5 );
  21. bg.graphics.beginGradientFill( GradientType.LINEAR, [ 0x333333, 0x000000 ], [ 1, 1 ], [ 0, 255 ], mat );
  22. bg.graphics.drawRect( 0, 0, stage.stageWidth, stage.stageHeight );
  23. bg.graphics.endFill();
  24. addChild( bg );
  25. Font.registerFont( arialFont );
  26. }
  27. }
  28. }

Step 4: Creating the UI

Since we're going to need some sort of UI for the user to be able to interact with our app, we're going to have to code one! Now it's up to you whether you want to create your UI in the Flash IDE (or the new Flash Catalyst IDE, looks nice) but I'm going to be hardcore and code it..

In terms of UI components, we're after:

  • An input text box
  • A button
  • A text area

It's just the start to your UI classes, but these are all we're going to need at the moment.

Step 5: Creating an Input Text Box

Since I believe that re-writing code is just long and unnecessary, let's create a reusable UI button component. Create a new file called "InputTextField.as" within "src/com/flashtuts/lib/display/ui" and use the following code within it:

  1. package com.flashtuts.lib.display.ui
  2. {
  3. import flash.display.Sprite;
  4. import flash.text.TextField;
  5. import flash.text.TextFieldAutoSize;
  6. import flash.text.TextFieldType;
  7. import flash.text.TextFormat;
  8. public class InputTextField extends Sprite
  9. {
  10. private var minWidth:Number = 200;
  11. private var bg:Sprite;
  12. private var field:TextField;
  13. public function InputTextField()
  14. {
  15. bg = new Sprite();
  16. addChild( bg );
  17. field = new TextField();
  18. addChild( field );
  19. }
  20. public function init(format:TextFormat, width:Number=0, height:Number=0, backgroundColour:uint=0, strokeThickness:Number=0, strokeColour:uint=0, cornerRadius:Number=0, padding:Number=4):void
  21. {
  22. field.autoSize = TextFieldAutoSize.LEFT;
  23. field.defaultTextFormat = format;
  24. field.embedFonts = true;
  25. field.type = TextFieldType.INPUT;
  26. field.width = ( width > 0 ? width - ( padding * 2 ) : minWidth );
  27. field.x = padding;
  28. field.y = padding;
  29. bg.graphics.clear();
  30. bg.graphics.lineStyle( strokeThickness, strokeColour );
  31. bg.graphics.beginFill( backgroundColour );
  32. bg.graphics.drawRoundRect( 0, 0, ( width > 0 ? width : field.width + ( padding * 2 ) ), ( height > 0 ? height : field.height + ( padding * 2 ) ), cornerRadius );
  33. bg.graphics.endFill();
  34. }
  35. public function set text(text:String):void
  36. {
  37. field.text = text;
  38. }
  39. public function get text():String
  40. {
  41. return field.text;
  42. }
  43. }
  44. }

Now, let's quickly go through it. We start with our constructor, we construct our two main variables: the background 'bg' and the textfield 'field' then we add them to the stage:

  1. public function InputTextField()
  2. {
  3. bg = new Sprite();
  4. addChild( bg );
  5. field = new TextField();
  6. addChild( field );
  7. }

Our class relies on the user using the public function "init()" that will draw the background as well as setting up the text field:

  1. public function init(format:TextFormat, width:Number=0, height:Number=0, backgroundColour:uint=0, strokeThickness:Number=0, strokeColour:uint=0, cornerRadius:Number=0, padding:Number=4):void
  2. {
  3. field.autoSize = TextFieldAutoSize.LEFT;
  4. field.defaultTextFormat = format;
  5. field.embedFonts = true;
  6. field.type = TextFieldType.INPUT;
  7. field.width = ( width > 0 ? width - ( padding * 2 ) : minWidth );
  8. field.x = padding;
  9. field.y = padding;
  10. bg.graphics.clear();
  11. bg.graphics.lineStyle( strokeThickness, strokeColour );
  12. bg.graphics.beginFill( backgroundColour );
  13. bg.graphics.drawRoundRect( 0, 0, ( width > 0 ? width : field.width + ( padding * 2 ) ), ( height > 0 ? height : field.height + ( padding * 2 ) ), cornerRadius );
  14. bg.graphics.endFill();
  15. }

Then finally we want to be able to set some text (if we need to) and to get any text that the user has entered, so we use get/set functions:

  1. public function set text(text:String):void
  2. {
  3. field.text = text;
  4. }
  5. public function get text():String
  6. {
  7. return field.text;
  8. }

You can see that you're able to set an ample number of variables. You can always come back and change this, but for the time being it works for this tutorial, so let's continue.

We now want to add this field to our application, so I've created a new function called "addTextField()", and called it within "init()", so our app's code looks like this:

  1. package
  2. {
  3. import com.flashtuts.lib.display.ui.InputTextField;
  4. import flash.display.GradientType;
  5. import flash.display.Sprite;
  6. import flash.geom.Matrix;
  7. import flash.text.Font;
  8. import flash.text.TextFormat;
  9. [SWF( width='600', height='400', frameRate='30', backgroundColor='#000000' )]
  10. public class App extends Sprite
  11. {
  12. [Embed( systemFont='Arial', fontName='Arial', mimeType='application/x-font' )]
  13. private var arialFont:Class;
  14. public function App()
  15. {
  16. init();
  17. }
  18. private function init():void
  19. {
  20. var mat:Matrix = new Matrix();
  21. var bg:Sprite = new Sprite();
  22. mat.createGradientBox( stage.stageWidth, stage.stageHeight, Math.PI * .5 );
  23. bg.graphics.beginGradientFill( GradientType.LINEAR, [ 0x333333, 0x000000 ], [ 1, 1 ], [ 0, 255 ], mat );
  24. bg.graphics.drawRect( 0, 0, stage.stageWidth, stage.stageHeight );
  25. bg.graphics.endFill();
  26. addChild( bg );
  27. Font.registerFont( arialFont );
  28. addTextField();
  29. }
  30. private function addTextField():void
  31. {
  32. var textField:InputTextField = new InputTextField();
  33. textField.init( new TextFormat( 'Arial', 12, 0x000000 ), 200, 25, 0xFFFFFF, 1, 0x333333, 5 );
  34. textField.text = 'Hello';
  35. addChild( textField );
  36. }
  37. }
  38. }

You can see that within the function I've created a new text format and passed that, with other parameters too, to our "InputTextField()" class. Fire up a preview and you'll see your text field in the top left hand corner. Before we position anything, let's finish the other UI components.

Step 6: Creating a Button

Now that we have our input text area down, let's create a button that'll allow us to push the data we enter to AMFPHP. So create file called "Button.as" inside "src/com/flashtuts/lib/display/ui/" and use the following code:

  1. package com.flashtuts.lib.display.ui
  2. {
  3. import flash.display.Sprite;
  4. import flash.text.TextField;
  5. import flash.text.TextFieldAutoSize;
  6. import flash.text.TextFormat;
  7. public class Button extends Sprite
  8. {
  9. private var bg:Sprite;
  10. private var field:TextField;
  11. public function Button()
  12. {
  13. this.buttonMode = true;
  14. this.mouseChildren = false;
  15. bg = new Sprite();
  16. addChild( bg );
  17. field = new TextField();
  18. addChild( field );
  19. }
  20. public function init(text:String, format:TextFormat, backgroundColour:uint=0, strokeThickness:Number=0, strokeColour:uint=0, cornerRadius:Number=0, padding:Number=4):void
  21. {
  22. field.autoSize = TextFieldAutoSize.LEFT;
  23. field.defaultTextFormat = format;
  24. field.embedFonts = true;
  25. field.mouseEnabled = false;
  26. field.text = text;
  27. field.x = padding;
  28. field.y = padding;
  29. bg.graphics.clear();
  30. bg.graphics.lineStyle( strokeThickness, strokeColour );
  31. bg.graphics.beginFill( backgroundColour );
  32. bg.graphics.drawRoundRect( 0, 0, field.width + ( padding * 2 ), field.height + ( padding * 2 ), cornerRadius );
  33. bg.graphics.endFill();
  34. }
  35. }
  36. }

A quick run through: much like our "InputTextField()" class, we construct our two variables 'bg' and 'field' and also set properties so that this class acts as a button:

  1. public function Button()
  2. {
  3. this.buttonMode = true;
  4. this.mouseChildren = false;
  5. bg = new Sprite();
  6. addChild( bg );
  7. field = new TextField();
  8. addChild( field );
  9. }

Again, just like our "InputTextField()" class, we set up the display of the class:

  1. public function init(text:String, format:TextFormat, backgroundColour:uint=0, strokeThickness:Number=0, strokeColour:uint=0, cornerRadius:Number=0, padding:Number=4):void
  2. {
  3. field.autoSize = TextFieldAutoSize.LEFT;
  4. field.defaultTextFormat = format;
  5. field.embedFonts = true;
  6. field.mouseEnabled = false;
  7. field.text = text;
  8. field.x = padding;
  9. field.y = padding;
  10. bg.graphics.clear();
  11. bg.graphics.lineStyle( strokeThickness, strokeColour );
  12. bg.graphics.beginFill( backgroundColour );
  13. bg.graphics.drawRoundRect( 0, 0, field.width + ( padding * 2 ), field.height + ( padding * 2 ), cornerRadius );
  14. bg.graphics.endFill();
  15. }

Finally we need to adjust our app's base class like so:

  1. package
  2. {
  3. import com.flashtuts.lib.display.ui.Button;
  4. import com.flashtuts.lib.display.ui.InputTextField;
  5. import flash.display.GradientType;
  6. import flash.display.Sprite;
  7. import flash.geom.Matrix;
  8. import flash.text.Font;
  9. import flash.text.TextFormat;
  10. [SWF( width='600', height='400', frameRate='30', backgroundColor='#000000' )]
  11. public class App extends Sprite
  12. {
  13. [Embed( systemFont='Arial', fontName='Arial', mimeType='application/x-font' )]
  14. private var arialFont:Class;
  15. public function App()
  16. {
  17. init();
  18. }
  19. private function init():void
  20. {
  21. var mat:Matrix = new Matrix();
  22. var bg:Sprite = new Sprite();
  23. mat.createGradientBox( stage.stageWidth, stage.stageHeight, Math.PI * .5 );
  24. bg.graphics.beginGradientFill( GradientType.LINEAR, [ 0x333333, 0x000000 ], [ 1, 1 ], [ 0, 255 ], mat );
  25. bg.graphics.drawRect( 0, 0, stage.stageWidth, stage.stageHeight );
  26. bg.graphics.endFill();
  27. addChild( bg );
  28. Font.registerFont( arialFont );
  29. addTextField();
  30. }
  31. private function addTextField():void
  32. {
  33. var format:TextFormat = new TextFormat( 'Arial', 12, 0x000000 );
  34. var textField:InputTextField = new InputTextField();
  35. var button:Button = new Button();
  36. textField.init( format, 200, 25, 0xFFFFFF, 1, 0x333333, 5 );
  37. textField.text = 'Hello';
  38. addChild( textField );
  39. button.init( 'Click on me!', format, 0xCCCCCC, 1, 0x333333, 5 );
  40. addChild( button );
  41. }
  42. }
  43. }

If you ran this in debug you'd see a button over a text field, so nearly there!

Step 7: Create a Text Area

This one won't be so hard as we've done the hard work in the "InputTextField()" class already, so all we'll need to do is set the height and width of our textarea, a simple enough copy and paste job. So create a new file called "InputTextArea.as" inside "src/com/flashtuts/lib/display/ui/" and use the following code:

  1. package com.flashtuts.lib.display.ui
  2. {
  3. import flash.display.Sprite;
  4. import flash.text.TextField;
  5. import flash.text.TextFieldAutoSize;
  6. import flash.text.TextFormat;
  7. public class InputTextArea extends Sprite
  8. {
  9. private var minHeight:Number = 200;
  10. private var minWidth:Number = 200;
  11. private var bg:Sprite;
  12. private var field:TextField;
  13. public function InputTextArea()
  14. {
  15. bg = new Sprite();
  16. addChild( bg );
  17. field = new TextField();
  18. addChild( field );
  19. }
  20. public function init(format:TextFormat, width:Number=0, height:Number=0, backgroundColour:uint=0, strokeThickness:Number=0, strokeColour:uint=0, cornerRadius:Number=0, padding:Number=4):void
  21. {
  22. field.autoSize = TextFieldAutoSize.LEFT;
  23. field.defaultTextFormat = format;
  24. field.embedFonts = true;
  25. field.multiline = true;
  26. field.wordWrap = true;
  27. field.height = ( height > 0 ? height - ( padding * 2 ) : minHeight );
  28. field.width = ( width > 0 ? width - ( padding * 2 ) : minWidth );
  29. field.x = padding;
  30. field.y = padding;
  31. bg.graphics.clear();
  32. bg.graphics.lineStyle( strokeThickness, strokeColour );
  33. bg.graphics.beginFill( backgroundColour );
  34. bg.graphics.drawRoundRect( 0, 0, ( width > 0 ? width : field.width + ( padding * 2 ) ), ( height > 0 ? height : field.height + ( padding * 2 ) ), cornerRadius );
  35. bg.graphics.endFill();
  36. }
  37. public function set text(text:String):void
  38. {
  39. field.text = text;
  40. }
  41. public function get text():String
  42. {
  43. return field.text;
  44. }
  45. }
  46. }

I think at this point there's no need to explain the whole class, but it's worth mentioning the configuration of the field that allows it to become a text area. We simply added two more properties and set them to true: 'multiline' and 'wordwrap'. These turn the input box into a text area.

So finally, let's add the last component to the app's class:

  1. package
  2. {
  3. import com.flashtuts.lib.display.ui.Button;
  4. import com.flashtuts.lib.display.ui.InputTextArea;
  5. import com.flashtuts.lib.display.ui.InputTextField;
  6. import flash.display.GradientType;
  7. import flash.display.Sprite;
  8. import flash.geom.Matrix;
  9. import flash.text.Font;
  10. import flash.text.TextFormat;
  11. [SWF( width='600', height='400', frameRate='30', backgroundColor='#000000' )]
  12. public class App extends Sprite
  13. {
  14. [Embed( systemFont='Arial', fontName='Arial', mimeType='application/x-font' )]
  15. private var arialFont:Class;
  16. public function App()
  17. {
  18. init();
  19. }
  20. private function init():void
  21. {
  22. var mat:Matrix = new Matrix();
  23. var bg:Sprite = new Sprite();
  24. mat.createGradientBox( stage.stageWidth, stage.stageHeight, Math.PI * .5 );
  25. bg.graphics.beginGradientFill( GradientType.LINEAR, [ 0x333333, 0x000000 ], [ 1, 1 ], [ 0, 255 ], mat );
  26. bg.graphics.drawRect( 0, 0, stage.stageWidth, stage.stageHeight );
  27. bg.graphics.endFill();
  28. addChild( bg );
  29. Font.registerFont( arialFont );
  30. addTextField();
  31. }
  32. private function addTextField():void
  33. {
  34. var format:TextFormat = new TextFormat( 'Arial', 12, 0x000000 );
  35. var textField:InputTextField = new InputTextField();
  36. var textArea:InputTextArea = new InputTextArea();
  37. var button:Button = new Button();
  38. textField.init( format, 200, 25, 0xFFFFFF, 1, 0x333333, 5 );
  39. textField.text = 'Hello';
  40. addChild( textField );
  41. textArea.init( format, 200, 200, 0xFFFFFF, 1, 0x333333, 5 );
  42. textArea.text = 'Text will appear here that's returned from the server!';
  43. addChild( textArea );
  44. button.init( 'Click on me!', format, 0xCCCCCC, 1, 0x333333, 5 );
  45. addChild( button );
  46. }
  47. }
  48. }

Finally, let's just align our UI so it's usable and set the 'textField' and 'textInput' so that they're class wide functions rather than within the "addTextField()" function:

  1. package
  2. {
  3. import com.flashtuts.lib.display.ui.Button;
  4. import com.flashtuts.lib.display.ui.InputTextArea;
  5. import com.flashtuts.lib.display.ui.InputTextField;
  6. import flash.display.GradientType;
  7. import flash.display.Sprite;
  8. import flash.geom.Matrix;
  9. import flash.text.Font;
  10. import flash.text.TextFormat;
  11. [SWF( width='600', height='400', frameRate='30', backgroundColor='#000000' )]
  12. public class App extends Sprite
  13. {
  14. private var textField:InputTextField = new InputTextField();
  15. private var textArea:InputTextArea = new InputTextArea();
  16. [Embed( systemFont='Arial', fontName='Arial', mimeType='application/x-font' )]
  17. private var arialFont:Class;
  18. public function App()
  19. {
  20. init();
  21. }
  22. private function init():void
  23. {
  24. var mat:Matrix = new Matrix();
  25. var bg:Sprite = new Sprite();
  26. mat.createGradientBox( stage.stageWidth, stage.stageHeight, Math.PI * .5 );
  27. bg.graphics.beginGradientFill( GradientType.LINEAR, [ 0x333333, 0x000000 ], [ 1, 1 ], [ 0, 255 ], mat );
  28. bg.graphics.drawRect( 0, 0, stage.stageWidth, stage.stageHeight );
  29. bg.graphics.endFill();
  30. addChild( bg );
  31. Font.registerFont( arialFont );
  32. addTextField();
  33. }
  34. private function addTextField():void
  35. {
  36. var format:TextFormat = new TextFormat( 'Arial', 12, 0x000000 );
  37. var button:Button = new Button();
  38. textField.init( format, 200, 25, 0xFFFFFF, 1, 0x333333, 10 );
  39. textField.text = 'Hello';
  40. addChild( textField );
  41. textArea.init( format, 300, 200, 0xFFFFFF, 1, 0x333333, 10 );
  42. textArea.text = 'Text will appear here that\'s returned from the server!';
  43. addChild( textArea );
  44. button.init( 'Click on me!', format, 0xCCCCCC, 1, 0x333333, 10 );
  45. addChild( button );
  46. textField.x = ( stage.stageWidth / 2 ) - ( ( textField.width + button.width + 20 ) / 2 );
  47. textField.y = 20;
  48. button.x = textField.x + textField.width + 20;
  49. button.y = 20;
  50. textArea.x = ( stage.stageWidth / 2 ) - ( textArea.width / 2 );
  51. textArea.y = 60;
  52. }
  53. }
  54. }

So that's our base class pretty much finished. Now we'll move on to writing the classes for AMFPHP communication.

Step 8: Starting With the Basics

I'm assuming that you've worked with loading data into Flash player before. When working with static data that's normally loaded right at the start of your application being loaded, using XML or JSON is fine. It's also fine with dynamic data, but AMF gives you the advantage of a constant gateway, good performance and, most importantly, less code to write as there's no need encode or decode the data.

What normally happens when working with data and Flash is you'd normally either load a static file or have XML or JSON dynamically created, for example:

  • A static file: ./assets/xml/data.xml
  • A dynamic file: http://mydomain.com/data.php?someId=1234

It's all fair and well loading data, but what if you want to send data? Yep you can of course set up a few classes to send data to your site's dynamic scripts but then you'll already have set up a class for retrieving data and then you'll have to modify it so that it'll allow you to send data. Plus of course, you'll need to update your dynamic scripts on your site's servers. Now this is where AMFPHP is so great, you only need to create one set of classes for ActionScript and then all your PHP code can be contained within one simple code base. I'll also extend it to show you how you can use a framework, in this case CodeIgniter.

Step 9: Creating the ActionScript Classes

Since AMFPHP is a remote connection service, you may want to create other classes in the future, besides what's the point of duplicating code? What we'll do is create three classes:

  • "RemoteConnectionService.as": this will act as our base class and will have useful functions that we can use in the future for other remote connection services
  • "RemoteConnectionServiceEvent.as": this will act as the events class that the base class will use
  • "AMFPHPService.as": and this will be our AMFPHP connection class

This way we'll be able to create a nice set of classes but also have them clean and scalable should we need to write some new ones in the future.

Step 10: Creating the Base Class

First thing we'll do is start with the base class. So create a file called "RemoteConnectionService.as" within "src/com/flashtuts/lib/data/" and it'll extend the "NetConnection" class:

  1. package com.flashtuts.lib.data
  2. {
  3. import flash.net.NetConnection;
  4. public class RemoteConnectionService extends NetConnection
  5. {
  6. public function RemoteConnectionService()
  7. {
  8. super();
  9. }
  10. }
  11. }

Now we've got our class, we need to write some code, so we've got to think what functions can be placed within the base rather than be duplicated. I would suggest using the constructor to set up the connection, then using the base to handle any events the connection sends and finally we create a standard function that we will use to send our data.

The first thing to do is to write our constructor:

  1. package com.flashtuts.lib.data
  2. {
  3. import flash.events.NetStatusEvent;
  4. import flash.net.NetConnection;
  5. import flash.net.Responder;
  6. public class RemoteConnectionService extends NetConnection
  7. {
  8. public var handleReady:Boolean = true;
  9. public var data:Object;
  10. public var gatewayURL:String;
  11. public var responder:Responder;
  12. private var loadedEvent:String;
  13. private var readyEvent:String;
  14. private var faultEvent:String;
  15. public function RemoteConnectionService(gatewayURL:String, loadedEvent:String='', readyEvent:String='', faultEvent:String='', encoding:uint=3)
  16. {
  17. this.gatewayURL = gatewayURL;
  18. this.loadedEvent = loadedEvent;
  19. this.readyEvent = readyEvent;
  20. this.faultEvent = faultEvent;
  21. objectEncoding = encoding;
  22. if ( gatewayURL )
  23. {
  24. responder = new Responder( handleResponseResult, handleResponseFault );
  25. addEventListener( NetStatusEvent.NET_STATUS, handleNetEvent );
  26. connect( gatewayURL );
  27. }
  28. }
  29. }
  30. }

Now you may be wondering what on earth all that is, not to fear, here's a quick run down:

  • The public vars:
    Since this class is going to act like a service we need to tell it where the AMFPHP gateway is, therefore we set the 'gatewayURL', furthermore in order to have result and fault listeners we have to create a new "Responder()" (you may be wondering why this isn't through events, maybe Adobe were hungover that day) and we will bind this responder to our calls. Finally it's always handy to have the ability to store the data we receive from our transactions in an accessible public var, in this case called 'data'.
  • The private vars and 'handleReady' var:
    Now since this is the base class for our remote connection service, there may be times when you'll want to manipulate the data before it's sent back to your application. Therefore instead of just having a 'ready' event fired, I've set a 'loaded' event too. This will allow you to hook into the service, manipulate the data and then dispatch the 'ready' event. By default, any subclasses will define these events as part of the 'RemoteConnectionServiceEvent()' class, but we have the ability, when super-ing, to say otherwise, allowing for greater flexibility.

When we construct the class, remember this is a base class so it'll always be super-ed rather than constructed itself, we pass in the 'gatewayURL', the events, and the encoding value (this defaults to '3' which is AMF3 style encoding, perfect for us).

Step 11: Handling the Responses and Event

Now we need to create the event and responder handlers for this class, so let's add them:

  1. package com.flashtuts.lib.data
  2. {
  3. import com.flashtuts.lib.events.RemoteConnectionServiceEvent;
  4. import flash.events.NetStatusEvent;
  5. import flash.net.NetConnection;
  6. import flash.net.Responder;
  7. public class RemoteConnectionService extends NetConnection
  8. {
  9. public var handleReady:Boolean = true;
  10. public var data:Object;
  11. public var gatewayURL:String;
  12. public var responder:Responder;
  13. private var loadedEvent:String;
  14. private var readyEvent:String;
  15. private var faultEvent:String;
  16. public function RemoteConnectionService(gatewayURL:String, loadedEvent:String='', readyEvent:String='', faultEvent:String='', encoding:uint=3)
  17. {
  18. this.gatewayURL = gatewayURL;
  19. this.loadedEvent = loadedEvent;
  20. this.readyEvent = readyEvent;
  21. this.faultEvent = faultEvent;
  22. objectEncoding = encoding;
  23. if ( gatewayURL )
  24. {
  25. responder = new Responder( handleResponseResult, handleResponseFault );
  26. addEventListener( NetStatusEvent.NET_STATUS, handleNetEvent );
  27. connect( gatewayURL );
  28. }
  29. }
  30. private function handleNetEvent(e:NetStatusEvent):void
  31. {
  32. dispatchEvent( new RemoteConnectionServiceEvent( faultEvent, e.info.code ) );
  33. }
  34. private function handleResponseResult(data:Object):void
  35. {
  36. dispatchEvent( new RemoteConnectionServiceEvent( loadedEvent, data ) );
  37. if ( handleReady )
  38. {
  39. handleLoaderDataReady( data );
  40. }
  41. }
  42. public function handleLoaderDataReady(data:Object):void
  43. {
  44. this.data = data;
  45. dispatchEvent( new RemoteConnectionServiceEvent( readyEvent, data ) );
  46. }
  47. private function handleResponseFault(data:Object):void
  48. {
  49. dispatchEvent( new RemoteConnectionServiceEvent( faultEvent, data ) );
  50. }
  51. }
  52. }

Here you see that we've added the responder functions "handleResponseResult()" and "handleResponseFault()". Also, we have the "handleNetEvent()" just incase anything falls on it's face and finally we have the "handleLoaderDataReady()" that will be called once the data is ready - instead of any hooks you may want to use.

You can see that it's always dispatching events that you're able to define. This will allow for greater flexibility and scalability in the long run.

Step 12: Creating the Events Class

Since we're dispatching events from our service base class, it makes sense to have a default events class. If you're feeling lazy and can't be bothered to create your own events class, you can just use this as we'll specify the class to use these events by default, so create a file called "RemoteConnectionServiceEvent.as" inside "src/com/flashtuts/lib/events/":

  1. package com.flashtuts.lib.events
  2. {
  3. import flash.events.Event;
  4. public class RemoteConnectionServiceEvent extends Event
  5. {
  6. public static const NAME:String = 'RemoteConnectionServiceEvent';
  7. public static const LOADED:String = NAME + 'Loaded';
  8. public static const READY:String = NAME + 'Ready';
  9. public static const FAULT:String = NAME + 'Fault';
  10. public var data:Object;
  11. public function RemoteConnectionServiceEvent(type:String, data:Object=null, bubbles:Boolean=true, cancelable:Boolean=false)
  12. {
  13. super( type, bubbles, cancelable );
  14. this.data = data;
  15. }
  16. }
  17. }

It's a pretty basic events class, we're extending the standard "Event()" class and adding our own public var called 'data' so that we can send data with the event and easily retrieve it through our listeners.

Step 13: Creating the AMFPHP Class

Now our base class and it's events class are ready, all we need to do is extend the base so that we can use it for our AMFPHP service class. Create a new file called "AMFPHPService.as" inside "src/com/flashtuts/lib/data/":

  1. package com.flashtuts.lib.data
  2. {
  3. import com.flashtuts.lib.events.RemoteConnectionServiceEvent;
  4. import flash.net.ObjectEncoding;
  5. public class AMFPHPService extends RemoteConnectionService
  6. {
  7. public function AMFPHPService( url:String )
  8. {
  9. super( url, RemoteConnectionServiceEvent.LOADED, RemoteConnectionServiceEvent.READY, RemoteConnectionServiceEvent.FAULT, ObjectEncoding.AMF3 );
  10. }
  11. }
  12. }

Don't be confused, it'll all make sense in a few paragraphs time, let's start with the constructor: like I've said further up, our base class will always be super-ed, so within the constructor we pass the 'url' variable given to this class when it's constructed as well as the events and finally the default encoding. The reason I'm choosing to separate the "AMFPHPService()" class from the "RemoteConnectionService()" class is because it'll allow for greater flexibility and scalability, as you can write other services that will take advantage of the base, but have different events and functions.

Before we continue, it's worth talking these special variables called ...(rest) which basically means that a function can accept any number of comma-delimited arguments, for example:

Traditionally you'd need to declare a function's arguments like this:

  1. function test(arg1, arg2, arg3, arg4):void
  2. {
  3. ...
  4. }

However with ...(rest) arguments you can do this:

  1. function test(... args):void
  2. {
  3. ...
  4. }

And call it like this:

  1. test('var1', 'var2', 'var3', 'var4', 'var5', 'varN', 'varN+1');

So you can use any number of variables. These are then accessible in the function as an array, for example:

  1. function test(... args):void
  2. {
  3. trace(args.length);
  4. }
  5. test('var1', 'var2'); // traces 2
  6. test('var1', 'var2', 'var3', 'var4', 'var5'); // traces 5

"NetConnection.call()" takes advantage of these ...(rest) arguments allowing you to send any number of arguments to your gateway. However, the function also requires you to give the 'responder', something that we created within our "RemoteConnectionService()" class. So since we're always going to need to use this "call()" function but don't want to write the 'responder' variable every time we use it, we should put it within our base class. Go ahead and open up the "RemoteConnectionService()" class then update it with this:

  1. package com.flashtuts.lib.data
  2. {
  3. import com.flashtuts.lib.events.RemoteConnectionServiceEvent;
  4. import flash.events.NetStatusEvent;
  5. import flash.net.NetConnection;
  6. import flash.net.Responder;
  7. public class RemoteConnectionService extends NetConnection
  8. {
  9. public var handleReady:Boolean = true;
  10. public var data:Object;
  11. public var gatewayURL:String;
  12. public var responder:Responder;
  13. private var loadedEvent:String;
  14. private var readyEvent:String;
  15. private var faultEvent:String;
  16. public function RemoteConnectionService(gatewayURL:String, loadedEvent:String='', readyEvent:String='', faultEvent:String='', encoding:uint=3)
  17. {
  18. this.gatewayURL = gatewayURL;
  19. this.loadedEvent = loadedEvent;
  20. this.readyEvent = readyEvent;
  21. this.faultEvent = faultEvent;
  22. objectEncoding = encoding;
  23. if ( gatewayURL )
  24. {
  25. responder = new Responder( handleResponseResult, handleResponseFault );
  26. addEventListener( NetStatusEvent.NET_STATUS, handleNetEvent );
  27. connect( gatewayURL );
  28. }
  29. }
  30. public function send(method:String, ... args):void
  31. {
  32. call.apply( null, [ method, responder ].concat( args ) );
  33. }
  34. private function handleNetEvent(e:NetStatusEvent):void
  35. {
  36. dispatchEvent( new RemoteConnectionServiceEvent( faultEvent, e.info.code ) );
  37. }
  38. private function handleResponseResult(data:Object):void
  39. {
  40. dispatchEvent( new RemoteConnectionServiceEvent( loadedEvent, data ) );
  41. if ( handleReady )
  42. {
  43. handleLoaderDataReady( data );
  44. }
  45. }
  46. public function handleLoaderDataReady(data:Object):void
  47. {
  48. this.data = data;
  49. dispatchEvent( new RemoteConnectionServiceEvent( readyEvent, data ) );
  50. }
  51. private function handleResponseFault(data:Object):void
  52. {
  53. dispatchEvent( new RemoteConnectionServiceEvent( faultEvent, data ) );
  54. }
  55. }
  56. }

Step 14: Tying it in

Now that our service classes are ready, if we go back to our app's base class we have to add the service by creating it as a class-wide variable and constructing it within our "init()" function:

  1. package
  2. {
  3. import com.flashtuts.lib.data.AMFPHPService;
  4. import com.flashtuts.lib.display.ui.Button;
  5. import com.flashtuts.lib.display.ui.InputTextArea;
  6. import com.flashtuts.lib.display.ui.InputTextField;
  7. import flash.display.GradientType;
  8. import flash.display.Sprite;
  9. import flash.events.MouseEvent;
  10. import flash.geom.Matrix;
  11. import flash.text.Font;
  12. import flash.text.TextFormat;
  13. [SWF( width='600', height='400', frameRate='30', backgroundColor='#000000' )]
  14. public class App extends Sprite
  15. {
  16. private var service:AMFPHPService = new AMFPHPService( 'http://localhost/amfphp/gateway.php' );
  17. private var textField:InputTextField = new InputTextField();
  18. private var textArea:InputTextArea = new InputTextArea();
  19. [Embed( systemFont='Arial', fontName='Arial', mimeType='application/x-font' )]
  20. private var arialFont:Class;
  21. public function App()
  22. {
  23. init();
  24. }
  25. private function init():void
  26. {
  27. var mat:Matrix = new Matrix();
  28. var bg:Sprite = new Sprite();
  29. mat.createGradientBox( stage.stageWidth, stage.stageHeight, Math.PI * .5 );
  30. bg.graphics.beginGradientFill( GradientType.LINEAR, [ 0x333333, 0x000000 ], [ 1, 1 ], [ 0, 255 ], mat );
  31. bg.graphics.drawRect( 0, 0, stage.stageWidth, stage.stageHeight );
  32. bg.graphics.endFill();
  33. addChild( bg );
  34. Font.registerFont( arialFont );
  35. addTextField();
  36. }
  37. private function addTextField():void
  38. {
  39. var format:TextFormat = new TextFormat( 'Arial', 12, 0x000000 );
  40. var button:Button = new Button();
  41. textField.init( format, 200, 25, 0xFFFFFF, 1, 0x333333, 10 );
  42. textField.text = 'Hello';
  43. addChild( textField );
  44. textArea.init( format, 300, 200, 0xFFFFFF, 1, 0x333333, 10 );
  45. textArea.text = 'Text will appear here that's returned from the server!';
  46. addChild( textArea );
  47. button.init( 'Click on me!', format, 0xCCCCCC, 1, 0x333333, 10 );
  48. addChild( button );
  49. textField.x = ( stage.stageWidth / 2 ) - ( ( textField.width + button.width + 20 ) / 2 );
  50. textField.y = 20;
  51. button.x = textField.x + textField.width + 20;
  52. button.y = 20;
  53. textArea.x = ( stage.stageWidth / 2 ) - ( textArea.width / 2 );
  54. textArea.y = 60;
  55. }
  56. }
  57. }

And since we're going to be using the service when the user hits the button, we need to add the event listeners to the buttons and then set up the function that will run our service's "send()" function to make the call:

  1. package
  2. {
  3. import com.flashtuts.lib.data.AMFPHPService;
  4. import com.flashtuts.lib.display.ui.Button;
  5. import com.flashtuts.lib.display.ui.InputTextArea;
  6. import com.flashtuts.lib.display.ui.InputTextField;
  7. import flash.display.GradientType;
  8. import flash.display.Sprite;
  9. import flash.events.MouseEvent;
  10. import flash.geom.Matrix;
  11. import flash.text.Font;
  12. import flash.text.TextFormat;
  13. [SWF( width='600', height='400', frameRate='30', backgroundColor='#000000' )]
  14. public class App extends Sprite
  15. {
  16. private var service:AMFPHPService = new AMFPHPService( 'http://dev.ahmednuaman.com/amfphp/gateway.php' );
  17. private var textField:InputTextField = new InputTextField();
  18. private var textArea:InputTextArea = new InputTextArea();
  19. [Embed( systemFont='Arial', fontName='Arial', mimeType='application/x-font' )]
  20. private var arialFont:Class;
  21. public function App()
  22. {
  23. init();
  24. }
  25. private function init():void
  26. {
  27. var mat:Matrix = new Matrix();
  28. var bg:Sprite = new Sprite();
  29. mat.createGradientBox( stage.stageWidth, stage.stageHeight, Math.PI * .5 );
  30. bg.graphics.beginGradientFill( GradientType.LINEAR, [ 0x333333, 0x000000 ], [ 1, 1 ], [ 0, 255 ], mat );
  31. bg.graphics.drawRect( 0, 0, stage.stageWidth, stage.stageHeight );
  32. bg.graphics.endFill();
  33. addChild( bg );
  34. Font.registerFont( arialFont );
  35. addTextField();
  36. }
  37. private function addTextField():void
  38. {
  39. var format:TextFormat = new TextFormat( 'Arial', 12, 0x000000 );
  40. var button:Button = new Button();
  41. textField.init( format, 200, 25, 0xFFFFFF, 1, 0x333333, 10 );
  42. textField.text = 'Hello';
  43. addChild( textField );
  44. textArea.init( format, 300, 200, 0xFFFFFF, 1, 0x333333, 10 );
  45. textArea.text = 'Text will appear here that's returned from the server!';
  46. addChild( textArea );
  47. button.init( 'Click on me!', format, 0xCCCCCC, 1, 0x333333, 10 );
  48. button.addEventListener( MouseEvent.CLICK, handleButtonClick );
  49. addChild( button );
  50. textField.x = ( stage.stageWidth / 2 ) - ( ( textField.width + button.width + 20 ) / 2 );
  51. textField.y = 20;
  52. button.x = textField.x + textField.width + 20;
  53. button.y = 20;
  54. textArea.x = ( stage.stageWidth / 2 ) - ( textArea.width / 2 );
  55. textArea.y = 60;
  56. }
  57. private function handleButtonClick(e:MouseEvent):void
  58. {
  59. service.send( 'HelloWorld.say', textField.text );
  60. }
  61. }
  62. }

You'll see that we're using a method called 'HelloWorld.say', this is a test AMFPHP service and I'll show you how to create your own later. And finally we need to add listeners to the service so that we can handle the ready event and display the response in the text area:

  1. package
  2. {
  3. import com.flashtuts.lib.data.AMFPHPService;
  4. import com.flashtuts.lib.display.ui.Button;
  5. import com.flashtuts.lib.display.ui.InputTextArea;
  6. import com.flashtuts.lib.display.ui.InputTextField;
  7. import com.flashtuts.lib.events.RemoteConnectionServiceEvent;
  8. import flash.display.GradientType;
  9. import flash.display.Sprite;
  10. import flash.events.MouseEvent;
  11. import flash.geom.Matrix;
  12. import flash.text.Font;
  13. import flash.text.TextFormat;
  14. [SWF( width='600', height='400', frameRate='30', backgroundColor='#000000' )]
  15. public class App extends Sprite
  16. {
  17. private var service:AMFPHPService = new AMFPHPService( 'http://dev.ahmednuaman.com/amfphp/gateway.php' );
  18. private var textField:InputTextField = new InputTextField();
  19. private var textArea:InputTextArea = new InputTextArea();
  20. [Embed( systemFont='Arial', fontName='Arial', mimeType='application/x-font' )]
  21. private var arialFont:Class;
  22. public function App()
  23. {
  24. init();
  25. }
  26. private function init():void
  27. {
  28. var mat:Matrix = new Matrix();
  29. var bg:Sprite = new Sprite();
  30. mat.createGradientBox( stage.stageWidth, stage.stageHeight, Math.PI * .5 );
  31. bg.graphics.beginGradientFill( GradientType.LINEAR, [ 0x333333, 0x000000 ], [ 1, 1 ], [ 0, 255 ], mat );
  32. bg.graphics.drawRect( 0, 0, stage.stageWidth, stage.stageHeight );
  33. bg.graphics.endFill();
  34. addChild( bg );
  35. Font.registerFont( arialFont );
  36. addTextField();
  37. service.addEventListener( RemoteConnectionServiceEvent.READY, handleDataReady );
  38. }
  39. private function addTextField():void
  40. {
  41. var format:TextFormat = new TextFormat( 'Arial', 12, 0x000000 );
  42. var button:Button = new Button();
  43. textField.init( format, 200, 25, 0xFFFFFF, 1, 0x333333, 10 );
  44. textField.text = 'Hello';
  45. addChild( textField );
  46. textArea.init( format, 300, 200, 0xFFFFFF, 1, 0x333333, 10 );
  47. textArea.text = 'Text will appear here that's returned from the server!';
  48. addChild( textArea );
  49. button.init( 'Click on me!', format, 0xCCCCCC, 1, 0x333333, 10 );
  50. button.addEventListener( MouseEvent.CLICK, handleButtonClick );
  51. addChild( button );
  52. textField.x = ( stage.stageWidth / 2 ) - ( ( textField.width + button.width + 20 ) / 2 );
  53. textField.y = 20;
  54. button.x = textField.x + textField.width + 20;
  55. button.y = 20;
  56. textArea.x = ( stage.stageWidth / 2 ) - ( textArea.width / 2 );
  57. textArea.y = 60;
  58. }
  59. private function handleButtonClick(e:MouseEvent):void
  60. {
  61. service.send( 'HelloWorld.say', textField.text );
  62. }
  63. private function handleDataReady(e:RemoteConnectionServiceEvent):void
  64. {
  65. textArea.text = e.data.toString();
  66. }
  67. }
  68. }

That's our ActionScript classes done! Now on to some PHP!

Step 15: Using AMFPHP

The first thing to do is to go to the AMFPHP web site and download yourself a copy of the code base. Once you've got it, uncompress it and stick it on your testing server or run it on your localhost. If you're new to PHP, it's worth reading up on it at w3schools.com.

Once you're happy that you know enough about PHP, it's time to get your hands dirty! The idea behind AMFPHP is that you can create a set of services and call them much like how you import files in ActionScript, for example:

AMFPHP comes with a standard service called 'HelloWorld.say'. The class and file are called 'HelloWorld' and the function is called 'say'. AMFPHP goes to the services folder, finds a file called 'HelloWorld.php', constructs the class called 'HelloWorld' and then runs the function called 'say'. If you were to have a class called 'Test' that belonged to the 'com.flashtuts' code base, so you created a file called 'Test.php' inside the folders 'com/flashtuts/' then you would reference the service like 'com.flashtuts.Test.function'. Simple eh?

The first thing we're going to do is just get our PHP class to return a simple string and then we'll move on to integrating CodeIgniter.

Step 16: Saying Hello Back

Since we've defaulted the text in our input field to say 'Hello', we may as well say something back, so create a new file called "HelloWorld.php" inside the "services" folder in your AMFPHP folder and use the following class:

  1. /* include the AMFPHP utils */
  2. include_once( AMFPHP_BASE . 'shared/util/MethodTable.php' );
  3. class HelloWorld
  4. {
  5. function say($msg)
  6. {
  7. return 'You said: ' . $msg;
  8. }
  9. }
  10. ?>

Now if you fire up your application you'll see that the server will return 'You said: (whatever you said, probably hello!)' in the text area, without the bracket bit at the end. Yay, you've used AMFPHP, good work! Let's get a bit deeper.

Step 17: Using a Database

We've returned what we sent to the server, but who wants that eh? Surely we'd want to store something in the database right? So, let's sort that out. Open up the file "globals.php" in the AMFPHP base directory and you'll see a bit of code that's commented out like this:

  1. /*
  2. if(!PRODUCTION_SERVER)
  3. {
  4. define("DB_HOST", "localhost");
  5. define("DB_USER", "root");
  6. define("DB_PASS", "");
  7. define("DB_NAME", "amfphp");
  8. }
  9. */

You can either copy this and paste it underneath or just uncomment it, we'll be using it to set our DB access. So enter your DB username, password and database, so it looks like this:

  1. if(!PRODUCTION_SERVER)
  2. {
  3. define("DB_HOST", "localhost");
  4. define("DB_USER", "bob");
  5. define("DB_PASS", "bobsCatsName");
  6. define("DB_NAME", "bobsDatabase");
  7. }

Now we're ready to alter our PHP class, so load up the "HelloWorld.php" service class and paste the following inside:

  1. include_once( AMFPHP_BASE . 'shared/util/MethodTable.php' );
  2. class HelloWorld
  3. {
  4. var $db;
  5. function __construct()
  6. {
  7. $this->db = mysql_connect( DB_HOST, DB_USER, DB_PASS );
  8. mysql_select_db( DB_NAME );
  9. }
  10. function say($msg)
  11. {
  12. return 'You said: ' . $msg;
  13. }
  14. function put($data)
  15. {
  16. $query = mysql_query( 'INSERT INTO hello_world ( id, data ) VALUES ( \'\', \'' . $data . '\' )', $this->db );
  17. if ( $query )
  18. {
  19. return mysql_insert_id();
  20. }
  21. else
  22. {
  23. return mysql_error();
  24. }
  25. }
  26. }
  27. /* End of file HelloWorld.php */
  28. /* Location: ./services/HelloWorld.php */
  29. ?>

Here's a quick run through:

  • We create a class wide variable called $db, we'll use this to store our database connection so that we won't need to reconnect within our functions
  • The "__construct()" function sets up the database connection for us
  • The "put()" function inserts the string $data into a table called 'hello_world' within our database

Cool yea? let's try it out. Change the method in your ActionScript app to 'HelloWorld.put', fire up the debug and have a go. Then use your SQL browser or phpMyAdmin to check the results in your database. You should see the insert id returned in your text area.

Step 18: Tying in CodeIgniter

Now stopping with the above is fine, you can go ahead and make a few classes and functions that will suit your site's needs, but what if you have bigger aspirations? We all know that building apps can be faster when using frameworks and CodeIgniter is a great framework. I don't want to get into the argument of which framework to use because at the end of the day CodeIgniter works for me for a few simple reasons:

  • It's lightweight
  • It's simple to install
  • It's easy to learn
  • It's got good community support
  • And it's free!

There are other frameworks out there, but for this tut I'm going to show you how you can hook CodeIgniter into the AMFPHP platform.

Step 19: Installing CodeIgniter

Grab yourself a copy of CodeIgniter from their site, decompresss it and stick it up on your server or localhost. It's pretty simple to install but for the time being we don't need to set up a database or anything like that. For future reference, have a read of their user guide. So if you then load up your browser and point it towards where you've put CodeIgniter you'll see something like this:

Step 20: Creating the Hook

Ok, now we need to modify CodeIgniter's output by using a hook. A hook is simply a method that allows us to 'hook' into the core of the system without directly editing CodeIgniter's code. So we need to edit CodeIgniter's config so that we can enable hooks, register our hook and then write our hook. Within your CodeIgniter directory open up "system/application/config/config.php", scroll down to line 91 and change the variable to true:

  1. $config['enable_hooks'] = TRUE;

Now open up "hooks.php" in the same folder and add the following code before the '/* End of file ... */' line (so add it to line 13):

  1. $hook['display_override'] = array(
  2. 'class' => 'AMFPHP',
  3. 'function' => 'output',
  4. 'filename' => 'amfphp.php',
  5. 'filepath' => 'hooks'
  6. );

What we're telling CodeIgniter here is that we want it to override it's display output method and instead re-route everything to our hook class called 'AMFPHP' within the 'hooks/amfphp.php' file. Since that fine doesn't exist, create the file "system/application/hooks/amfphp.php" and use the following code:

  1. if ( !defined('BASEPATH') ) exit( 'No direct script access allowed' );
  2. class AMFPHP
  3. {
  4. function output()
  5. {
  6. if ( !defined( 'AMFPHP' ) )
  7. {
  8. $CI =& get_instance();
  9. $CI->output->_display( $CI->output->get_output() );
  10. }
  11. }
  12. }
  13. /* End of file amfphp.php */
  14. /* Location: ./system/application/hooks/amfphp.php */
  15. ?>

All we're doing here is telling CodeIgniter to write its output. This way we're controlling the system by checking to see we're not making a request via AMFPHP so it can display the code as normal, otherwise AMFPHP won't work.

Step 21: Creating the Controller

Ok we're nearly there, all we have to do now is create our controller so that we can return the data back to the AMFPHP gateway. Create a file called "system/application/controllers/main.php" and paste this in the file:

  1. if ( !defined('BASEPATH') && !defined('AMFPHP') ) exit( 'No direct script access allowed' );
  2. class Main extends Controller
  3. {
  4. function __construct()
  5. {
  6. parent::Controller();
  7. }
  8. function index()
  9. {
  10. global $value;
  11. $value = $_POST;
  12. }
  13. }
  14. /* End of file main.php */
  15. /* Location: ./system/application/controllers/main.php */
  16. ?>

All we're going to do here is just return the variables we sent to AMFPHP as an array rather than as the string we sent. This will show you how magic AMFPHP is, the fact that you don't need to serialise objects before sending them back and forth is a great asset and speeds things up.

Step 22: Configuring AMFPHP

We're really nearly there! We now need to create a service that will invoke the CodeIgniter framework. This service will act in the same way as the "HelloWorld.php" service we created, but will instead fire up CodeIgniter so that we can access all of it's goodness. Create a new file inside your AMFPHP directory "services/CI.php" and paste in the following code:

  1. include_once( AMFPHP_BASE . 'shared/util/MethodTable.php' );
  2. class CI
  3. {
  4. function execute($path='', $vars=false)
  5. {
  6. global $value;
  7. define( 'AMFPHP', true );
  8. if ( $vars && is_array( $vars ) )
  9. {
  10. $_POST = $vars;
  11. }
  12. $_SERVER['PATH_INFO'] = $_SERVER['QUERY_STRING'] = $_SERVER['REQUEST_URI'] = '/' . $path;
  13. require_once( '../../codeigniter/index.php' );
  14. return $value;
  15. }
  16. }
  17. /* End of file CI.php */
  18. /* Location: ./services/CI.php */
  19. ?>

We define a function called "execute()" where we will pass the URL of our 'spoof' CodeIgniter method (if you're confused, it works on the basic principle of MVC URLs where by the application responds to URL patterns like http://host/controller/function/value1/value2/valueN, you can find out more here: http://codeigniter.com/user_guide/general/urls.html) and the values for that method. In then requires the 'index.php' file of your CodeIgniter app, make sure it can find this by the way!

Step 23: One Last Configuration Item

You're so close! One last thing to do is to tell CodeIgniter to only explicitly show errors, so open up the "index.php" file in your CodeIgniter directory and edit line 12 to be:

  1. error_reporting(E_ALL ^ E_NOTICE);

We're telling PHP not to display any notice errors, only full fat ones!

And that's the PHP side down! All we need to do now is go back to our ActionScript code and change the method, so open up your app's base file and change the method and values we send like so:

  1. private function handleButtonClick(e:MouseEvent):void
  2. {
  3. service.send( 'CI.execute', 'main', [ textField.text, 'another var', 1, 0xFFFFFF, { data: 'test', data2: 'test2' } ] );

Here we're sending an Array rather than a number of values. This is easier and means that you can have your gateway receive the method (as the 'URL') and the data as an Array.

There we have it! You've just dug deep into AMFPHP from both the ActionScript and PHP side! Congrats!

Conclusion

While some people may ask what's the point of this, there will be times within a professional developers life where he or she will need to use AMFPHP when it comes to sending and receiving data. And while XML and JSON work just fine, the beauty with AMFPHP is that when you've set up the classes (as you just have) you don't have to worry about the joys of serialising data and parsing it at the receiving end as it's all done for you!

source from: flash tuts+

0 nhận xét:

Post a Comment

 

Blogroll

Site Info

Text

Tut - Designer Copyright © 2009 WoodMag is Designed by Ipietoon for Free Blogger Template