Daily Archive for August 19th, 2008

Generating a PHP View

Creating PHP Views

In the Zend Framework (a recent and high-quality framework for PHP 5), a view is a way of specifying how the data associated to the underlying data model should be represented, in terms of HTML code. An example of view file would be the following:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
<html>
  <head>
    <title><?=i18n('website title')?></title>
     <?foreach($this->meta as $equiv => $value){?>
       <meta http-equiv="<?=$equiv?>" value="<?=$value?>"/>
    <?}?>
  </head>
  <body>
    <div class="navig"><?display($this->menu);?></div>
    <div class="content"><?display($this->content);?></div>
    <div class="footer"><?display($this->footer);?></div>
  </body>
</html>

I won’t go into the details of explaining why this is a good idea (that is a subject for a Dynamic Wednesdays article, after all), but the overall idea is that it separates the display information (the HTML layout) from the data itself (the various member variables of the $this object, meaning that several layouts can be used for the same object and present things differently.

Why, however, does this belong in Functional Tuesdays? What isn’t obvious above is that this view was (aside for the beautification of adding colors, line breaks and indentation) generated by a piece of Objective Caml code, illustrated below:

open Html
let doc = document
  ~head:[
    title (i18n "website title");
    foreach "$this->meta" "$equiv" "$value" [ meta (var "$equiv") (var "$value") ]]
  ~body:[
    div ~a:["class", "navig"] [ show "$this->menu" ];
    div ~a:["class", "content"] [ show "$this->content" ];
    div ~a:["class", "footer"] [ show "$this->footer" ]]

An HTML generator generator

The underlying idea is very simple: one uses the Html module to write a description of an HTML document, which may include inline PHP code such as that used by a view. Then, the program is compiled and run, and outputs the described document, which may be uploaded to an appropriate PHP web server.

What is the point?

  • Elementary safety: unlike PHP, the Objective Caml is statically compiled. This means that a lot of verifications (such as checking for existing functions and variables) can be done automatically to detect such errors. By contrast, one would have to test all the possible paths in a view template to determine that no error happens.
  • Additional verifications: although those are not readily done by the OCaml type system, it’s possible to perform static checks on the generated code. This includes generated XHTML code validation (examining the generated XHTML tree against a provided DTD), automatic string literal escaping, and even some analysis of the inline PHP code.
  • Reflection: it’s possible to extract a simplified tree that displays all the arguments that were provided to show, “class” or i18n, allowing the automatic construction of a piece of documentation (required $this fields), CSS wishlist (used classes) and translation tables (used internationalization labels).
  • Factoring: since OCaml has a much more expressive set of semantics than a typical view template, it becomes easier to define frequently recurring subexpressions (such as a mandatory wrapper around a block) into a simple function.
  • Automatic generation: if a compiler is using PHP as a target language, then this module can be used as a generator.

Implementation fundamentals

How is this implemented? The simplest way is simply to have functions return a string representing the HTML they generate. Later on, this return type could change to a product type containing both the generated string and various other information for extraction of verification purposes. The fundamental functions used here are:

let flatten xl = concat "" xl
let attrs l    = flatten (map (fun (n,v) -> " "^n^"=\""^v^"\"") l) 

let tag t a i  = sprintf "<%s%s>\n%s\n</%s>\n" t (attrs a) (flatten i) t
let ctag t a   = sprintf "<%s%s/>\n" t (attrs a)
let ptag c     = sprintf "<?%s?>\n" c

These functions allow the creation of well-formed tags. Since one wishes to allow the usual “a tag contains a list of tags” approach frequently in XML, a good idea is to use a flattening function to turn a list of strings into a single string, and consider the contents of a tag to be a list of strings. Attributes are handled as name/value pairs and accordingly printed out where applicable.

Once these functions are provided, it’s easy to write helpers for elementary XHTML tags:

let div        ?(a = []) x = tag "div" a x
let ul         ?(a = []) x = tag "ul" a x
let ol         ?(a = []) x = tag "ol" a x
let span       ?(a = []) x = tag "span" a x
let li         ?(a = []) x = tag "li" a x
let p          ?(a = []) x = tag "p" a x
let blockquote ?(a = []) x = tag "blockquote" a x
let a          ?(a = []) x = tag "a" a x 

let title x  =  tag "title" [] [x]
let meta e c = ctag "meta" ["http-equiv",e; "content",c]
let i18n s   = ptag ("=i18n('"^s^"')")

Here, optional arguments are used to provide attribute lists when necessary, without forcing them to be used if not necessary. The same can be done for the classic PHP control statements:

let var e = ptag ("="^e)
let run e = ptag (e^";")
let show o = ptag ("display("^o^");")
let ifthen c i = ptag ("if("^c^"){") ^ flatten i ^ ptag "}"
let ifelse c i e = ptag ("if("^c^"){") ^ flatten i ^ ptag "}else{" ^ flatten e ^ ptag "}"
let foreach s k v e =
  ptag (sprintf "foreach(%s as %s => %s){" s k v) ^ flatten e ^ ptag "}"

For these two code sets, OCaml will check the syntax of the generator program, which will implicitly test the syntax of the generated program for validity (as far as brace and tag balancing are involved). Of course, variable existence or initialization issues are not necessarily solved (this will be done in future situations).



1150 feed subscribers
(readers who polled a feed this week)