AJAX Chat Tutorial Part 4.2: IndexController MessageAction() and JSON Encoding
Encoding New Messages to JSON
As we've already seen in our section on SimpleXML we already have the means to extract from our XML all messages stored since the user's last refresh/message submission. Using XPATH this is as simple as:
$xml = simplexml_load_file('./data/chat.xml');
$lastRefresh = '1161790302';
/*
* Select all message elements with a timestamp greater than 1161790302
* i.e. XPATH "/chat/message[timestamp>1161790302]"
*/
$newMessages = $xml->xpath('/chat/message[timestamp>' . $lastRefresh . ']');
foreach($newMessages as $message)
{
$timestamp = $message->timestamp;
$author = $message->author;
$text = $message->text;
echo 'Date: ', date($timestamp), ' Author: ', $author, ' Message: ', $text, '<br />';
}
Going a step further; in order for us to send these new messages to the browser we need to add them to an array which is then encoded into the JSON format. Building the response will require using XPATH to gather the data and then using a for() statement (where $i is the number of messages) to build a PHP array.
We can then encode this PHP array in the JSON format using Zend_Json".
/**
* $xml already holds our XML data
* The last refresh timestamp is stored in user's Session Data
*/
$newMessages = $xml->xpath('/chat/message[timestamp>'
. $_SESSION['chat_lastrefresh'] . ']');
$newMessageCount = count($newMessages);
$phpMessageArray = array();
/*
* Build a PHP array of new messages.
*
* We must cast each XML element to String (since it's only
* done automatically if we attempt to use the element as a string
* and PHP autmatically calls __toString() on a SimpleXMLElement
* object, e.g. echo().
*
* Escape all expected text output based on user input for
* htmlentities. (Security precaution.)
*/
for($i=0;$i<$newMessageCount;++$i)
{
$phpMessageArray[$i]['author'] =
htmlentities((string) $newMessages[$i]->author, ENT_QUOTES, 'utf-8');
$phpMessageArray[$i]['timestamp'] =
(string) $newMessages[$i]->timestamp;
$phpMessageArray[$i]['text'] =
htmlentities((string) $newMessages[$i]->text, ENT_QUOTES, 'utf-8');
}
/*
* Encode the PHP array into JSON
*/
require_once 'Zend/Json.php';
$responseJSON = Zend_Json::encode($phpMessageArray);
The Updated IndexController
Keeping the above in mind, we can revisit our IndexController's MessageAction() method again. In this case, we add the above code block and also ensure we reset the user's $_SESSION['chat_lastrefresh'] value to the current timestamp.
With this in place, we have substantially completed the server side coding for basic operation of a chat application. At a later time we will add support for letting a user set a screen name.
class IndexController extends Zend_Controller_Action
{
public function IndexAction()
{
/*
* Get View from Registry
*/
$view = Zend_Registry::getInstance()->get('view');
/*
* Set a default Screen Name
* Once a user sets their own screen name
* it will be stored in the user session
*/
if(!isset($_SESSION['chat_screenname']))
{
$view->screenName = 'NewUser';
}
else
{
$view->screenName = $_SESSION['chat_screenname'];
}
/*
* Lets use a Response object to collate
* any output.
*/
$this->getResponse()->setBody(
$view->render('index.tpl.php')
);
}
public function MessageAction()
{
/*
* Check for Session value chat_lrefresh (last refresh timestamp)
* otherwise set it to time() - 120 (2 minutes earlier)
*/
if(!isset($_SESSION['chat_lastrefresh']))
{
$_SESSION['chat_lastrefresh'] = time() - 1200;
}
/*
* At this point the user has submitted a new chat message
* without setting a screen name. Since the default is in
* place, we'll simply add to the session for future use.
*/
if(!isset($_SESSION['chat_screenname']))
{
$_SESSION['chat_screenname'] = 'NewUser';
}
/*
* Grab the message text from the relevant superglobal variable
*/
$message = isset($_GET['message']) ? $_GET['message'] : '';
/*
* The message value must exist and not exceed 255 characters
* User data must always be filtered and validated.
* We allow empty messages, and assume an empty message
* is a simple request to refresh the chat panel without
* adding a new user message.
*/
if(strlen($message) > 255)
{
throw new Exception('Invalid message! Must be 255 characters or less.');
}
/*
* Create the XML file if not existing! Directory must be writeable...
*/
if(!file_exists('./data/chat.xml'))
{
$newXML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><chat></chat>';
file_put_contents('./data/chat.xml', $newXML);
}
/*
* Load the current XML store
*/
$xml = simplexml_load_file('./data/chat.xml');
/*
* Only store the message if it's not empty!
*/
if(!empty($message))
{
/*
* First we entitise special characters so they do
* not create future XML parsing errors. XML has several
* invalid characters like <>&"' which can only be used
* as entities, or when placed in a CDATA section.
*
* Although not fatal, SimpleXML will simply reverse
* entities for quotes and single apostrophes before
* writing the XML to file. The XML will therefore
* be illegal, but will not create any problem for
* SimpleXML when parsing. Odd behaviour...;)
*/
$entity_message = htmlspecialchars($message, ENT_QUOTES);
/*
* Add new <message> element to XML file
*/
$newMessage = $xml->addChild('message');
/*
* Add our data to the new <message> element
*/
$newMessage->addChild('author', $_SESSION['chat_screenname']);
$newMessage->addChild('timestamp', time());
$newMessage->addChild('text', $entity_message);
/*
* Write updated XML to file!
*/
$xml->asXML('./data/chat.xml');
}
/**
* $xml already holds our XML data
* The last refresh timestamp is stored in user's Session Data
*/
$newMessages = $xml->xpath('/chat/message[timestamp>' . $_SESSION['chat_lastrefresh'] . ']');
$newMessageCount = count($newMessages);
$phpMessageArray = array();
/*
* Build a PHP array of new messages.
* We must cast each XML element to String (since it's only
* done automatically if we attempt to use the element as a string
* and PHP autmatically casts to String, e.g. echo().
*/
for($i=0;$i<$newMessageCount;++$i)
{
$phpMessageArray[$i]['author'] = (string) $newMessages[$i]->author;
$phpMessageArray[$i]['timestamp'] = (string) $newMessages[$i]->timestamp;
$phpMessageArray[$i]['text'] = (string) $newMessages[$i]->text;
}
/*
* Reset the user's Session chat_lastrefresh value
*/
$_SESSION['chat_lastrefresh'] = time();
/*
* Encode the PHP array into JSON
*/
require_once 'Zend/Json.php';
$responseJSON = Zend_Json::encode($phpMessageArray);
/*
* Set the Response for the user testing this
* or for the AJAX handler on the client.
*/
$this->getResponse()->setHeader('Content-Type', 'text/plain');
$this->getResponse()->setBody($responseJSON);
}
}
If you make additional requests through the browser as before, you should see that the XML output has been replaced with a block of JSON encoded data in the form:
[{"author" : "NewUser", "timestamp" : 1161876001, "text" : "HelloWorld"}]
On the client side, this will simplify using the data since we can use the Javascript eval() procedure to evaluate this string into Javascript structures. This is far simpler than sending XML and using the Javascript DOM to process it. We'll see how simple this is in the next section where we take a look at our client side Javascript.
On security, attempting a common XSS exploit like adding a message of "I'm an annoying spacemonkey! <script>window.alert('XSS');</script>" which contains a javascript function, should in any JSON notation be replaced with an entitised version. This ensures it's printed literally and can't run on another user's browser.
搜索更多相关主题的帖子:
zend framework