Easy PHP Framework tutorial

Warning: my English is terrible :-). Any corrections are welcome!

I will present the Easy PHP Framework on a simple page with comments. On the beginning of the page will be listing of all existing comments and then there will be a form for adding new one.

Database model will be extremely simple. For each comment we will save this information:

SQL script for creating the database with tables (for each step there is an extra table) and with complete source code of this tutorial you can download here.

1. Step

In this first step we will only use message body, sender's name and email will be added later.

We will need just three pages. index.php page will be showing comments and a form and a do_add_comment.php page will be "action page", which will insert new comment into the database. By "action page" I mean PHP script which will do some action and then redirect the browser to some other page, i.e. the page does not have any HTML output by itself. The last page we need is error.php which will be used for showing errors.

We need to create necessary directory structure. We will somewhere create directory for the created application. This directory must have two subdirectories:

Now we need to create configuration file. Its name must be config.php and it must exist even if it is empty. In this tutorial we will use database support embedded into the EPF, so we must set variables to correct values for database access:

<?php

  $mysql_host = 'localhost';
  $mysql_username = 'root';
  $mysql_password = '';
  $mysql_dbname = 'epf_tutor';

?>

You will of course have to adjust these values to match your configuration. EPF database layer is really simple, currently supporting just MySQL 4.1.3 and higher through mysqli. With the upcoming PHP 5.1 with the PHP Data Objects it won't be probably needed anymore.

Now we will create auxiliary template common.xsl and put it into the view directory. It will contain code which is common for most pages. Root element of XML documents produced by EPF is page element. In our case will this common template generate basic XHTML skeleton and then it will call <xsl:apply-templates/>, which will fill the content.

page element has title attribute which we will use for filling document's title and main header. Here is complete code:

<?xml version="1.0"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output indent="yes" method="xml" version="1.0" omit-xml-declaration="yes"
            doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
            doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" />

<xsl:template match="/">

  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
    <head>
      <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
      <title><xsl:value-of select='/page/@title'/></title>
    </head>
    <body>
      <h1><xsl:value-of select='/page/@title'/></h1>
      <xsl:apply-templates/>
    </body>
  </html>

</xsl:template>

</xsl:stylesheet>

Now we can create our index page. Comments will be in the input XML represented as comment elements with message attribute which will contain the text of the comment. Content of the page will be generated by a rule, which selects the page element. Resulting code is quite straightforward:

<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:import href="common.xsl"/>

<xsl:template match="/page">

<p>Hello, this is small forum.</p>
<h2>Existing comments</h2>

<xsl:choose>
  <xsl:when test='count(comment) > 0'>
    <xsl:for-each select='comment'>
      <hr />
      <p><xsl:value-of select='@message'/></p>
    </xsl:for-each>
  </xsl:when>
  <xsl:otherwise>
    <hr />
    <p>Sorry, currently no comments.</p>
  </xsl:otherwise>
</xsl:choose>
<hr />

<h2>New comment</h2>
<form action='do_add_comment.php' method='post'>
  <table>
    <tr>
      <th>Comment message</th>
      <td>
        <textarea cols='50' rows='5' name='message'></textarea>
      </td>
    </tr>
    <tr><td><input type='submit' /></td></tr>
  </table>
</form>

</xsl:template>

</xsl:stylesheet>

Last needed template is error.xsl. This template is used by EPF when fails validation of inputs (GET, POST) or when application throws an exception. Errors are represented by msg elements with error message in the desc attribute. It's easy to create simple error page:

<?xml version="1.0" ?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:import href="common.xsl"/>

<xsl:template match="/page">
  <h1>Error!</h1>
  <xsl:for-each select='msg'>
    <p><xsl:value-of select='@desc' /></p>
  </xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Now we will create code for the title page which will show existing comments. First thing we must do is to include EPF. For this purpose there is main.php script in the epf directory:

<?php

  require_once('epf/main.php');

If the script is not run from the directory where is located (e.g. if it's run by cron), you will have to set the script's directory as current directory, e.g. by executing chdir(dirname(__FILE__)); code before using the require_once statement.

Pages are in EPF represented by descendants of the Servlet class:

  class IndexServlet extends Servlet {

In pages which creates some HTML output you should override get_title() method, which should return title of the page:

    function get_title() {
      return 'EPF tutorial';
    }

The activity of the page is located in the overriden work() method. First of all we will call query() function to get all comments. This function will return instance of the DbQuery class, which implements Iterator interface, so we can traverse rows returned by database using foreach. The rows are returned in a form of objects. We will add them into the page using add method. First parameter is name of the element, second is an array of attributes.

    function work() {
      $q = query('SELECT `message` FROM `tutor1_comments`');
      foreach ($q as $comment):
        $this->add('comment', array('message' => $comment->message));
      endforeach;
    }

Now we simply create an instance and call run() method:

  }

  $servlet = new IndexServlet();
  $servlet->run();

?>

OK, now we handle adding of comments. We will override validate_input() method. It will read message value, which is send from form using the POST method. We will use req_post_string() method for this. It has three parameters: name, variable and error message. There are several such methods. They have all this shape: (req|opt)_(get|post)_type.

req means that this value is REQuired. If it is missing or have wrong form (depends on type) the error message is used. The opt methods are for OPTional parameters and they haven't error message argument.

post is for POST and get is for GET. Ahem :-).

