Infernal Device

10 Apr 2003

Introduction

This page describes the initial design of what became the Infernal Device. The software and documentation are available elsewhere.

(This note should probably be called "Stupid PHP tricks." This whole thing is just silly.)

I have recently been working on a web interface for a Cascabel queue (for donations, but that's not terribly relevant). I was using PHP and a more-or-less traditional, script-per-page approach, but I kept getting tangled up, particularly when I considered authentication and session handling. The code kept getting bigger and uglier, both of which are bad. I was just starting to think of other ways of approaching the problem when I ran across [AutoRestructure], [ProgTheWeb], [InvertingBack], and [BrowsInfluence] on the ll1-discuss mailing list.

Aha! I thought. This was more-or-less what I needed. Of course, PHP doesn't support continuations (that I know of), but that would hardly stop me. All I needed to do was to manually handle the continuation passing style, and the simplest way to do that was a state machine. The state machine

The state machine reverses the normal idea of states and transitions---the states are not really visible as data structures, but the transitions are represented by PHP classes. The states, in fact, are user-visible pages. The state graph below provides a concrete illustration:

stategraph

The states shown are:

start
The initial state, which the machine enters if it does not appear to be in any other state. The start state is special.
auth
Displaying the login form.
base
Displaying the menu of available actions.
admin
Showing a form for changing passwords (and presumably anything else needed, account-wise).
logout
Displaying a thank-you web page.

The Machine class, which operates the state machine, uses PHP's session handling support. When a Machine is created, it checks the SESSION (a persistent array) for a "state" entry. If it does not find one, it creates it, and sets it to "start".

From that point on, when the Machine is created it reloads the SESSION array, which contains marshalled objects of subclasses of Transition. Based on an identifier provided as either a URL query or a field in a form, it chooses an object which represents the next transition to take. The Machine calls the execute() method of the transition, which has the job of changing the state, inserting new Transition objects into the SESSION, and calling the function to display the next page. Building an application

Building an application is seemingly simple, and almost entirely mechanical, to start with. For me, this turns out to be:

  1. Draw a state graph describing the application structure in terms of user-visible pages and transitions between them.

  2. Create the initial index.cgi script. An example of one is:

    require_once("Shopreq.phpi");
    require_once("TemplateCache.phpi");
    require_once("CascabelShopreq.phpi");
    
    $t = new TemplateCache();
    $c = new ShopreqConnection();
    $m = new ShopreqMachine($t, $c);
    
    $p = $t->get_template("page");
    $p->replace("body", $m->to_string());
    echo $p->show();
    
    $c->close();
    

    The TemplateCache.phpi is a template manager from the Cascabel library, the CascabelShopreq.phpi is a wrapper for the Cascabel PHP library (it supplies the ShopreqConnection database connection), and the Shopreq.phpi is the major body of the application.

  3. Create the Shopreq.phpi body of the application. It begins with a subclass of the Machine class that, as seen above, manages the connections to the database and the template cache.

  4. Create skeletal transitions (one class per transition in the graph) and functions (one per state in the graph) for displaying state/pages. The transitions are subclasses of Transition:

    // 'list' state to 'show' state.
    class Show extends Transition { }
    

    While the functions look like:

    // Show a ticket.  State: "show"
    function show_form ($mc, $rq, $msg="") { }
    

    (The $mc is the Machine object, needed to manage the SESSION and Transitions; the $rq is the request from the browser, with information that may be needed in the form; the $msg is a message from the Transition, describing successful actions and errors.)

    The actual arguments to the function may need to be modified later, but those shown are a pretty good first estimate.

  5. Now for the part requiring work: Go through the state graph filling in the contents of the state/page functions, using the templates to provide most of the page contents, and the transition execute methods. An example of a state/page function is:

    // Show the index menu.  State: "index"
    function index_form ($mc, $rq, $msg="")
    {
      $t = $mc->get_template('index');
      $t->replace('message', $msg);
      $t->replace('action', $_SERVER['PHP_SELF']);
      $k = $mc->set_transition(new CreateShop);
      $t->replace('k:shopreq', $k->as_query());
      $k = $mc->set_transition(new CreateAV);
      $t->replace('k:av', $k->as_query());
      $k = $mc->set_transition(new GetStatus);
      $t->replace('k:query', $k->as_query());
      return $t->show();
    }
    

    This function gets the "index" template and replaces the "k:shopreq" (and so forth) antiquotations (text replacement points?) with a transition key---the set_transition method takes a Transition object, stores it in the SESSION, and returns an object describing where it was stored. This key object can generate a URL query string for the key, with an as_query method, or a form field, with an as_form_field method.

    An example Transition execute method looks like:

    class ToIndex extends Transition
    {
      function execute ($mc, $rq) {
        echo index_form($mc, $rq);
        return 'index';
      }
    }
    

    This one is exceptionally simple; it calls the function to display the index form and returns the new state, "index". The value of the new state has only one purpose: to not be "start", which is special. Other than that, it is currently uninterpreted.

Continuations

In this architecture, the Transition objects are created going into a state, are stored while the user interacts with the browser, and then used to provide the next step in the computation. In this sense, they act as simple, basic continuations.

As described above, this architecture makes creating the structure of the application code almost mechanical, up to the point where the actual processing in the transitions is needed.

References

[AutoRestructure]Automatically Restructuring Programs for the Web. Paul Graunke, Robert Bruce Findler, Shriram Krishnamurthi, and Matthias Felleisen. [Link by Jacob Matthews.]
[ProgTheWeb]Programming the Web with High-Level Programming Languages. Programming the Web with High-Level Programming Languages. Paul Graunke, Shriram Krishnamurthi, Van der Hoeven and Matthias Felleisen. [From the Continuations and Continuation Passing Style page of ReadScheme.org.
[InvertingBack]Inverting back the inversion of control, or Continuations versus page-centric programming. Christian Queinnec.
[BrowsInfluence]The Influence of Browsers on Evaluators or, Continuations to Program Web Servers. Christian Queinnec. [Both linked by Florin Mihaila.]

gloria i ad inferni
faciamus opus

Return to Top | About this site...
Last edited Sat Aug 8 03:29:10 2009.
Copyright © 2005-2016 Tommy M. McGuire