Wednesday, May 30, 2007

Initial text of the book complete!

Current word count: 73,430

I've absolutely no idea what that means in terms of pages. Doing a quick wc -l text/*.txt it looks like an average of about ten words per line, but I still haven't a clue how many pages that works out to, though I suppose I'll find out soon enough.

I still have a few figures to create, one or two code samples to write, an appendix to write, and lots of edits to come shortly, but I've finished the initial text of the book!

Labels:

Sunday, May 27, 2007

A PDOIterator class using SPL

While writing about the scalability issues of using function calls like file_get_contents and PDOStatement::fetchAll, I wrote the following PDOIterator class, which I have every intention of using for a while. It makes it very easy to keep queries in the application logic while keeping the retrieval of the results in the output, without using any database driver calls. Because it implements the Iterator interface from SPL, the template can use a foreach loop just like it would if the application had passed the results from PDOStatement::fetchAll.

class PDOIterator implements Iterator {
 /**
  * The PDO connection object
  */
 protected $database;
 protected $statement;
 /**
  * The query to run on the first iteration
  */
 protected $query;
 /**
  * Optional parameters to use for prepared statements
  */
 protected $parameters;
 /**
  * The current record in the results
  */
 protected $current;
 /**
  * The row number of the current record
  */
 protected $key;
 /**
  * A boolean as to whether it has more results
  */
 protected $valid;

 /**
  * Forward-only cursor assumed and enforced
  */
 public function rewind() {
  return false;
 }
 
 public function current() {
  if ($this->key === -1) {
   if (!$this->runQuery()) {
    $this->valid = false;
    return false;
   } else {
    $this->next();
   }
  }
  return $this->current;
 }
 
 public function key() {
  return $this->key;
 }
 
 public function next() {
  $this->current = $this->statement->fetch(PDO::FETCH_ASSOC);
  if ($this->current) {
   $this->key++;
   if (!$this->valid) {
    $this->valid = true;
   }
   return true;
  } else {
   $this->statement = null;
   $this->valid = false;
   return false;
  }
 }
 
 protected function runQuery() {
  $this->statement = $this->database->prepare($this->query);
  $this->statement->execute($this->parameters);
 }
 
 public function valid() {
  return $this->valid;
 }
 
 public function setParameters($params) {
  $this->parameters = $params;
 }
 
 public function __construct($database, $query) {
  $this->database = $database;
  $this->query = $query;
  $this->parameters = null;
  $this->current = null;
  $this->key = -1;
  $this->valid = true;
 }
}

The following two code chunks show how to use this class in the application logic, and then later on when displaying the results of the query, assuming an already open connection in $database, an instance of the MySQL flavor of PDO:

// First, run the query and get the list
$query = 'SELECT `id`, `name` FROM `users` ORDER BY `name`';
$users = new PDOIterator($database, $query);

<!-- Later in the application, output the list -->
<ol>
<?php foreach ($users as $user) { ?>
 <li><a href="?id=<?php echo (int)$user['id']; ?>">
  <?php echo Utilities::escapeXMLEntities($user['name']); ?>
 </a></li>
<?php } ?>
</ol>

The query doesn't actually run until the first iteration, so the database connection does not have any open statement until it starts the iterations. Once complete, it sets the statement to null, freeing up the connection for other queries, without having to clutter any of the application code with PDO-specific calls.

This could easily extend to other database drivers (like the mysqli-specific driver, for example), but since PDO stands to have a large rate of adoption (and since I like it), I used it instead.

Labels: ,

Saturday, May 26, 2007

Intro to Chapter 10: Game Development

I do hope to finish this game since I have it quite close, but I need to finish the book first. I considered using sprites for the ships, star, and missiles, but decided against it to practice rotation and see what canvas could handle. Also, the original version didn't exactly use image sprites, so I stuck with entirely two-dimensional shapes in canvas. Anyway, on with a couple of paragraphs:

Ajax-driven game development brings together the challenges of scalability and performance, but often allows developers to push the boundaries of current web technologies. Just as with console or computer games, users will put up with stricter minimum requirements in order to have the better experience with the more advanced technologies available, properly used.

This chapter will focus on Universe Conflict, an implementation of Space War!, one of the first digital computer games, created in 1961 on the PDP-1 computer, as recreated using the canvas HTML5 element and Ajax (shown below). This allows the two players to battle each other from different machines, as opposed to the same one as in the original and ports since then. The game has very simple rules and a simple setup. Two ships, each controlled by a user, try to shoot each other without getting pulled into a star in the center of the screen.

Two ships poised to attach each other around a star

Sidenote: you can see the rendering in action in this simple demo of Universe Conflict, which just allows you to click a button and fly each ship around the screen using the arrow keys. Not terribly exciting, since I took the networking bits out, haven't created gravity, and only works in Opera and Firefox for now (Safari has some flickering and keyboard issues that I just haven't bothered to fix yet), but it should give a decent idea of how canvas can handle animation. I haven't tested this a whole lot, so if it sucks for you, let me know.

Labels: , ,

Monday, May 14, 2007

Slight knock on PHP in the section on coding standards

I admittedly held back quite a bit from how much I could easily rant on the topic of PHP's complete lack of consistency when it comes to its function/object library. However, I need to finish the chapter on documentation, not how much PHP's lack of consistency annoys me. As a result, the following excerpt reads much more like a hint of slight frustration than the outburst of obscenities uttered as I hit <ctrl>+<l>, <p>, <tab>, <return> to go back to the PHP manual for the twelfth time that day.