Supported types:

We add the comment using SQL which we will pass to the update() function. There is also insert() function, which is nearly the same, except that it also returns a value of insert id for tables which has the AUTO_INCREMENT column. Complete code of do_add_comment.php:

<?php
  require_once('epf/main.php');

  class DoAddCommentServlet extends Servlet {

    private $message;

    function validate_input() {
      $this->req_post_string('message', $this->message, 'Please enter some message text.');
    }

    function work() {
      update("INSERT INTO `tutor1_comments` SET `message`='$this->message'");
      go_to('');
    }

  }

  $servlet = new DoAddCommentServlet();
  $servlet->run();

?>

And this is all. First version is complete. You can now browse all of the created files on-line.

2. Step

Now we will add name of the author to the comment.

Changes in theindex.xsl are simple:

<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:import href="common.xsl"/>

<xsl:template match="/page">

<p>Hello, this is small forum.</p>
<h2>Existing comments</h2>

<xsl:choose>
  <xsl:when test='count(comment) > 0'>
    <xsl:for-each select='comment'>
      <hr />
<p><b>Name: <xsl:value-of select='@name'/></b></p>
<p><xsl:value-of select='@message'/></p> </xsl:for-each> </xsl:when> <xsl:otherwise> <hr /> <p>Sorry, currently no comments.</p> </xsl:otherwise> </xsl:choose> <hr /> <h2>New comment</h2> <form action='do_add_comment.php' method='post'> <table>
<tr> <th>Name</th> <td> <input type='text' name='name'></input> </td> </tr>
<tr> <th>Comment message</th> <td> <textarea cols='50' rows='5' name='message'></textarea> </td> </tr> <tr><td><input type='submit' /></td></tr> </table> </form> </xsl:template> </xsl:stylesheet>

Also changes in do_add_comment.php are not complicated.

<?php
  require_once('epf/main.php');

  class DoAddCommentServlet extends Servlet {

    private $message;
private $name;
function validate_input() { $this->req_post_string('message', $this->message, 'Please enter some message text.');
$this->req_post_string('name', $this->name, 'Please enter your name.');
} function work() { update(<<<SQL INSERT INTO `tutor2_comments` SET `message`='$this->message',
`name`='$this->name'
SQL ); go_to(''); } } $servlet = new DoAddCommentServlet(); $servlet->run(); ?>

And index.php:

<?php

  require_once('epf/main.php');

  class IndexServlet extends Servlet {

    function get_title() {
      return 'EPF tutorial';
    }

    function work() {
$q = query('SELECT `name`, `message` FROM `tutor2_comments`');
foreach ($q as $comment): $this->add('comment', array( 'message' => $comment->message,
'name' => $comment->name
)); endforeach; } } $servlet = new IndexServlet(); $servlet->run(); ?>

So, this step was quite easy. Complete code.

3. Step

Now we will add optional email entry. To prevent email from spam robots we will replace the @ (at sign) and . (dot) with corresponding pictures. We will use email() for this. It means that we will from the string "a@b.cz" create XHTML "a <img src="at.gif" alt="at" /> b <img src="dot.gif" alt="dot" /> cz". email() function won't return it as a string, but as an array of DOMNode instances (or more precisely its descendants DOMText and DOMElement):

Inserting the email into the database is quite simple. We will add email entry to the form:

    <tr>
      <th>Email</th>
      <td>
        <input type='text' name='email'></input>
      </td>
    </tr>

Also changes in do_add_comment.php are not surprising. As the email is optional we must initialize the $email variable:

<?php
  require_once('epf/main.php');

  class DoAddCommentServlet extends Servlet {

    private $message;
    private $name;
private $email = '';
function validate_input() { $this->req_post_string('message', $this->message, 'Please enter some message text.'); $this->req_post_string('name', $this->name, 'Please enter your name.');
$this->opt_post_string('email', $this->email);
} function work() { update(<<<SQL INSERT INTO `tutor3_comments` SET `message`='$this->message', `name`='$this->name',
`email`='$this->email'
SQL ); go_to(''); } } $servlet = new DoAddCommentServlet(); $servlet->run(); ?>

