Interacting With PHP and MySQL
This article outlines an approach for getting shockwave to read and write data to a mySQL data. Although there are some Xtras that let Director connect directly to a MySQL data base, this article will focus on using PHP to connect to connect with the database, and Shockwave will interact with PHP using some netLingo commands.

Requirements
This article assumes you have access to a server (even if just your local machine) with PHP (4.3 or later) and MySQL 4 (or later?). If you don't, and you are using MacOSX, then have a look at the packages provided by Server Logistics - although note that they haven't been updated for Tiger yet (they run a treat with OS 10.2 and 10.3). Also consider some (free) MySQL tools like CocoaMySQL, an Open Source MySQL Database Manager that is very easy to use.
The Demo Application
The demo application we are going to build is a simple 'guestbook'. The first step is to create a table to store the data. Next we will create some PHP pages to add to and get data from the database. Finally, we'll create the Shockwave movie that interacts with the PHP pages.
The MySQL Database
The first step is to create a MySQL database and add the following table:
# SQL to create table
CREATE TABLE `comments` (
`id` int(11) NOT NULL auto_increment,
`who` varchar(128) NOT NULL default '',
`fromURL` varchar(255) default '',
`postedOn` datetime NOT NULL default '0000-00-00 00:00:00',
`comment` text NOT NULL,
PRIMARY KEY (`id`)
) TYPE=MyISAM
If you are usign CocoaMySQL or a similar GUI tool for working with MySQL, you can use the comments.SQL file (included with the downloads) to create the table (just be sure you have the correct database selected first).
The PHP Pages
The next step is to create some PHP pages - one to get data from the database, and another to add a record to the database. To do this, we need to get PHP to create a database connection and execute a SQL query.
Rather than having to write out the code to connect and query the database with each PHP file, I have chosen to use a standard class which is included in each PHP page. Below is an edited version of this PHP class to handle a connection to a MySQL database. Put this script into a file called "db.inc.php" (or name it whatever you like).
//db.inc.php
class DB {
/*
Edited version of Luke's MySQL DB class (v.1.0.0)
Note - you will need to edit some properties below,
inserting your username and password, database name
and host (if not localHost).
*/
function DB() {
$this->host = "localhost"; // EDIT THIS
$this->db = "test"; // EDIT THIS
$this->user = "usernamehere"; // EDIT THIS
$this->pass = "dbpasswordhere"; // EDIT THIS
$this->link = mysql_connect($this->host, $this->user, $this->pass);
if (!$this->link) die;
mysql_select_db($this->db);
}
function GetXML($q='') {
// return a recordset as XML
// returns an empty string if there is an error
$errResult ="";
if (!$q) return $errResult;
$rs = mysql_query($q, $this->link);
if(!$rs) return $errResult;
if (mysql_num_rows($rs) > 0) {
$doc = domxml_new_doc("1.0"); // new XML doc
$root = $doc->add_root("recordset");
while ($a = mysql_fetch_assoc($rs)) {
$record = $root->new_child("record","");
foreach ($a as $k => $v) $record->new_child("$k","$v");
}
return $doc->dumpmem();
}
return $errResult;
}
function Query($q='') {
// Make a query, returning false if there is no result;
if (!$q) return false;
$result = mysql_query($q,$this->link);
if(!$result) return false;
return $result;
}
}
Note this PHP script uses the XML Extension for PHP 4.3. Included in the downloaded files is a version that manually constructs the XML (no extensions needed). The version of the GetXML function that does not rely on the domxml extension looks like this:
PHP
function GetXML ($q='') {
// return a recordset as XML (MANUALLY CONSTRUCTED)
// returns an empty string if there is an error
$errResult ="";
if (!$q) return $errResult;
$rs = mysql_query($q, $this->link);
if(!$rs) return $errResult;
$doc = "";
$doc .="<recordset>";
while ($a = mysql_fetch_assoc($rs)) {
$record = "";
foreach ($a as $k => $v) {
$v2 = $this->_xmlentities($v);
$record .="<$k>$v2</$k>";
}
$doc .="<record>$record</record>";
}
$doc .= "</recordset>";
return $doc;
}
function _xmlentities($string, $quote_style=ENT_QUOTES) {
// function from au.php.net user comments
static $trans;
if (!isset($trans)) {
$trans = get_html_translation_table(HTML_ENTITIES, $quote_style);
foreach ($trans as $key => $value)
$trans[$key] = '&#'.ord($key).';';
// dont translate the '&' in case it is part of &xxx;
$trans[chr(38)] = '&';
}
// after the initial translation, _do_ map standalone '&' into '&'
return preg_replace("/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,3};)/","&",
strtr($string, $trans));
}
This script can be used to create a "DB" object with methods for executing a SQL statement (such as an INSERT or UPDATE), and another for returning an XML representation of a recordset.
Getting Data - the PHP Script
To create a PHP page that returns all the records in the comments table we created above, you would write a PHP page like this:
// getcomments.php
require_once("db.inc.php");
$DB = new DB;
$SQL = "SELECT * FROM Comments ORDER BY postedOn DESC"
echo $DB->GetXML($SQL );
The data returned from a MySQL database to PHP will be text which is written to the browser's output. In this case, I have chosen to format the output data as XML. I could have chosen some other format (a combination of commas and linebreaks to indicate fields and records), or even format so that lingo's value() function might be able to interpret it as a list. However, since the comments might include commas and returns, XML would be the safest format (and beside which, we can use the XML parser to parse the data without having to write our own parser).
Inserting Data - the PHP Script
To insert data into the database, we would want the PHP page to do some validation on the data. The following PHP page checks that a user name and comment string has been provided before attempting to add it to the database:
// addcomment.php
require_once("db.inc.php");
// validate the provided data
$who = $_POST['who'];
if ((!$who) || (strlen($who) < 2)) {
echo "Error: invalid name '$who'";
exit;
}
$comment = $_POST['comment'];
if ((!$comment) || (strlen($comment) < 2)) {
echo "Error: invalid comment";
exit;
}
$from = $_POST['from'];
$sql = "INSERT INTO comments (who, fromURL, postedOn, comment)
VALUES ('$who', '$from', NOW(), '$comment')";
$DB = new DB;
$result = $DB->QUERY($sql);
if ($result) echo "OK";
else echo "Error";
Checking Everything is working.
If you open getcomments.php in a browser, you should see the data returned by the PHP script as XML (you might need to select 'View Source' in your browser).
To test that the addcomment function is working as expected, create a simple HTML page with this form:
<form name="form1" id="form1" method="post" action="setcomment.php">
<p>Name:<input type="text" name="who" /></p>
<p>URL:<input type="text" name="fromURL" /></p>
<p>Comment:<textarea name="comment"></textarea></p>
<p><input type="submit" name="Submit" value="Submit" /></p>
</form>
This form posts data to the setcomment.php page created earlier. The setcomment.php page looks for the data in the $_POST 'superglobal' (which requires PHP 4.1 or later; for older versions of PHP, use $HTTP_POST_VARS).
If this is working as expected, then we can be confident that the PHP - MySQL side of things are working. The next step is to get Shockwave to play nice with PHP.
The Shockwave
Rather than using the HTML form, in Shockwave we are going to post the data using the postnettext function.
Using the NetOp.transaction scripts discussed elsewhere, we can create a simple behaviour to test that everything is working:
LingoBehaviour
on exitFrame me
go to the frame
end
on beginSprite (me)
FormData = ["who": "Luke", "url": "", "comment": "This is a test comment"]
theURL = "http://www.lingoworkshop.com/articles/mySQL/setcomment.php"
NetOp = script("NetOp.transaction").new(theURL, FormData)
NetOp.AddListener(me)
NetOp.Start()
end
on NetTransactionComplete (me, sender,theData)
if theData.error = 0 then
put "SERVER RESPONSE: " & thedata.text -- should be "OK"
else
put "NETWORK ERROR: " & sender.GetErrorDescription(theData.error)
end if
end
Create a new empty movie and add this behaviour to a frame script, and hit play. If the data was successfully posted, you should see "SERVER RESPONSE: OK" in the message window.
To retrieve the data, we can do one of two things: Use network lingo get get the XML string printed by the getcomments.php page and then parse this as a string, or (2) use the XML Xtra to parse the URL. Here is an example of the first approach:
Lingo Behaviour
on beginSprite (me)
theURL ="http://www.lingoworkshop.com/articles/mySQL/getcomments.php"
NetOp = script("NetOp.transaction").new(theURL)
NetOp.AddListener(me)
NetOp.Start()
end
on NetTransactionComplete (me,sender,data)
if data.error = 0 then
XMLStr = data.text
XMLParser =script("XMLParser").new()
put "THE DATA: " & XMLParser.ParseBuffer(XMLStr)
else
put "ERROR: "&sender.GetErrorDescription(data.error)
end if
go to the frame + 1
end
And here is an example of the second approach:
Lingo Behaviour
on beginSprite (me)
XMLParser =script("XMLParser").new()
XMLParser.AddListener(me)
theURL = "http://www.lingoworkshop.com/articles/mySQL/getcomments.php"
XMLParser.Parse(theURL)
end
on XMLParsed (me, aList, anError)
if voidP(anError) then
put "THE DATA (METHOD 2): " & aList
else
put "ERROR PARSING XML URL: " & anError
end if
end
Both these behaviours use an "XMLParser" which is a simple script providing a wrapper for the XML Xtra. Here is the XML Parser script:
Script "XMLParser"
-- XML Parser Script 1.0
-- Note: this uses the newer XML Xtra that came with DMX2004
-- If you are using Director MX, you can get the xtra from your
-- shockwave installation (the Xtra seems to work fine with this
-- older version of Director)
property xmlparser, err
property Listeners
on new (me)
me.err = ""
me.Listeners = []
return me
end
on Destroy (this)
me.Listeners.deleteAll()
end
on AddListener (this, obj)
-- Any object that wants to receive callback messages from this
-- widget needs to add itself as a listner object.
if not(me.Listeners.getOne(obj)) then
me.Listeners.add(obj)
end if
end
on RemoveListener (this, obj)
-- Any object that wants to stop receiving callback messages from
-- this widget can remove itself from this widget's list of listeners.
if me.Listeners.getOne(obj) then
me.Listeners.deleteOne(obj)
end if
end
on Parse me, thisURL
-- Start parsing. A XMLParsed message will be sent to all listeners
-- when the parsing is finished.
me.xmlparser = new(xtra "xmlparser")
err = me.xmlparser.parseURL(thisURL, #parseDone, me)
return err
end
on ParseBuffer me, thisStr
-- parse the string buffer, returning the result immediately.
me.xmlparser = new(xtra "xmlparser")
me.xmlparser.parseString(thisStr)
return xmlparser.makeProplist()
end
on parseDone me
tmp = timeOut(me.string).new(1, #MakeCallback, me)
end
on MakeCallback (me, aTimer)
me.err = xmlparser.getError()
aTimer.forget()
theList = xmlparser.makeProplist()
call(#XMLParsed, me.Listeners, me, theList, me.err )
end
The advantage of using a network operation outlined in the first approach to download the XML is that you can provide some feedback on how the network operation is going (and the NetLingo seems to give better errors if things go wrong). On the other hand, using theXML Parser involves writing less code.
Luke Wigley said
Hi,
For a fairly basic XML output, you could modify the DB Class described above as to replace the GetXML function (which uses the XML extension) to something like this (which manually constructs the XML without any extensions). The code doesn't display well in this comment field - I'll post it up shortly.
- Luke
Posted 16th August, 2005
Derek said
I wanted to note that my last post was incorrectly worded, as I had just noticed (don't want to confuse anyone).
What I had meant to say, was that the GET of the data from MySQL using the DB class and the domxml_new_doc() function is what was causing the problem, as this function is not supported by php.net any longer and adding support for the extensions is not something that most anyones servers will include.
Great job on this tutorial by the way, and looking forward to what you have in mind, Luke. I noticed that the director files for download are missing, but I am currently working on replacing the GetXML function myself...maybe we can compare notes.
I've been looking at implementing better database control for director for some time, and your tutorial here has brought even more light to the subject. It's appreciated.
Let me know when you have the files available again, and maybe we can swap some techniques.
Posted 20th August, 2005
Luke Wigley said
Hi,
I've written a version og GetXML that just manually constructs a basic XML. The main isssue - as far as I can tell - is ensuring that 'xml-unfriendly' characters are properly encoded so that Director's XML parser doesn't choke. The _xmlentities() function (snaffled from the php site) seems to work ok - but I haven't really tried hard to break it yet.
I put the files back up with the (correct name this time).
Thanks again for the feedback
- Luke
Posted 22nd August, 2005
Andreas Gysin said
I just want to say thank you for this excellent article.
Posted 11th February, 2006
Raru said
Great Article Luke.........
Keep good work up in director community......
Posted 18th March, 2006
Bo said
First -- Thanks so much for this excellent resource. I've been poring through your code all day and I've learned a huge amount.
2) I am a total noob when it comes to XMLinteracting with Director, but I think I discovered a glitch when trying to get this example to work in MX 2004. That is:
it seems that the makePropList method does not exist in at least the version of the xmlparser I'm using (presumably the latest). I believe the correct method is makeList
With that in mind a few lines in the XML parsing script need to be changed.
Thanks again...
Posted 14th May, 2006
Luke said
Hi,
Thanks for the kind words.
I'm pretty sure the XMLParser Xtra included with DMX 2004 has the makePropList method - earlier versions didn't. You can check your version and the methods available by typing the following in the message window:
put xtra("XmlParser").interface()
You should see something like this:
-- "xtra XmlParser -- version 10.1.0.r11
new object me
-- XmlParser Xtra --
parseString object me, string data -- parses the buffer
parseURL object me, string url, * -- parses the specified url
doneParsing object me -- returns true if it's done parsing a URL
ignoreWhitespace object me, int ignore -- toggles whether to ignore whitespace
getError object me -- returns the error string (if any) generated when parsing
makeList object me -- make a lingo list based on the XML document
makePropList object me -- make a lingo property list based on the XML document"
Posted 14th May, 2006
Bo Monroe said
Hmmm. You are right, of course. Both methods are listed.
But the Director documentation for makeList seems to contradict this (and makePropList is unmentioned):
---snip---
makeList()
Usage
--Lingo syntax
parserObject.makeList()
// JavaScript syntax
parserObject.makeList();
Description
Function; returns a property list based on the XML document parsed using parseString() or parseURL().
Parameters
(etc)
---snip---
So I'm totally confused... the results I got with makeList seem to be a property list...:
[#name: "recordset", #attributes: [:], #child: [[#name: "record", #attributes: [:], #child: [[#name: "id", #attributes: [:], #child: [], #chardata: "1"], [#name: "name", #attributes: [:], #child: [], #chardata: "Photovoltaic-NEMS06"], (etc.)
meanwhile, when I use makePropList nothing seems to happen.
Posted 15th May, 2006
Bo said
Upon further testing, head scratching, muttering of curses, etc... makePropList absolutely DOES work, the list it generates seems to be a proper property list, while I'm not sure what makeList is returning...
Posted 15th May, 2006
Derek said
This is a great tutorial on how to send data to MySQL correctly using Director, but because of the domxml_new_doc() function that you have included in the POST of the data to the database, it does not work correctly unless you have complied PHP to use those extensions, which by the way, is an extreme pain in the rear (and the function has been abandoned by php.net)...take a look here: http://us3.php.net/domxml
I was wondering if anyone else was having this problem, and if there is a work around for it, as I would love to get this working corretly, as it poses a great work around for using php / mysql during the shockwave runtime environment, and not being stuck with only Arca or similiar in simply projectors and auth mode.
-Derek
Posted 16th August, 2005