I was excited to read about the release of AMFPHP 1.9 in February, but I didn't need to install it until a couple of weeks ago, when I was working on refactoring my Lazy Loading Tree example to take advantage of Maté. I wound up burning up almost an entire day due to permission problems on AMFPHP that were difficult to track down.
It turns out that something about my setup, whether it's Windows 7 or IE 8, caused a problem with the zip file when I downloaded it. The first thing I needed to do was to go into the properties of the zip file and hit the "Unblock" button. I had no idea that the "blocked" setting on a zip file could affect what are, in effect, text files inside.
But even after I did that, I had problems with the AMFPHP files that were in subdirectories, and no matter how I changed security settings on it, I couldn't find anything that would work. It seems that somehow Windows was encrypting the folders as they were being extracted. When I extracted them on a thumb drive, it couldn't apply the encryption. When I copied those extracted files over to my WAMP installation, AMFPHP worked perfectly.
Hope this helps someone avoid blowing out a day on this.
Installing AMFPHP to Windows 7
english mobileDebugging Responder Result Functions
english mobileLots of times when we use AsyncTokens with Responders, something will go wrong between when the call is made and when it returns. Usually if there is a problem, the fault handler will fire. Because of this, it's fairly straightforward to get information on what has happened in the fault responder function by looking at the FaultEvent's properties, such as the fault property.
But what about when there's an error and the Result handler is the one that fires? This does happen. For example, the infamous Access "Operation must use updateable query" error message in access triggers the result handler.
One of the first things I try to do when there is an error with a database call is to reconstruct the exact call that was made, so I can make the same call directly from a browser. It's not exactly intuitive where to find this information in the ResultEvent that is the parameter in the result handler, but, with a little digging, I found it.
It turns out that AsyncToken has a property called "Message," which is actually a HTTPRequestMessage. HTTPRequestMessage has a "body" property that is described this way: The body of a message contains the specific data that needs to be delivered to the remote destination. This, apparently, is a circuitous way of saying that this is the actual request object that was sent in the HTTPRequest.send() method that returned this token.
Hope this helps someone!
Lazy Loading Tree example file posted
english mobileA lot of people on the flexcoders yahoo list have asked about creating a tree that uses a custom DataDescriptor to allow lazy loading of the data. In other words, it gets the first level of data when the application loads, but does not call for the children of the branches until each node is open. I had a bit of down time this week, so I thought I'd put one together.
This example actually has two ways to lazy load. The first is the obvious one using a custom DataDescriptor that is designed to work with any class that is written to the LazyLoading Interface. The major things it does are
- Tell the tree that the branch "intends" to have children even when no children have been loaded.
- When a branch is loaded, it tells the LazyLoading to load its children.
This could be slicker, in that I could have extended the Tree to listen for the load to finish, but I chose not to do that.
The second way that the lazy loading can be done is to not use the DefaultDataDescriptor at all, but to instead let the instances of Classification load themselves. This code is in place in the hasChildren setter--you'd just need to uncomment it.
This example is unusual in that it not only uses RemoteObject to retrieve the data, it contains the php files and instructions to recreate the mySQL database, so even if you're mainly looking for an AMFPHP example, you might find it helpful.
The bad part about about this example is that I can't post the working example due to reasons I've already blogged about, so you'll just have to look at this pretty picture and take it on faith that if you follow the instructions below that you've got a really good shot at producing a working lazy loading tree application. (As of 1-14-09, you can click this image to see the working example with View Source enabled. See note at end of post.)