As I already said, we will use the email() function for generating email. This function takes as a parameter — and so does all of the XML generating functions — instance of DOMDocument, which we are using for generating XML. We use the get_doc() method of instance of Servlet class. The output of email() function, which is the array of DOMNode instances, we can pass to the add() method of Servlet as we already did with plain string:

<?php

  require_once('epf/main.php');

  class IndexServlet extends Servlet {

    function get_title() {
      return 'EPF tutorial';
    }

    function work() {
$q = query('SELECT `name`, `email`, `message` FROM `tutor3_comments`');
foreach ($q as $comment): $this->add('comment', array( 'message' => $comment->message, 'name' => $comment->name,
'email' => email($this->get_doc(), $comment->email)
)); endforeach; } } $servlet = new IndexServlet(); $servlet->run(); ?>

The last thing is to show this email with <xsl:copy-of>:

    <xsl:for-each select='comment'>
      <hr />
      <p><b>Name: <xsl:value-of select='@name'/></b></p>
<xsl:if test='email != ""'> <p><b> Email: <xsl:copy-of select='email/node()'/> </b></p> </xsl:if>
<p><xsl:value-of select='@message'/></p> </xsl:for-each>

Notice that we have to write email/node(). If we would write just email, this element would copy to the resulting XHTML document which is something we don't want. Complete code.

4. Step

Now we will convert all newline characters (i.e. characters with ASCII code 0x0A) to the <br /> element. We will use nl2brXML() function for this:

    function work() {
      $q = query('SELECT `name`, `email`, `message` FROM `tutor4_comments`');
      foreach ($q as $comment):
        $this->add('comment', array(
'message' => nl2brXML($this->get_doc(), $comment->message),
'name' => $comment->name, 'email' => email($this->get_doc(), $comment->email) )); endforeach; }

Because this function works on the same principle as the email() function, we have to accordingly update the XSLT template:

      <p><xsl:copy-of select='message/node()'/></p>

Complete code.

5. Step

It will be handy if our miniforum can interpret basic URLs. All we need is to replace the nl2brXML() function with the nl2br_and_link2a_XML() one:

    function work() {
      $q = query('SELECT `name`, `email`, `message` FROM `tutor5_comments`');
      foreach ($q as $comment):
        $this->add('comment', array(
'message' => nl2br_and_link2a_XML($this->get_doc(), $comment->message),
'name' => $comment->name, 'email' => email($this->get_doc(), $comment->email) )); endforeach; }

Complete code.

6. Step

In this last step we will learn how to create functions like email() or nl2brXML(). We will change the index.php code so our forum will show text between [b] and [/b] with bold font.

For replacing strings with XML elements there is xml_convert_str2elem() function, which replaces given text with an element and xml_convert_regex2elem() function, which searchs for the string using the regular expression. Our first try can look like this:

$message = xml_convert_regex2elem($this->get_doc(), $message, '#(\[b\].*?\[/b\])#i', "b");

First parameter is reference to our DOMDocument, $message is the converted message, third parameters is the regular expression and the last parameter is name of the resulting element.

This code has one disadvantage: it will leave the [b] and [/b] marks in the resulting text. The xml_convert_... function can have as a fourth parameter not just plain string but also an instance of ElementCreator class so we can control how is the resulting element created. In this case we will use its descendant, ExtractingElementCreator class. We will pass to instance of this class a regular expression, where we will parenthesize the part which we want to use. Look at the resulting code:

    function work() {
      $q = query('SELECT `name`, `email`, `message` FROM `tutor6_comments`');

      foreach ($q as $comment):
$message = nl2br_and_link2a_XML($this->get_doc(), $comment->message); $message = xml_convert_regex2elem($this->get_doc(), $message, '#(\[b\].*?\[/b\])#i', new ExtractingElementCreator('#\[b\](.*?)\[/b\]#i', 'b'));
$this->add('comment', array(
'message' => $message,
'name' => $comment->name, 'email' => email($this->get_doc(), $comment->email) )); endforeach; }

Notice that we can pass to functions xml_convert_..., email(), nl2brXML() and nl2br_and_link2a_XML() not just plain string but also an array of DOMNode instances, so we can call them with result of another call to some of this functions.

Complete code.

Conclusion

Resulting application.

Complete source code of this tutorial and SQL script for creating needed database structure.

If you want to see more code which uses EPF you can study source code of the devpaks.org portal.

All remarks, suggestions, questions or bug fixes for EPF or this tutorial please tell me at the EPF forum. You can also use my email or ICQ which you can find on my homepage.