Coding conventions should also include variable, function, method, and object naming practices. When languages support upper-case and lower-case alphanumerics, underscores, and sometimes even the dollar sign character, function libraries and APIs have the potential to include a wide variety of calls available to developers. The following four PHP library functions, while all consistently lower-case, have different parameter ordering and variable naming:

strpos ( string $haystack, mixed $needle [, int $offset] )
str_split ( string $string [, int $split_length] )
explode ( string $delimiter, string $string [, int $limit] )

The strpos and str_split functions in particular should not have differences in their naming, as they reside within in the same categorization of library functions in the PHP Manual, but one has an underscore separating the "str" prefix from the full word of "split" while the other has the "str" prefix unseparated from the abbreviated "pos" instead. Looking at str_split and explode, these two functions have very similar functionality: str_split breaks a string into an array of substrings of a constant length, while explode breaks a string into an array of strings as divided by a passed delimiter. Unfortunately, while str_split takes the string as the first parameter and the split length as the second, explode takes the delimiter as the first parameter and the string as the second, requiring calls to pass an empty string as the first parameter in order to break the string into an array of characters.

No matter what the conventions decided, they should not deviate so far from standard practice that new developers have a difficult time working in the code. By using tabs, consistent naming conventions, and consistent parameter ordering, developers should have the ability to "just know" what a library function or API call looks and acts like, and developers will spend less time researching functions and more time using them.

Labels: , ,

Tuesday, May 08, 2007

Practice with the canvas tag: cellular automata

I decided to do a quick study on the canvas tag (which I'll use in the chapter on game development) before diving into the next chapter, so I ported an old PHP script that generated one-dimensional cellular automata to a JavaScript and <canvas> cellular automata display. I recommend numbers 30, 60, 182, and 225 (see Elementary Cellular Automaton on Mathworld for more, including previews of all 256 of them).

This obviously will not work in IE, and I have it defaulting to rendering them line by line so that it doesn't seize up Firefox and Safari for a few seconds (depending on which one you pick) while it draws it out. Opera displays it incredibly quickly and seems the only browser to have threaded rendering rather than locking up the browser until it finishes when drawing it all at once.

Labels: , ,

Saturday, May 05, 2007

Support for different HTTP status codes with Ajax

While writing about adding support for different status codes into the AjaxRequest object (very easy, as I'll show momentarily), I made a little test page to try each of the initial set: HTTP Status Codes. Just click each link (check Firebug) and it optionally fetches all, part, or none of a 400-line file.

Now on to the excerpt:

Most browsers and XML feed readers take full advantage of the HTTP/1.1 specification to greatly reduce the data sent from the server, and the XMLHttpRequest object gives developers access to the same functionality. Two intertwined aspects of the HTTP/1.1 specification in particular can help Ajax-driven applications: status codes and cache-control.

Supporting these in the previously defined AjaxRequest object comes easily. It already supports setting custom headers through its headers object, just like setting GET and POST variables. In order to support the various status codes that the server can return, it just takes adding event types and adding some more flexibility to the stateChanged() method by changing it from:

// Event dispatching
AjaxRequest.prototype.events = {
  abort : [],
  data : [],
  load : [],
  open : [],
  send : [],
 };

// Callback for this.xhr.onreadystatechanged
AjaxRequest.prototype.stateChanged = function() {
 // Only trigger load if finished returning
 switch(this.xhr.readyState) {
  case 3:
   var e = new AjaxEvent(this);
   this.dispatchEvent('data', e);
   break;
  case 4:
   // Only continue if status OK
   if (this.xhr.status == 200) {
    var e = new AjaxEvent(this);
    this.dispatchEvent('load', e);
   }
 }
}

...to a new version that can handle multiple status codes:

// Event dispatching
AjaxRequest.prototype.events = {
  abort : [],
  data : [],
  internalservererror : [],
  load : [],
  notfound : [],
  notmodified : [],
  open : [],
  partialload : [],
  requestedrangenotsatisfiable : [],
  send : [],
  unauthorized : []
 };
// Simple lookup of event types by status code
AjaxRequest.prototype.statusCodeEvents = {
  200 : 'load',
  206 : 'partialload',
  304 : 'notmodified',
  401 : 'unauthorized',
  404 : 'notfound',
  416 : 'requestedrangenotsatisfiable',
  500 : 'internalservererror'
 };
// Callback for this.xhr.onreadystatechanged
AjaxRequest.prototype.stateChanged = function() {
 // Only trigger load if finished returning
 switch(this.xhr.readyState) {
  case 3:
   var e = new AjaxEvent(this);
   this.dispatchEvent('data', e);
   break;
  case 4:
   if (this.statusCodeEvents[this.xhr.status]) {
    var e = new AjaxEvent(this);
    this.dispatchEvent(this.statusCodeEvents[this.xhr.status], e);
   }
 }
}

The new implementation of stateChanged() now simply triggers the event mapped to the returned status code from the request, if the AjaxRequest object implements that event type. While the list of status codes only includes the most commonly used codes for this chapter's usage, it can include any additional codes added to the events and statusCodeEvents objects.

Labels: , , , ,