http://www.hbcms.com/main/smarty/index.html
This is an example of a full working PHP application using Smarty. The purpose of this example is to demonstrate how Smarty ties in with an application and how to separate your presentation. The example given is a fairly simple yet complete mini-framework for building Smarty driven applications quickly and easily. Once you understand the concepts of presentation separation, you should be able to apply them to any type of programming pattern. With that said, you may use the following code at your own discretion, and at your own risk.
You can download the source of this Sample Application here.
This is not a guide on how to setup Apache, PEAR or MySQL. Be sure you know these things or have handy references to them. If you are using alternatives, you will need to make appropriate adjustments in the code.
We will be building a guestbook where users can sign it or view it. There is no administration interface. We will be covering a few programming topics that involve Smarty such as form processing and database data retrieval and display.
This example extends the guestbook application setup given in the installation guide for Smarty, so we'll build on top of that. Here are the files we'll start with for our app:
guestbook app files/directories |
---|
/web/www.example.com/docs/ /web/www.example.com/docs/guestbook/ /web/www.example.com/docs/guestbook/index.php /web/www.example.com/smarty/guestbook/ /web/www.example.com/smarty/guestbook/templates/ /web/www.example.com/smarty/guestbook/templates_c/ /web/www.example.com/smarty/guestbook/configs/ /web/www.example.com/smarty/guestbook/cache/ /web/www.example.com/smarty/guestbook/libs/ /web/www.example.com/smarty/guestbook/libs/guestbook_setup.php /web/www.example.com/smarty/guestbook/libs/guestbook.lib.php /web/www.example.com/smarty/guestbook/libs/sql.lib.php |
Lets go over each one:
/web/www.example.com/docs/ |
The /docs/ directory is our web server document root.
/web/www.example.com/docs/guestbook/ |
/guestbook/ is the subdirectory where our application is accessed by the browser.
/web/www.example.com/docs/guestbook/index.php |
index.php will be the entry point of our application. The web browser will be accessing this script directly via http://www.example.com/guestbook/index.php.
/web/www.example.com/smarty/guestbook/ |
This is the directory we will keep all files for our guestbook app that do not need to be under doc root. Whether you choose to keep files under doc root is up to you, but for this example we follow the practice of putting only files directly accessed by the browser there. You could also use Apache .htaccess or other web server means to stop direct access to application files under doc root.
/web/www.example.com/smarty/guestbook/templates/ |
This where we will put our Smarty template files.
/web/www.example.com/smarty/guestbook/templates_c/ |
This is where Smarty places its compiled template files. If you installed this correctly, the web server user running PHP has write access here. For most intents and purposes you can just ignore this directory.
/web/www.example.com/smarty/guestbook/configs/ |
This is where we keep config files for our application. Config files are a place to store information that you want accessible from either the templates or the application. These are not PHP scripts, they are text files parsed by the Smarty config file parser.
/web/www.example.com/smarty/guestbook/cache/ |
This is where Smarty puts its cache files. This directory is only used if Smarty caching features are enabled. If you installed this correctly, the web server user running PHP has write access here. Much the same as the compile directory, it can be ignored.
/web/www.example.com/smarty/guestbook/libs/ |
/libs/ is the directory we'll keep our main application files.
/web/www.example.com/smarty/guestbook/libs/guestbook_setup.php |
guestbook_setup.php is where we'll keep some basic initialization information for our application.
/web/www.example.com/smarty/guestbook/libs/guestbook.lib.php |
guestbook.lib.php is where we'll keep the bulk of our application logic.
/web/www.example.com/smarty/guestbook/libs/sql.lib.php |
sql.lib.php is where we keep our database access logic.
We'll start with index.php, the entry point of our application. This is the file directly accessed by the web browser.
/web/www.example.com/docs/guestbook/index.php |
---|
<?php |
The index.php file acts as the application controller. It handles all incoming browser requests and directs what actions to take. It will define our application directories, include the setup script, and direct an action depending on the action value from the $_REQUEST super-global. We will have three basic actions: add when a user wants to add an entry to the guestbook, submit when a user submits an entry, and view when the user displays the guestbook. The default action is view.
/web/www.example.com/smarty/guestbook/libs/guestbook_setup.php |
---|
<?php |
guestbook_setup.php is where we do some basic application configuration, such as our database and template configs. We will be using the PEAR::DB library available from PEAR. Be sure DB.php is in your include_path, or supply an absolute path to it. We will be using MySQL as our database, enter the appropriate dsn information for your database setup.
NOTE: If you get a runtime error similar to Call to undefined function: query(), it is likely that your $dsn information is incorrect. Check it twice, test your db connection.
We will be needing a basic database setup. The following is a script that you can dump directly into MySQL with mysql < guestbook.sql. Be sure you change the GRANT line with your database/user information.
guestbook.sql |
---|
CREATE DATABASE GUESTBOOK; USE GUESTBOOK; CREATE TABLE GUESTBOOK ( id int(11) NOT NULL auto_increment, Name varchar(255) NOT NULL default '', EntryDate datetime NOT NULL default '0000-00-00 00:00:00', Comment text NOT NULL, PRIMARY KEY (id), KEY EntryDate (EntryDate) ) TYPE=MyISAM; GRANT ALL ON GUESTBOOK.* to guestbook@localhost identified by 'foobar'; |
/web/www.example.com/smarty/guestbook/libs/sql.lib.php |
---|
<?php |
sql.lib.php is our database wrapper class around PEAR::DB. It will help keep the database access syntax in our application to a minimum. You can just copy and paste the above code, don't worry too much about understanding it unless you feel inclined. Here is a crash course on the usage:
$guestbook->sql->query("select * from GUESTBOOK", SQL_ALL); print_r($guestbook->sql->record); |
OUTPUT: |
---|
Array ( [0] => Array ( [0] => 1 [1] => Monte [2] => 2005-03-12 17:23:32 [3] => test entry 1 ) [1] => Array ( [0] => 2 [1] => Monte [2] => 2005-03-12 17:23:33 [3] => test entry 2 ) [2] => Array ( [0] => 3 [1] => Monte [2] => 2005-03-12 17:23:35 [3] => test entry 3 ) ) |
All of the guestbook entries are shown. SQL_ALL will get all of the query records.
$guestbook->sql->query("select * from GUESTBOOK"); while($guestbook->sql->next()) { print_r($guestbook->sql->record); } |
OUTPUT: |
---|
Array ( [0] => 1 [1] => Monte [2] => 2005-03-12 17:23:32 [3] => test entry 1 ) Array ( [0] => 2 [1] => Monte [2] => 2005-03-12 17:23:33 [3] => test entry 2 ) Array ( [0] => 3 [1] => Monte [2] => 2005-03-12 17:23:35 [3] => test entry 3 ) |
This loops over the records one by one. If no second parameter is supplied to query(), then the resulting records are looped over with next().
$guestbook->sql->query("select * from GUESTBOOK", SQL_INIT); print_r($guestbook->sql->record); |
OUTPUT: |
---|
Array ( [0] => 1 [1] => Monte [2] => 2005-03-12 17:23:32 [3] => test entry 1 ) |
This outputs only one record (the first one). SQL_INIT will get one record only.
$guestbook->sql->query("select * from GUESTBOOK", SQL_INIT, SQL_ASSOC); print_r($guestbook->sql->record); |
OUTPUT: |
---|
Array ( [id] => 1 [Name] => Monte [EntryDate] => 2005-03-12 17:23:32 [Comment] => test entry 1 ) |
Passing a third parameter of SQL_ASSOC to query() will return the results as an associative array: fieldname => value.
$guestbook->sql->query("select * from GUESTBOOK"); while($guestbook->sql->next(SQL_ASSOC)) { print_r($guestbook->sql->record); } |
OUTPUT: |
---|
Array ( [id] => 1 [Name] => Monte [EntryDate] => 2005-03-12 17:23:32 [Comment] => test entry 1 ) Array ( [id] => 2 [Name] => Monte [EntryDate] => 2005-03-12 17:23:33 [Comment] => test entry 2 ) Array ( [id] => 3 [Name] => Monte [EntryDate] => 2005-03-12 17:23:35 [Comment] => test entry 3 ) |
Passing SQL_ASSOC as a parameter to next() will also return results as an associative array.
/web/www.example.com/smarty/guestbook/libs/guestbook.lib.php |
---|
<?php |
guestbook.lib.php is our application class. It contains the main logic for our entire application. Lets look at each class method.
class method Guestbook() |
---|
/** * class constructor */ function Guestbook() { // instantiate the sql object $this->sql =& new GuestBook_SQL; // instantiate the template object $this->tpl =& new Guestbook_Smarty; } |
This is the class constructor. It is executed each time we instantiate the guestbook object. It instantiates the SQL and Smarty objects as properties. We can then access them from within our object methods.
class method displayForm() |
---|
/** * display the guestbook entry form * * @param array $formvars the form variables */ function displayForm($formvars = array()) { // assign the form vars $this->tpl->assign('post',$formvars); // assign error message $this->tpl->assign('error', $this->error); $this->tpl->display('guestbook_form.tpl'); } |
The displayForm() method is used for displaying the guestbook entry form. It assigns the form variables and the form validation error message to the template, then displays the form.
class method mungeFormData() |
---|
/** * fix up form data if necessary * * @param array $formvars the form variables */ function mungeFormData(&$formvars) { // trim off excess whitespace $formvars['Name'] = trim($formvars['Name']); $formvars['Comment'] = trim($formvars['Comment']); } |
The mungeFormData() method trims off whitespace from the form input. This is called prior to form validation. Notice the form data is passed into the method by reference so the changes will affect the original array.
class method isValidForm() |
---|
/** * test if form information is valid * * @param array $formvars the form variables */ function isValidForm($formvars) { // reset error message $this->error = null; // test if "Name" is empty if(strlen($formvars['Name']) == 0) { $this->error = 'name_empty'; return false; } // test if "Comment" is empty if(strlen($formvars['Comment']) == 0) { $this->error = 'comment_empty'; return false; } // form passed validation return true; } |
The method isValidForm() validates the form input. This is a simple test to see if the Name or Comment was empty. If so, the appropriate error code is assigned to the error property. (These error codes are used by the template later on.)
class method addEntry() |
---|
/** * add a new guestbook entry * * @param array $formvars the form variables */ function addEntry($formvars) { $_query = sprintf( "insert into GUESTBOOK values(0,'%s',NOW(),'%s')", mysql_escape_string($formvars['Name']), mysql_escape_string($formvars['Comment']) ); return $this->sql->query($_query); } |
The addEntry method enters a new guestbook entry into the database. Notice the values are escaped to avoid SQL syntax errors or injection attacks.
class method getEntries() |
---|
/** * get the guestbook entries */ function getEntries() { $this->sql->query( "select * from GUESTBOOK order by EntryDate", SQL_ALL, SQL_ASSOC ); return $this->sql->record; } |
The method getEntries() gets all the guestbook entries from the database in field => value format (SQL_ASSOC).
class method displayBook() |
---|
/** * display the guestbook * * @param array $data the guestbook data */ function displayBook($data = array()) { $this->tpl->assign('data', $data); $this->tpl->display('guestbook.tpl'); } |
The method displayBook() displays the guestbook entries. The $data array is expected to be an array of the guestbook entries. This is assigned to the template and then the template is displayed.
We have two templates for our guestbook, one for viewing and one for adding a new entry.
/web/www.example.com/smarty/guestbook/templates/guestbook.tpl |
---|
{* Smarty *} <table border="0" width="300"> <tr> <th colspan="2" bgcolor="#d1d1d1">Guestbook Entries (<a href="{$SCRIPT_NAME}?action=add">add</a>)</th> </tr> {foreach from=$data item="entry"} <tr bgcolor="{cycle values="#dedede,#eeeeee" advance=false}"> <td>{$entry.Name|escape}</td> <td align="right">{$entry.EntryDate|date_format:"%e %b, %Y %H:%M:%S"}</td> </tr> <tr> <td colspan="2" bgcolor="{cycle values="#dedede,#eeeeee"}">{$entry.Comment|escape}</td> </tr> {foreachelse} <tr> <td colspan="2">No records</td> </tr> {/foreach} </table> |
guestbook.tpl is the template for viewing the guestbook. It loops over the guestbook data (which was assigned from displayBook()) in a foreach loop and displays the Name, Date and Comment from each entry. The date is formatted with the date_format modifier. The Name and Comment are HTML-escaped using the escape modifier to avoid any HTML tag clashes or scripting attacks. The {cycle} function is used to cycle through background colors every two table rows.
/web/www.example.com/smarty/guestbook/templates/guestbook_form.tpl |
---|
{* Smarty *} <form action="{$SCRIPT_NAME}?action=submit" method="post"> <table border="1"> {if $error ne ""} <tr> <td bgcolor="yellow" colspan="2"> {if $error eq "name_empty"}You must supply a name. {elseif $error eq "comment_empty"} You must supply a comment. {/if} </td> </tr> {/if} <tr> <td>Name:</td> <td><input type="text" name="Name" value="{$post.Name|escape}" size="40"></td> </tr> <tr> <td valign="top">Comment:</td> <td><textarea name="Comment" cols="40" rows="10">{$post.Comment|escape}</textarea></td> </tr> <tr> <td colspan="2" align="center"><input type="submit" value="Submit"></td> </tr> </table> </form> |
guestbook_form.tpl is the template for adding an entry to the guestbook. If the form is being redisplayed due to a validation error, the form values are repopulated and the appropriate error message is displayed. The form values are HTML-escaped so there are no HTML tag or quote character clashes (very important!)
With this sample application we have accomplished several key aspects of a Smarty driven application.
* All presentation elements are contained in the template. We don't assign HTML tags or other presentation elements from outside the template. The only thing we assign is the page content, in this case the guestbook entries.
* Error messages are also maintained from the template. We don't assign error messages themselves, but error codes which are used to determine which error message to display. An alternative way to maintain error messages are from within Smarty config files, where you can have error_code = Error Message in the config file, then displayed with {$smarty.config.$error_code}
* PHP objects are used extensively to show their usefulness for easily passing information around (such as sql/template objects and error codes) avoiding procedural functions and clunky parameter passing.
Hopefully this gives you an idea how to setup your applications to work with Smarty in a way that cleanly separates the application from its presentation.