- First, install AMFPHP to your server. I'm going to assume in these steps that you did it to the "flashservices" directory referred to in the installation instructions. I'd suggest that you test the service browser at this point to identify any showstoppers before you get too far.
- Next, download the flex project zip file and import it to your Flex builder workspace.
- Download the rtf translation of the mySQL database table export. This is simply the ".sql" file created from the table export with the extension changed to rtf and some formatting added to make your life easier.
- Download the zip with the php services files. Extract them and place them in the flashservices/services directory. If you didn't install AMFPHP to the flashservices directory, adjust accordingly.
- Create a new mySQL database called 'RemoteServiceTest'.
- Open the classification.rtf file you downloaded earlier.
- Log into phpAdmin or whatever you're using to administer your mySQL databases. Select your new database, and go to the "SQL" panel. In the classification.rtf file, select all of the text that is bold and red and copy it. Paste it into the sql window and run the query. Repeat with the blue, bold text (but only once with each block of text). I got an error with this one about a duplicate key, but it all seemed to come in just fine.
- Adjust your project properties so that your debug and release builds are exported to the right location relative to the flashservices directory. Alternatively, adjust the setChannels() function in the ClassificationGetter to point to where your service actually is.
- Enjoy!
Updated 1-14-09:
Reader David Becarra in Mexico donated a subdomain running PHP 5 so that I could show a running example of this. I don't think that he's accepting new hosting subscribers at the moment, but his website is http://www.misitioweb.net/. BIG THANK YOU, David!!
Before you start with AMFPHP...
english mobileLast week, I had some down time, so I decided to create an AMFPHP example and post it to my website. I have a Windows account with my web host, but they do support php, so I figured it would work. I successfully created the example on my local machine, which I will blog about tomorrow, but I won't be posting the working swf, unfortunately.
Let me tell you why, so that if you need to post to a server that someone else controls, you don't go off all half cocked like I did and make an AMFPHP application that you then can't put out on the Internet.
My first step was to try to export my mySQL database and import it to the server's database. Long story short, the versions didn't match, so I had to find a workaround, which I'll be detailing in the next post.
When I tried to test the service browser, it didn't work. I did a web search for the error I was getting, and I only found one useful link, which suggested that the problem could possibly be with permissions on the folder that contains my mySQL databases. So I went back and checked an old app I had written that writes to a different database. That one totally froze on write. The problem was exacerbated because some time between the last time I worked on a PHP project on my site and now, they'd turned off "display errors." This made the whole problem difficult to debug.
I opened a problem ticket with the web host and got a guy who assumed I was a Jenny Graphic Designer (apologies to serious developers with the name Jenny, but I think you know the attitude I mean) who can barely spell PHP, and so I wasted quite a lot of time getting him to pass me to someone who actually understood the phrase "make sure the default Internet User account has read/write/execute priveleges to the folder containing my database." So once that was resolved, AMFPHP still didn't work.
So, I posted to flexcoders and it seems that the latest AMFPHP requires PHP 5.2.3, and my host had stalled out somewhere in 4. Bottom line, I can't post a working example unless my host upgrades, I switch hosting plans/hosts, or I can find a remoting option which will work on PHP 4.
In summary, here are some things you should do before embarking on an AMFPHP project that will be deployed to a commercially hosted site:
- At least know if the version of mySQL on your development and production servers are the same and be prepared to work around it if they're not.
- Create a test database on your production server and make sure you can run a simple INSERT query into it from a php page to make sure the appropriate permissions are set on the directory with the database.
- Make sure that the right version of PHP is running on your server.
More thoughts on Remoting
english mobileAs I mentioned in my last post, I've been working with AMF for the first time this week. I'd read about using AsyncTokens to be able to determine which call to a service resulted in which result, so from the very beginning I'd intended to use this. The problem I had was that when I looked at the docs for RemoteObject, I couldn't find anything that actually returns an AsyncToken. So at first I figured I couldn't do it.
But here's the thing. All of my commands to access the RemoteObject use static properties and methods, which essentially means that for each service there's only one RemoteObject that gets used from any object in the application that needs to call that service. These different parts of the application didn't know about each other, and I didn't want them to. Nor did I want to make these commands refuse any new calls before the old one was finished. So I had to make this AsyncToken thing work, or completely change the architecture of how I was calling AMF.
Finally, I found the documentation for mx.rpc.remoting.Operation (don't ask me how). It turns out that a remoting Operation is the method on the service that you're calling. When you call a RemoteObject operation, you're invoking the Operation Class's send method, so it seems that creating an AsyncToken from a RemoteObject call is as simple as myToken:AsyncToken=myRemoteObject.myService(whateverParameters). I'm not sure exactly how you're supposed to figure that out unless you happen to luck into the right information like I did, since there's no direct link between the language reference on RemoteObjects and that on Operation, and it's only referred to in passing in a parentetical expression in the RemoteObject "how to" type documentation.
One of my command classes needed to be able to accept a result and fault function from the instance that was calling the service, so I then needed to find a way to set a Responder on the token. The first thing I tried was simply to push a new Responder onto the token's responders Array. Let me save you a little time and tell you in advance this doesn't work. Instead, you need to use the AsyncToken's addResponder() method.
Thoughts on remoting
english mobileThe past week or so, I've been doing something that's new to me--retrieving objects using AMFPHP and integrating them with Flex. In the meantime, I've found that there are some things that are hard to find out about the whole process, so I thought I'd make a note of them here.
First, all the examples I could find easily used NetConnection. The code on these was so similar that I think there was one basic example that has been modified slightly by several different people and posted. I had better luck using RemoteObject, and I finally found an example of this.
The next thing I ran up against is that you have to use a RemoteClass alias on your Flex class that defines the object that is receiving the data. The docs are a bit misleading in that it sounds like you only need to use this if you are sending to a remote service and the service is Java based. However, if you read a bit further (I didn't at first), you can see that this applies pretty much to any AMF mapping. If you are sending data only from the service to Flex and you're not sending data back from Flex to the service, the only thing that matters about this alias is that it must match the $_explicitType variable in the object on the PHP side.
I also discovered a few things on my own. My classes extended EventDispatcher, and at first they didn't deserialize properly. We speculated that the fact that the class wasn't just a subclass of Object might have been the problem, but it turns out that extending EventDispatcher doesn't break serialization. My classes also had their own methods and some additional properties. We speculated that these were causing the problem, but it seems that serialization ignores these things. I think that if I were sending data back to PHP through the RemoteObject, I'd need to mark those properties as transient.
I think the real reason the serialization wasn't working was the last thing I discovered, which is that you have to have a dummy variable typed as your custom class in the same class as the RemoteObject that you're using to retrieve the values. This makes sure that the class definition gets compiled into the class where it can be used.
Oh, and one more thing... To a PHP programmer who's not familiar with ActionScript, a setter looks like something he can't serialize to. So it's probably better to just write up a description of the properties you're expecting to have set than sending the PHP person your actionscript class as is.
